@strapi/admin 4.0.0-beta.14 → 4.0.0-beta.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/admin/src/components/AuthenticatedApp/index.js +2 -1
  2. package/admin/src/components/{UpgradePlanModal → UpgradePlanModal}/index.js +0 -0
  3. package/admin/src/content-manager/components/DynamicTable/TableRows/index.js +2 -1
  4. package/admin/src/content-manager/components/EditViewDataManagerProvider/index.js +3 -1
  5. package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/schema.js +1 -1
  6. package/admin/src/content-manager/components/SelectMany/index.js +11 -2
  7. package/admin/src/content-manager/components/SelectOne/index.js +34 -21
  8. package/admin/src/content-manager/components/SelectWrapper/index.js +4 -7
  9. package/admin/src/content-manager/components/Wysiwyg/Editor.js +3 -2
  10. package/admin/src/content-manager/components/Wysiwyg/EditorStylesContainer.js +5 -2
  11. package/admin/src/content-manager/components/Wysiwyg/WysiwygNav.js +1 -1
  12. package/admin/src/content-manager/pages/EditView/Informations/index.js +3 -2
  13. package/admin/src/content-manager/pages/EditView/index.js +3 -6
  14. package/admin/src/pages/AuthPage/components/Register/index.js +0 -9
  15. package/admin/src/pages/AuthPage/utils/forms.js +2 -2
  16. package/admin/src/pages/ProfilePage/index.js +3 -2
  17. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/components/ContentBox/index.js +5 -2
  18. package/admin/src/pages/SettingsPage/pages/ApiTokens/EditView/index.js +13 -1
  19. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/DeleteButton/index.js +6 -2
  20. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/DynamicTable/index.js +12 -2
  21. package/admin/src/pages/SettingsPage/pages/ApiTokens/ListView/index.js +7 -0
  22. package/admin/src/pages/SettingsPage/pages/Roles/ListPage/index.js +1 -1
  23. package/admin/src/pages/SettingsPage/pages/Users/EditPage/index.js +3 -3
  24. package/admin/src/pages/SettingsPage/pages/Users/ListPage/DynamicTable/TableRows/index.js +4 -3
  25. package/admin/src/pages/SettingsPage/pages/Users/ListPage/ModalForm/utils/schema.js +1 -1
  26. package/admin/src/pages/SettingsPage/pages/Users/utils/validations/users/edit.js +2 -2
  27. package/admin/src/pages/SettingsPage/pages/Users/utils/validations/users/profile.js +6 -2
  28. package/admin/src/utils/getFullName.js +9 -0
  29. package/admin/src/utils/index.js +1 -1
  30. package/build/3226.0dc582b2.chunk.js +2 -0
  31. package/build/3226.0dc582b2.chunk.js.LICENSE.txt +15 -0
  32. package/build/4362.fd69112c.chunk.js +1 -0
  33. package/build/4715.35096dd7.chunk.js +1 -0
  34. package/build/6250.11ba8b50.chunk.js +1 -0
  35. package/build/Admin-authenticatedApp.62e5ca51.chunk.js +1 -0
  36. package/build/Admin_profilePage.3aa61921.chunk.js +1 -0
  37. package/build/Admin_settingsPage.363ad01d.chunk.js +1 -0
  38. package/build/admin-edit-users.bcdd2e4d.chunk.js +1 -0
  39. package/build/admin-users.a2d08780.chunk.js +1 -0
  40. package/build/api-tokens-create-page.ac4285ba.chunk.js +1 -0
  41. package/build/api-tokens-edit-page.b8900ddd.chunk.js +1 -0
  42. package/build/api-tokens-list-page.d451255e.chunk.js +1 -0
  43. package/build/content-manager.d09d2a3a.chunk.js +1 -0
  44. package/build/index.html +1 -1
  45. package/build/main.3414cc4f.js +2 -0
  46. package/build/main.3414cc4f.js.LICENSE.txt +91 -0
  47. package/build/runtime~main.cc96a027.js +1 -0
  48. package/ee/server/controllers/authentication/middlewares.js +1 -1
  49. package/package.json +7 -7
  50. package/server/services/user.js +14 -0
  51. package/server/validation/authentication/register.js +2 -2
  52. package/server/validation/common-validators.js +1 -1
  53. package/server/validation/user.js +3 -3
@@ -9,12 +9,13 @@ import PluginsInitializer from '../PluginsInitializer';
9
9
  import RBACProvider from '../RBACProvider';
10
10
  import { fetchAppInfo, fetchCurrentUserPermissions, fetchStrapiLatestRelease } from './utils/api';
11
11
  import checkLatestStrapiVersion from './utils/checkLatestStrapiVersion';
12
+ import { getFullName } from '../../utils';
12
13
 
13
14
  const strapiVersion = packageJSON.version;
14
15
 
15
16
  const AuthenticatedApp = () => {
16
17
  const userInfo = auth.getUserInfo();
17
- const userName = get(userInfo, 'username') || `${userInfo.firstname} ${userInfo.lastname}`;
18
+ const userName = get(userInfo, 'username') || getFullName(userInfo.firstname, userInfo.lastname);
18
19
  const [userDisplayName, setUserDisplayName] = useState(userName);
19
20
  const { showReleaseNotification } = useConfigurations();
20
21
  const [
@@ -13,6 +13,7 @@ import { useHistory } from 'react-router-dom';
13
13
  import { useIntl } from 'react-intl';
14
14
  import { usePluginsQueryParams } from '../../../hooks';
15
15
  import CellContent from '../CellContent';
16
+ import { getFullName } from '../../../../utils';
16
17
 
17
18
  const TableRows = ({
18
19
  canCreate,
@@ -69,7 +70,7 @@ const TableRows = ({
69
70
  id: 'app.component.table.select.one-entry',
70
71
  defaultMessage: `Select {target}`,
71
72
  },
72
- { target: `${data.firstname} ${data.lastname}` }
73
+ { target: getFullName(data.firstname, data.lastname) }
73
74
  )}
74
75
  checked={isChecked}
75
76
  onChange={() => {
@@ -518,7 +518,9 @@ const EditViewDataManagerProvider = ({
518
518
  when={!isEqual(modifiedData, initialData)}
519
519
  message={formatMessage({ id: 'global.prompt.unsaved' })}
520
520
  />
521
- <form onSubmit={handleSubmit}>{children}</form>
521
+ <form noValidate onSubmit={handleSubmit}>
522
+ {children}
523
+ </form>
522
524
  </>
523
525
  )}
524
526
  </>
@@ -300,7 +300,7 @@ const createYupSchemaAttribute = (type, validations, options) => {
300
300
  return !isEmpty(value);
301
301
  }
302
302
 
303
- return !isEmpty(value.toString());
303
+ return !isEmpty(value?.toString());
304
304
  }
305
305
 
306
306
  return !isEmpty(value);
@@ -5,6 +5,7 @@ import isEmpty from 'lodash/isEmpty';
5
5
  import Select, { createFilter } from 'react-select';
6
6
  import { Box } from '@strapi/design-system/Box';
7
7
  import { Stack } from '@strapi/design-system/Stack';
8
+ import { Typography } from '@strapi/design-system/Typography';
8
9
  import ListItem from './ListItem';
9
10
 
10
11
  function SelectMany({
@@ -26,6 +27,7 @@ function SelectMany({
26
27
  styles,
27
28
  targetModel,
28
29
  value,
30
+ description,
29
31
  }) {
30
32
  const { formatMessage } = useIntl();
31
33
 
@@ -37,7 +39,7 @@ function SelectMany({
37
39
  };
38
40
 
39
41
  return (
40
- <>
42
+ <Stack size={1}>
41
43
  <Select
42
44
  components={components}
43
45
  isDisabled={isDisabled}
@@ -95,11 +97,17 @@ function SelectMany({
95
97
  })}
96
98
  </Stack>
97
99
  </Box>
98
- </>
100
+ {description && (
101
+ <Typography variant="pi" textColor="neutral600">
102
+ {description}
103
+ </Typography>
104
+ )}
105
+ </Stack>
99
106
  );
100
107
  }
101
108
 
102
109
  SelectMany.defaultProps = {
110
+ description: '',
103
111
  components: {},
104
112
  placeholder: null,
105
113
  searchToPersist: null,
@@ -133,6 +141,7 @@ SelectMany.propTypes = {
133
141
  styles: PropTypes.object.isRequired,
134
142
  targetModel: PropTypes.string.isRequired,
135
143
  value: PropTypes.array,
144
+ description: PropTypes.string,
136
145
  };
137
146
 
138
147
  export default memo(SelectMany);
@@ -1,6 +1,8 @@
1
1
  import React, { memo } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useIntl } from 'react-intl';
4
+ import { Stack } from '@strapi/design-system/Stack';
5
+ import { Typography } from '@strapi/design-system/Typography';
4
6
  import get from 'lodash/get';
5
7
  import isNull from 'lodash/isNull';
6
8
  import Select from 'react-select';
@@ -21,36 +23,46 @@ function SelectOne({
21
23
  placeholder,
22
24
  styles,
23
25
  value,
26
+ description,
24
27
  }) {
25
28
  const { formatMessage } = useIntl();
26
29
 
27
30
  return (
28
- <Select
29
- components={{
30
- ...components,
31
- SingleValue,
32
- }}
33
- id={name}
34
- isClearable
35
- isDisabled={isDisabled}
36
- isLoading={isLoading}
37
- mainField={mainField}
38
- options={options}
39
- onChange={onChange}
40
- onInputChange={onInputChange}
41
- onMenuClose={onMenuClose}
42
- onMenuOpen={onMenuOpen}
43
- onMenuScrollToBottom={onMenuScrollToBottom}
44
- placeholder={formatMessage(
45
- placeholder || { id: 'components.Select.placeholder', defaultMessage: 'Select...' }
31
+ <Stack size={1}>
32
+ <Select
33
+ components={{
34
+ ...components,
35
+ SingleValue,
36
+ }}
37
+ id={name}
38
+ isClearable
39
+ isDisabled={isDisabled}
40
+ isLoading={isLoading}
41
+ mainField={mainField}
42
+ options={options}
43
+ onChange={onChange}
44
+ onInputChange={onInputChange}
45
+ onMenuClose={onMenuClose}
46
+ onMenuOpen={onMenuOpen}
47
+ onMenuScrollToBottom={onMenuScrollToBottom}
48
+ placeholder={formatMessage(
49
+ placeholder || { id: 'components.Select.placeholder', defaultMessage: 'Select...' }
50
+ )}
51
+ styles={styles}
52
+ value={isNull(value) ? null : { label: get(value, [mainField.name], ''), value }}
53
+ />
54
+
55
+ {description && (
56
+ <Typography variant="pi" textColor="neutral600">
57
+ {description}
58
+ </Typography>
46
59
  )}
47
- styles={styles}
48
- value={isNull(value) ? null : { label: get(value, [mainField.name], ''), value }}
49
- />
60
+ </Stack>
50
61
  );
51
62
  }
52
63
 
53
64
  SelectOne.defaultProps = {
65
+ description: '',
54
66
  components: {},
55
67
  placeholder: null,
56
68
  value: null,
@@ -79,6 +91,7 @@ SelectOne.propTypes = {
79
91
  }),
80
92
  styles: PropTypes.object.isRequired,
81
93
  value: PropTypes.object,
94
+ description: PropTypes.string,
82
95
  };
83
96
 
84
97
  export default memo(SelectOne);
@@ -50,7 +50,7 @@ const buildParams = (query, paramsToKeep) => {
50
50
  }, {});
51
51
  };
52
52
  function SelectWrapper({
53
- // description,
53
+ description,
54
54
  editable,
55
55
  labelAction,
56
56
  intlLabel,
@@ -316,6 +316,7 @@ function SelectWrapper({
316
316
  styles={styles}
317
317
  targetModel={targetModel}
318
318
  value={value}
319
+ description={description}
319
320
  />
320
321
  </Stack>
321
322
  );
@@ -390,7 +391,7 @@ function SelectWrapper({
390
391
 
391
392
  SelectWrapper.defaultProps = {
392
393
  editable: true,
393
- // description: '',
394
+ description: '',
394
395
  labelAction: null,
395
396
  isFieldAllowed: true,
396
397
  placeholder: null,
@@ -398,11 +399,7 @@ SelectWrapper.defaultProps = {
398
399
 
399
400
  SelectWrapper.propTypes = {
400
401
  editable: PropTypes.bool,
401
- // description: PropTypes.shape({
402
- // id: PropTypes.string.isRequired,
403
- // defaultMessage: PropTypes.string.isRequired,
404
- // values: PropTypes.object,
405
- // }),
402
+ description: PropTypes.string,
406
403
  intlLabel: PropTypes.shape({
407
404
  id: PropTypes.string.isRequired,
408
405
  defaultMessage: PropTypes.string.isRequired,
@@ -31,13 +31,14 @@ const Editor = ({
31
31
  },
32
32
  readOnly: false,
33
33
  smartIndent: false,
34
+ placeholder,
34
35
  });
35
36
 
36
37
  CodeMirror.commands.newlineAndIndentContinueMarkdownList = newlineAndIndentContinueMarkdownList;
37
38
  editorRef.current.on('change', doc => {
38
39
  onChangeRef.current({ target: { name, value: doc.getValue(), type: 'wysiwyg' } });
39
40
  });
40
- }, [editorRef, textareaRef, name]);
41
+ }, [editorRef, textareaRef, name, placeholder]);
41
42
 
42
43
  useEffect(() => {
43
44
  if (value && !editorRef.current.state.focused) {
@@ -65,7 +66,7 @@ const Editor = ({
65
66
  return (
66
67
  <EditorAndPreviewWrapper>
67
68
  <EditorStylesContainer disabled={disabled || isPreviewMode}>
68
- <textarea ref={textareaRef} placeholder={placeholder} />
69
+ <textarea ref={textareaRef} />
69
70
  </EditorStylesContainer>
70
71
  {isPreviewMode && <PreviewWysiwyg data={value} />}
71
72
  </EditorAndPreviewWrapper>
@@ -5,6 +5,9 @@ import styled from 'styled-components';
5
5
  export const EditorStylesContainer = styled.div`
6
6
  cursor: ${({ disabled }) => (disabled ? 'not-allowed !important' : 'auto')};
7
7
  /* BASICS */
8
+ .CodeMirror-placeholder {
9
+ color: ${({ theme }) => theme.colors.neutral600} !important;
10
+ }
8
11
 
9
12
  .CodeMirror {
10
13
  /* Set height, width, borders, and global font properties here */
@@ -12,6 +15,8 @@ export const EditorStylesContainer = styled.div`
12
15
  height: 290px;
13
16
  color: ${({ theme }) => theme.colors.neutral800};
14
17
  direction: ltr;
18
+ font-family: --apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
19
+ 'Open Sans', 'Helvetica Neue', sans-serif;
15
20
  }
16
21
 
17
22
  /* PADDING */
@@ -331,8 +336,6 @@ export const EditorStylesContainer = styled.div`
331
336
  }
332
337
 
333
338
  span {
334
- font-family: --apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
335
- 'Open Sans', 'Helvetica Neue', sans-serif;
336
339
  color: ${({ theme }) => theme.colors.neutral800} !important;
337
340
  }
338
341
  `;
@@ -10,7 +10,7 @@ import { Flex } from '@strapi/design-system/Flex';
10
10
  import Bold from '@strapi/icons/Bold';
11
11
  import Italic from '@strapi/icons/Italic';
12
12
  import Underline from '@strapi/icons/Underline';
13
- import Strikethrough from '@strapi/icons/StrikeThrough';
13
+ import Strikethrough from '@strapi/icons/Strikethrough';
14
14
  import BulletList from '@strapi/icons/BulletList';
15
15
  import NumberList from '@strapi/icons/NumberList';
16
16
  import Code from '@strapi/icons/Code';
@@ -8,6 +8,7 @@ import { Flex } from '@strapi/design-system/Flex';
8
8
  import { Stack } from '@strapi/design-system/Stack';
9
9
  import { getTrad } from '../../../utils';
10
10
  import getUnits from './utils/getUnits';
11
+ import { getFullName } from '../../../../utils';
11
12
 
12
13
  const Informations = () => {
13
14
  const { formatMessage, formatRelativeTime } = useIntl();
@@ -17,7 +18,7 @@ const Informations = () => {
17
18
  const updatedByFirstname = initialData.updatedBy?.firstname || '';
18
19
  const updatedByLastname = initialData.updatedBy?.lastname || '';
19
20
  const updatedByUsername = initialData.updatedBy?.username;
20
- const updatedBy = updatedByUsername || `${updatedByFirstname} ${updatedByLastname}`;
21
+ const updatedBy = updatedByUsername || getFullName(updatedByFirstname, updatedByLastname);
21
22
  const currentTime = useRef(Date.now());
22
23
  const timestamp = initialData[updatedAt]
23
24
  ? new Date(initialData[updatedAt]).getTime()
@@ -28,7 +29,7 @@ const Informations = () => {
28
29
 
29
30
  return (
30
31
  <Box>
31
- <Typography variant="sigma" textColor="neutral600">
32
+ <Typography variant="sigma" textColor="neutral600" id="additional-informations">
32
33
  {formatMessage({
33
34
  id: getTrad('containers.Edit.information'),
34
35
  defaultMessage: 'Information',
@@ -252,7 +252,7 @@ const EditView = ({
252
252
  {displayedRelationsLength > 0 && (
253
253
  <Box
254
254
  as="aside"
255
- aria-labelledby="additional-informations"
255
+ aria-labelledby="relations-title"
256
256
  background="neutral0"
257
257
  borderColor="neutral150"
258
258
  hasRadius
@@ -262,7 +262,7 @@ const EditView = ({
262
262
  paddingTop={6}
263
263
  shadow="tableShadow"
264
264
  >
265
- <Typography variant="sigma" textColor="neutral600">
265
+ <Typography variant="sigma" textColor="neutral600" id="relations-title">
266
266
  {formatMessage(
267
267
  {
268
268
  id: getTrad('containers.Edit.relations'),
@@ -283,10 +283,7 @@ const EditView = ({
283
283
  {...fieldSchema}
284
284
  {...metadatas}
285
285
  key={name}
286
- description={{
287
- id: metadatas.description,
288
- defaultMessage: metadatas.description,
289
- }}
286
+ description={metadatas.description}
290
287
  intlLabel={{
291
288
  id: metadatas.label,
292
289
  defaultMessage: metadatas.label,
@@ -155,15 +155,6 @@ const Register = ({ fieldsToDisable, noSignin, onSubmit, schema }) => {
155
155
  <GridItem col={6}>
156
156
  <TextInput
157
157
  name="lastname"
158
- error={
159
- errors.lastname
160
- ? formatMessage({
161
- id: errors.lastname,
162
- defaultMessage: 'This value is required.',
163
- })
164
- : undefined
165
- }
166
- required
167
158
  value={values.lastname}
168
159
  onChange={handleChange}
169
160
  label={formatMessage({
@@ -59,7 +59,7 @@ const forms = {
59
59
  fieldsToOmit: ['userInfo.confirmPassword', 'userInfo.news', 'userInfo.email'],
60
60
  schema: yup.object().shape({
61
61
  firstname: yup.string().required(translatedErrors.required),
62
- lastname: yup.string().required(translatedErrors.required),
62
+ lastname: yup.string(),
63
63
  password: yup
64
64
  .string()
65
65
  .min(8, translatedErrors.minLength)
@@ -83,7 +83,7 @@ const forms = {
83
83
  fieldsToOmit: ['confirmPassword', 'news'],
84
84
  schema: yup.object().shape({
85
85
  firstname: yup.string().required(translatedErrors.required),
86
- lastname: yup.string().required(translatedErrors.required),
86
+ lastname: yup.string(),
87
87
  password: yup
88
88
  .string()
89
89
  .min(8, translatedErrors.minLength)
@@ -33,6 +33,7 @@ import Check from '@strapi/icons/Check';
33
33
  import useLocalesProvider from '../../components/LocalesProvider/useLocalesProvider';
34
34
  import { fetchUser, putUser } from './utils/api';
35
35
  import schema from './utils/schema';
36
+ import { getFullName } from '../../utils';
36
37
 
37
38
  const PasswordInput = styled(TextInput)`
38
39
  ::-ms-reveal {
@@ -87,7 +88,7 @@ const ProfilePage = () => {
87
88
  await queryClient.invalidateQueries('user');
88
89
 
89
90
  auth.setUserInfo(data);
90
- const userDisplayName = data.username || `${data.firstname} ${data.lastname}`;
91
+ const userDisplayName = data.username || getFullName(data.firstname, data.lastname);
91
92
  setUserDisplayName(userDisplayName);
92
93
  changeLocale(data.preferedLanguage);
93
94
 
@@ -172,7 +173,7 @@ const ProfilePage = () => {
172
173
  return (
173
174
  <Form>
174
175
  <HeaderLayout
175
- title={data.username || `${data.firstname} ${data.lastname}`}
176
+ title={data.username || getFullName(data.firstname, data.lastname)}
176
177
  primaryAction={
177
178
  <Button startIcon={<Check />} loading={isSubmitting} type="submit">
178
179
  {formatMessage({ id: 'form.button.save', defaultMessage: 'Save' })}
@@ -1,6 +1,6 @@
1
- import React from 'react';
1
+ import React, { useRef } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
- import { ContentBox, useNotification } from '@strapi/helper-plugin';
3
+ import { ContentBox, useNotification, useTracking } from '@strapi/helper-plugin';
4
4
  import { IconButton } from '@strapi/design-system/IconButton';
5
5
  import Duplicate from '@strapi/icons/Duplicate';
6
6
  import PropTypes from 'prop-types';
@@ -10,6 +10,8 @@ import Key from '@strapi/icons/Key';
10
10
  const HeaderContentBox = ({ apiToken }) => {
11
11
  const { formatMessage } = useIntl();
12
12
  const toggleNotification = useNotification();
13
+ const { trackUsage } = useTracking();
14
+ const trackUsageRef = useRef(trackUsage);
13
15
 
14
16
  return (
15
17
  <ContentBox
@@ -18,6 +20,7 @@ const HeaderContentBox = ({ apiToken }) => {
18
20
  <span style={{ alignSelf: 'start' }}>
19
21
  <CopyToClipboard
20
22
  onCopy={() => {
23
+ trackUsageRef('didCopyTokenKey');
21
24
  toggleNotification({
22
25
  type: 'success',
23
26
  message: { id: 'Settings.apiTokens.notification.copied' },
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React, { useEffect, useRef } from 'react';
2
2
  import { useIntl } from 'react-intl';
3
3
  import {
4
4
  SettingsPageTitle,
@@ -6,6 +6,7 @@ import {
6
6
  Form,
7
7
  useOverlayBlocker,
8
8
  useNotification,
9
+ useTracking,
9
10
  } from '@strapi/helper-plugin';
10
11
  import { HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
11
12
  import { Main } from '@strapi/design-system/Main';
@@ -37,6 +38,8 @@ const ApiTokenCreateView = () => {
37
38
  const { lockApp, unlockApp } = useOverlayBlocker();
38
39
  const toggleNotification = useNotification();
39
40
  const history = useHistory();
41
+ const { trackUsage } = useTracking();
42
+ const trackUsageRef = useRef(trackUsage);
40
43
 
41
44
  const {
42
45
  params: { id },
@@ -44,6 +47,10 @@ const ApiTokenCreateView = () => {
44
47
 
45
48
  const isCreating = id === 'create';
46
49
 
50
+ useEffect(() => {
51
+ trackUsageRef.current(isCreating ? 'didAddTokenFromList' : 'didEditTokenFromList');
52
+ }, [isCreating]);
53
+
47
54
  if (history.location.state?.apiToken.accessKey) {
48
55
  apiToken = history.location.state.apiToken;
49
56
  }
@@ -73,6 +80,7 @@ const ApiTokenCreateView = () => {
73
80
  }
74
81
 
75
82
  const handleSubmit = async (body, actions) => {
83
+ trackUsageRef.current(isCreating ? 'willCreateToken' : 'willEditToken');
76
84
  lockApp();
77
85
 
78
86
  try {
@@ -89,6 +97,10 @@ const ApiTokenCreateView = () => {
89
97
  message: formatMessage({ id: 'notification.success.saved', defaultMessage: 'Saved' }),
90
98
  });
91
99
 
100
+ trackUsageRef.current(isCreating ? 'didCreateToken' : 'didEditToken', {
101
+ type: apiToken.type,
102
+ });
103
+
92
104
  if (isCreating) {
93
105
  history.replace(`/settings/api-tokens/${response.id}`, { apiToken: response });
94
106
  }
@@ -2,17 +2,21 @@ import React from 'react';
2
2
  import Trash from '@strapi/icons/Trash';
3
3
  import { IconButton } from '@strapi/design-system/IconButton';
4
4
  import { Box } from '@strapi/design-system/Box';
5
- import { stopPropagation } from '@strapi/helper-plugin';
5
+ import { stopPropagation, useTracking } from '@strapi/helper-plugin';
6
6
  import { useIntl } from 'react-intl';
7
7
  import PropTypes from 'prop-types';
8
8
 
9
9
  const DeleteButton = ({ tokenName, onClickDelete }) => {
10
10
  const { formatMessage } = useIntl();
11
+ const { trackUsage } = useTracking();
11
12
 
12
13
  return (
13
14
  <Box paddingLeft={1} {...stopPropagation}>
14
15
  <IconButton
15
- onClick={onClickDelete}
16
+ onClick={() => {
17
+ trackUsage('willDeleteToken');
18
+ onClickDelete();
19
+ }}
16
20
  label={formatMessage(
17
21
  {
18
22
  id: 'app.component.table.delete',
@@ -2,7 +2,13 @@ import React from 'react';
2
2
  import { Typography } from '@strapi/design-system/Typography';
3
3
  import { Tbody, Tr, Td } from '@strapi/design-system/Table';
4
4
  import { Flex } from '@strapi/design-system/Flex';
5
- import { RelativeTime, useQueryParams, onRowClick, pxToRem } from '@strapi/helper-plugin';
5
+ import {
6
+ RelativeTime,
7
+ useQueryParams,
8
+ onRowClick,
9
+ pxToRem,
10
+ useTracking,
11
+ } from '@strapi/helper-plugin';
6
12
  import { useIntl } from 'react-intl';
7
13
  import PropTypes from 'prop-types';
8
14
  import { useHistory } from 'react-router-dom';
@@ -17,6 +23,7 @@ const TableRows = ({ canDelete, canUpdate, onClickDelete, withBulkActions, rows
17
23
  push,
18
24
  location: { pathname },
19
25
  } = useHistory();
26
+ const { trackUsage } = useTracking();
20
27
 
21
28
  const apiTokens = rows.sort((a, b) => {
22
29
  const comparaison = a.name.localeCompare(b.name);
@@ -31,7 +38,10 @@ const TableRows = ({ canDelete, canUpdate, onClickDelete, withBulkActions, rows
31
38
  <Tr
32
39
  key={apiToken.id}
33
40
  {...onRowClick({
34
- fn: () => push(`${pathname}/${apiToken.id}`),
41
+ fn: () => {
42
+ trackUsage('willEditTokenFromList');
43
+ push(`${pathname}/${apiToken.id}`);
44
+ },
35
45
  condition: canUpdate,
36
46
  })}
37
47
  >
@@ -8,6 +8,7 @@ import {
8
8
  useRBAC,
9
9
  NoContent,
10
10
  DynamicTable,
11
+ useTracking,
11
12
  } from '@strapi/helper-plugin';
12
13
  import { HeaderLayout, ContentLayout } from '@strapi/design-system/Layout';
13
14
  import { Main } from '@strapi/design-system/Main';
@@ -31,6 +32,7 @@ const ApiTokenListView = () => {
31
32
  allowedActions: { canCreate, canDelete, canUpdate, canRead },
32
33
  } = useRBAC(adminPermissions.settings['api-tokens']);
33
34
  const { push } = useHistory();
35
+ const { trackUsage } = useTracking();
34
36
 
35
37
  useEffect(() => {
36
38
  push({ search: qs.stringify({ sort: 'name:ASC' }, { encode: false }) });
@@ -39,10 +41,13 @@ const ApiTokenListView = () => {
39
41
  const { data: apiTokens, status, isFetching } = useQuery(
40
42
  ['api-tokens'],
41
43
  async () => {
44
+ trackUsage('willAccessTokenList');
42
45
  const {
43
46
  data: { data },
44
47
  } = await axiosInstance.get(`/admin/api-tokens`);
45
48
 
49
+ trackUsage('didAccessTokenList', { number: data.length });
50
+
46
51
  return data;
47
52
  },
48
53
  {
@@ -67,6 +72,7 @@ const ApiTokenListView = () => {
67
72
  {
68
73
  onSuccess: async () => {
69
74
  await queryClient.invalidateQueries(['api-tokens']);
75
+ trackUsage('didDeleteToken');
70
76
  },
71
77
  onError: err => {
72
78
  if (err?.response?.data?.data) {
@@ -100,6 +106,7 @@ const ApiTokenListView = () => {
100
106
  data-testid="create-api-token-button"
101
107
  startIcon={<Plus />}
102
108
  size="L"
109
+ onClick={() => trackUsage('willAddTokenFromList')}
103
110
  to="/settings/api-tokens/create"
104
111
  >
105
112
  {formatMessage({
@@ -20,7 +20,7 @@ import { useIntl } from 'react-intl';
20
20
  import { useHistory } from 'react-router-dom';
21
21
  import RoleRow from './components/RoleRow';
22
22
  import EmptyRole from './components/EmptyRole';
23
- import UpgradePlanModal from '../../../../../components/UpgradePlanModal ';
23
+ import UpgradePlanModal from '../../../../../components/UpgradePlanModal';
24
24
  import { useRolesList } from '../../../../../hooks';
25
25
 
26
26
  const useSortedRoles = () => {
@@ -29,7 +29,7 @@ import { Stack } from '@strapi/design-system/Stack';
29
29
  import ArrowLeft from '@strapi/icons/ArrowLeft';
30
30
  import Check from '@strapi/icons/Check';
31
31
  import MagicLink from 'ee_else_ce/pages/SettingsPage/pages/Users/components/MagicLink';
32
- import { formatAPIErrors } from '../../../../../utils';
32
+ import { formatAPIErrors, getFullName } from '../../../../../utils';
33
33
  import { fetchUser, putUser } from './utils/api';
34
34
  import layout from './utils/layout';
35
35
  import { editValidation } from '../utils/validations/users';
@@ -89,7 +89,7 @@ const EditPage = ({ canUpdate }) => {
89
89
  if (id.toString() === userInfos.id.toString()) {
90
90
  auth.setUserInfo(data);
91
91
 
92
- const userDisplayName = get(body, 'username') || `${body.firstname} ${body.lastname}`;
92
+ const userDisplayName = get(body, 'username') || getFullName(body.firstname, body.lastname);
93
93
 
94
94
  setUserDisplayName(userDisplayName);
95
95
  }
@@ -131,7 +131,7 @@ const EditPage = ({ canUpdate }) => {
131
131
  }, {});
132
132
 
133
133
  const headerLabelName =
134
- initialData.username || `${initialData.firstname} ${initialData.lastname}`;
134
+ initialData.username || getFullName(initialData.firstname, initialData.lastname);
135
135
 
136
136
  const title = formatMessage(headerLabel, { name: headerLabelName });
137
137