@strapi/admin 4.12.4 → 4.13.0-beta.0

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 (74) hide show
  1. package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/getTableColumns.js +2 -0
  2. package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +8 -1
  3. package/admin/src/content-manager/components/Filter/CustomInputs/AdminUsersFilter.js +42 -0
  4. package/admin/src/content-manager/components/{AttributeFilter/Filters.js → Filter/Filter.js} +5 -7
  5. package/admin/src/content-manager/components/Filter/index.js +1 -0
  6. package/admin/src/content-manager/hooks/useAllowedAttributes.js +47 -0
  7. package/admin/src/content-manager/hooks/useSyncRbac/index.js +10 -2
  8. package/admin/src/content-manager/pages/EditView/Information/index.js +9 -8
  9. package/admin/src/content-manager/pages/EditViewLayoutManager/index.js +2 -2
  10. package/admin/src/content-manager/pages/ListSettingsView/components/Settings.js +40 -7
  11. package/admin/src/content-manager/pages/ListSettingsView/index.js +6 -2
  12. package/admin/src/content-manager/pages/ListView/components/FieldPicker/index.js +67 -69
  13. package/admin/src/content-manager/pages/ListView/components/ViewSettingsMenu/index.js +74 -0
  14. package/admin/src/content-manager/pages/ListView/index.js +254 -68
  15. package/admin/src/content-manager/pages/ListViewLayoutManager/index.js +2 -2
  16. package/admin/src/content-manager/utils/getDisplayName.js +33 -0
  17. package/admin/src/content-manager/utils/index.js +1 -0
  18. package/admin/src/translations/en.json +3 -1
  19. package/admin/src/translations/zh-Hans.json +918 -902
  20. package/build/1227.32fe57ce.chunk.js +1 -0
  21. package/build/4174.fa8f9954.chunk.js +1 -0
  22. package/build/4546.ff09eeda.chunk.js +1 -0
  23. package/build/4724.baf7c5b1.chunk.js +6 -0
  24. package/build/6158.c3c13c20.chunk.js +1 -0
  25. package/build/78.dcc6df5c.chunk.js +1 -0
  26. package/build/{9806.3392505e.chunk.js → 9806.5d5a0e8d.chunk.js} +16 -16
  27. package/build/{Admin-authenticatedApp.f5ece8ff.chunk.js → Admin-authenticatedApp.53a24d28.chunk.js} +2 -2
  28. package/build/audit-logs-settings-page.0f73ccf8.chunk.js +1 -0
  29. package/build/content-manager.7f96a2f1.chunk.js +1097 -0
  30. package/build/{content-type-builder.40534de5.chunk.js → content-type-builder.cd999f6e.chunk.js} +2 -2
  31. package/build/{en-json.08c05fcf.chunk.js → en-json.4f06fe03.chunk.js} +1 -1
  32. package/build/i18n-translation-ru-json.a3dbc125.chunk.js +1 -0
  33. package/build/index.html +1 -1
  34. package/build/main.40b94779.js +2859 -0
  35. package/build/{runtime~main.bb4efc54.js → runtime~main.b16af570.js} +2 -2
  36. package/build/users-permissions-translation-zh-Hans-json.8d82c809.chunk.js +1 -0
  37. package/build/{users-roles-settings-page.3f9f063e.chunk.js → users-roles-settings-page.9d9a1eff.chunk.js} +1 -1
  38. package/build/zh-Hans-json.97efd015.chunk.js +1 -0
  39. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/AssigneeFilter.js +42 -0
  40. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/StageFilter.js +70 -0
  41. package/ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants.js +71 -0
  42. package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +9 -217
  43. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/AssigneeSelect.js +149 -0
  44. package/ee/admin/content-manager/pages/EditView/InformationBox/components/AssigneeSelect/index.js +1 -0
  45. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/StageSelect.js +241 -0
  46. package/ee/admin/content-manager/pages/EditView/InformationBox/components/StageSelect/index.js +1 -0
  47. package/ee/admin/content-manager/pages/EditView/InformationBox/constants.js +2 -0
  48. package/ee/admin/content-manager/pages/ListSettingsView/constants.js +7 -0
  49. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/ReviewWorkflowsAssigneeEE.js +51 -0
  50. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/constants.js +44 -17
  51. package/ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn/index.js +1 -0
  52. package/ee/server/constants/workflows.js +1 -0
  53. package/ee/server/controllers/index.js +1 -0
  54. package/ee/server/controllers/workflows/assignees/index.js +44 -0
  55. package/ee/server/routes/review-workflows.js +17 -0
  56. package/ee/server/services/index.js +1 -0
  57. package/ee/server/services/review-workflows/assignees.js +54 -0
  58. package/ee/server/services/review-workflows/metrics/index.js +5 -0
  59. package/ee/server/services/review-workflows/review-workflows.js +20 -11
  60. package/ee/server/validation/review-workflows.js +8 -0
  61. package/package.json +10 -10
  62. package/server/services/permission/permissions-manager/sanitize.js +12 -0
  63. package/admin/src/content-manager/components/AttributeFilter/hooks/useAllowedAttributes.js +0 -42
  64. package/admin/src/content-manager/components/AttributeFilter/index.js +0 -40
  65. package/build/3984.dda474f7.chunk.js +0 -1
  66. package/build/4546.cfafae68.chunk.js +0 -1
  67. package/build/5483.6dd2e776.chunk.js +0 -6
  68. package/build/6158.c974fd83.chunk.js +0 -1
  69. package/build/audit-logs-settings-page.4b422831.chunk.js +0 -1
  70. package/build/content-manager.2af15f57.chunk.js +0 -1099
  71. package/build/i18n-translation-ru-json.401bc498.chunk.js +0 -1
  72. package/build/main.f13fc96c.js +0 -2856
  73. package/build/users-permissions-translation-zh-Hans-json.6ab714ee.chunk.js +0 -1
  74. package/build/zh-Hans-json.937b395b.chunk.js +0 -1
@@ -1,9 +1,7 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  import {
4
- IconButton,
5
4
  Main,
6
- Box,
7
5
  ActionLayout,
8
6
  Button,
9
7
  ContentLayout,
@@ -17,8 +15,8 @@ import {
17
15
  lightTheme,
18
16
  } from '@strapi/design-system';
19
17
  import {
18
+ findMatchingPermissions,
20
19
  NoPermissions,
21
- CheckPermissions,
22
20
  SearchURLQuery,
23
21
  useFetchClient,
24
22
  useFocusWhenNavigate,
@@ -28,12 +26,13 @@ import {
28
26
  useTracking,
29
27
  Link,
30
28
  useAPIErrorHandler,
29
+ useCollator,
31
30
  useStrapiApp,
32
31
  Table,
33
32
  PaginationURLQuery,
34
33
  PageSizeURLQuery,
35
34
  } from '@strapi/helper-plugin';
36
- import { ArrowLeft, Cog, Plus } from '@strapi/icons';
35
+ import { ArrowLeft, Plus } from '@strapi/icons';
37
36
  import axios, { AxiosError } from 'axios';
38
37
  import isEqual from 'lodash/isEqual';
39
38
  import PropTypes from 'prop-types';
@@ -43,33 +42,29 @@ import { useMutation } from 'react-query';
43
42
  import { connect, useSelector } from 'react-redux';
44
43
  import { useHistory, useLocation, Link as ReactRouterLink } from 'react-router-dom';
45
44
  import { bindActionCreators, compose } from 'redux';
46
- import styled from 'styled-components';
47
45
 
48
46
  import { INJECT_COLUMN_IN_TABLE } from '../../../exposedHooks';
47
+ import { useAdminUsers } from '../../../hooks/useAdminUsers';
49
48
  import { useEnterprise } from '../../../hooks/useEnterprise';
50
- import { selectAdminPermissions } from '../../../pages/App/selectors';
51
49
  import { InjectionZone } from '../../../shared/components';
52
- import AttributeFilter from '../../components/AttributeFilter';
53
- import { getTrad } from '../../utils';
50
+ import { Filter } from '../../components/Filter';
51
+ import { AdminUsersFilter } from '../../components/Filter/CustomInputs/AdminUsersFilter';
52
+ import { useAllowedAttributes } from '../../hooks/useAllowedAttributes';
53
+ import { getTrad, getDisplayName } from '../../utils';
54
54
 
55
55
  import { getData, getDataSucceeded, onChangeListHeaders, onResetListHeaders } from './actions';
56
56
  import { Body } from './components/Body';
57
57
  import BulkActionButtons from './components/BulkActionButtons';
58
58
  import CellContent from './components/CellContent';
59
- import { FieldPicker } from './components/FieldPicker';
59
+ import { ViewSettingsMenu } from './components/ViewSettingsMenu';
60
60
  import makeSelectListView, { selectDisplayedHeaders } from './selectors';
61
61
  import { buildValidGetParams } from './utils';
62
62
 
63
- const ConfigureLayoutBox = styled(Box)`
64
- svg {
65
- path {
66
- fill: ${({ theme }) => theme.colors.neutral900};
67
- }
68
- }
69
- `;
70
-
71
63
  const REVIEW_WORKFLOW_COLUMNS_CE = null;
72
64
  const REVIEW_WORKFLOW_COLUMNS_CELL_CE = () => null;
65
+ const REVIEW_WORKFLOW_FILTER_CE = [];
66
+ const CREATOR_ATTRIBUTES = ['createdBy', 'updatedBy'];
67
+ const USER_FILTER_ATTRIBUTES = [...CREATOR_ATTRIBUTES, 'strapi_assignee'];
73
68
 
74
69
  function ListView({
75
70
  canCreate,
@@ -95,23 +90,106 @@ function ListView({
95
90
  const [isConfirmDeleteRowOpen, setIsConfirmDeleteRowOpen] = React.useState(false);
96
91
  const toggleNotification = useNotification();
97
92
  const { trackUsage } = useTracking();
98
- const { refetchPermissions } = useRBACProvider();
93
+ const { allPermissions, refetchPermissions } = useRBACProvider();
99
94
  const trackUsageRef = React.useRef(trackUsage);
100
95
  const fetchPermissionsRef = React.useRef(refetchPermissions);
101
96
  const { notifyStatus } = useNotifyAT();
102
97
  const { formatAPIError } = useAPIErrorHandler(getTrad);
103
- const permissions = useSelector(selectAdminPermissions);
98
+ const allowedAttributes = useAllowedAttributes(contentType, slug);
99
+ const [{ query }] = useQueryParams();
100
+ const { pathname } = useLocation();
101
+ const { push } = useHistory();
102
+ const { formatMessage, locale } = useIntl();
103
+ const fetchClient = useFetchClient();
104
+ const formatter = useCollator(locale, {
105
+ sensitivity: 'base',
106
+ });
107
+
108
+ const selectedUserIds =
109
+ query?.filters?.$and?.reduce((acc, filter) => {
110
+ const [key, value] = Object.entries(filter)[0];
111
+ const id = value.id?.$eq || value.id?.$ne;
112
+
113
+ // TODO: strapi_assignee should not be in here and rather defined
114
+ // in the ee directory.
115
+ if (USER_FILTER_ATTRIBUTES.includes(key) && !acc.includes(id)) {
116
+ acc.push(id);
117
+ }
118
+
119
+ return acc;
120
+ }, []) ?? [];
121
+
122
+ const { users, isLoading: isLoadingAdminUsers } = useAdminUsers(
123
+ { filter: { id: { in: selectedUserIds } } },
124
+ {
125
+ // fetch the list of admin users only if the filter contains users and the
126
+ // current user has permissions to display users
127
+ enabled:
128
+ selectedUserIds.length > 0 &&
129
+ findMatchingPermissions(allPermissions, [
130
+ {
131
+ action: 'admin::users.read',
132
+ subject: null,
133
+ },
134
+ ]).length > 0,
135
+ }
136
+ );
104
137
 
105
138
  useFocusWhenNavigate();
106
139
 
107
- const [{ query }] = useQueryParams();
108
140
  const params = React.useMemo(() => buildValidGetParams(query), [query]);
109
141
  const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false });
110
142
 
111
- const { pathname } = useLocation();
112
- const { push } = useHistory();
113
- const { formatMessage } = useIntl();
114
- const fetchClient = useFetchClient();
143
+ const displayedAttributeFilters = allowedAttributes.map((name) => {
144
+ const attribute = contentType.attributes[name];
145
+ const { type, enum: options } = attribute;
146
+
147
+ const trackedEvent = {
148
+ name: 'didFilterEntries',
149
+ properties: { useRelation: type === 'relation' },
150
+ };
151
+
152
+ const { mainField, label } = metadatas[name].list;
153
+
154
+ const filter = {
155
+ name,
156
+ metadatas: { label: formatMessage({ id: label, defaultMessage: label }) },
157
+ fieldSchema: { type, options, mainField },
158
+ trackedEvent,
159
+ };
160
+
161
+ if (attribute.type === 'relation' && attribute.target === 'admin::user') {
162
+ filter.metadatas = {
163
+ ...filter.metadatas,
164
+ customOperators: [
165
+ {
166
+ intlLabel: {
167
+ id: 'components.FilterOptions.FILTER_TYPES.$eq',
168
+ defaultMessage: 'is',
169
+ },
170
+ value: '$eq',
171
+ },
172
+ {
173
+ intlLabel: {
174
+ id: 'components.FilterOptions.FILTER_TYPES.$ne',
175
+ defaultMessage: 'is not',
176
+ },
177
+ value: '$ne',
178
+ },
179
+ ],
180
+ customInput: AdminUsersFilter,
181
+ options: users.map((user) => ({
182
+ label: getDisplayName(user, formatMessage),
183
+ customValue: user.id.toString(),
184
+ })),
185
+ };
186
+ filter.fieldSchema.mainField = {
187
+ name: 'id',
188
+ };
189
+ }
190
+
191
+ return filter;
192
+ });
115
193
 
116
194
  const hasDraftAndPublish = options?.draftAndPublish ?? false;
117
195
  const hasReviewWorkflows = options?.reviewWorkflows ?? false;
@@ -128,16 +206,82 @@ function ListView({
128
206
  enabled: !!options?.reviewWorkflows,
129
207
  }
130
208
  );
131
- const ReviewWorkflowsStage = useEnterprise(
209
+ const ReviewWorkflowsColumns = useEnterprise(
132
210
  REVIEW_WORKFLOW_COLUMNS_CELL_CE,
133
- async () =>
134
- (await import('../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn'))
135
- .ReviewWorkflowsStageEE,
211
+ async () => {
212
+ const { ReviewWorkflowsStageEE, ReviewWorkflowsAssigneeEE } = await import(
213
+ '../../../../../ee/admin/content-manager/pages/ListView/ReviewWorkflowsColumn'
214
+ );
215
+
216
+ return { ReviewWorkflowsStageEE, ReviewWorkflowsAssigneeEE };
217
+ },
136
218
  {
137
219
  enabled: hasReviewWorkflows,
138
220
  }
139
221
  );
140
222
 
223
+ const reviewWorkflowFilter = useEnterprise(
224
+ REVIEW_WORKFLOW_FILTER_CE,
225
+ async () =>
226
+ (
227
+ await import(
228
+ '../../../../../ee/admin/content-manager/components/Filter/CustomInputs/ReviewWorkflows/constants'
229
+ )
230
+ ).REVIEW_WORKFLOW_FILTERS,
231
+ {
232
+ combine(ceFilters, eeFilters) {
233
+ return [
234
+ ...ceFilters,
235
+ ...eeFilters
236
+ .filter((eeFilter) => {
237
+ // do not display the filter at all, if the current user does
238
+ // not have permissions to read admin users
239
+ if (eeFilter.name === 'strapi_assignee') {
240
+ return (
241
+ findMatchingPermissions(allPermissions, [
242
+ {
243
+ action: 'admin::users.read',
244
+ subject: null,
245
+ },
246
+ ]).length > 0
247
+ );
248
+ }
249
+
250
+ return true;
251
+ })
252
+ .map((eeFilter) => ({
253
+ ...eeFilter,
254
+ metadatas: {
255
+ ...eeFilter.metadatas,
256
+ // the stage filter needs the current content-type uid to fetch
257
+ // the list of stages that can be assigned to this content-type
258
+ ...(eeFilter.name === 'strapi_stage' ? { uid: contentType.uid } : {}),
259
+
260
+ // translate the filter label
261
+ label: formatMessage(eeFilter.metadatas.label),
262
+
263
+ // `options` allows the filter-tag to render the displayname
264
+ // of a user over a plain id
265
+ options:
266
+ eeFilter.name === 'strapi_assignee' &&
267
+ users.map((user) => ({
268
+ label: getDisplayName(user, formatMessage),
269
+ customValue: user.id.toString(),
270
+ })),
271
+ },
272
+ })),
273
+ ];
274
+ },
275
+
276
+ defaultValue: [],
277
+
278
+ // we have to wait for admin users to be fully loaded, because otherwise
279
+ // combine is called to early and does not contain the latest state of
280
+ // the users array
281
+ enabled: hasReviewWorkflows && !isLoadingAdminUsers,
282
+ }
283
+ );
284
+
141
285
  const { post, del } = fetchClient;
142
286
 
143
287
  const bulkUnpublishMutation = useMutation(
@@ -180,6 +324,22 @@ function ListView({
180
324
  data: { results, pagination: paginationResult },
181
325
  } = await fetchClient.get(endPoint, options);
182
326
 
327
+ // If user enters a page number that doesn't exist, redirect him to the last page
328
+ if (paginationResult.page > paginationResult.pageCount && paginationResult.pageCount > 0) {
329
+ const query = {
330
+ ...params,
331
+ page: paginationResult.pageCount,
332
+ };
333
+
334
+ push({
335
+ pathname,
336
+ state: { from: pathname },
337
+ search: stringify(query),
338
+ });
339
+
340
+ return;
341
+ }
342
+
183
343
  notifyStatus(
184
344
  formatMessage(
185
345
  {
@@ -219,7 +379,17 @@ function ListView({
219
379
  });
220
380
  }
221
381
  },
222
- [formatMessage, getData, getDataSucceeded, notifyStatus, push, toggleNotification, fetchClient]
382
+ [
383
+ formatMessage,
384
+ getData,
385
+ getDataSucceeded,
386
+ notifyStatus,
387
+ push,
388
+ toggleNotification,
389
+ fetchClient,
390
+ params,
391
+ pathname,
392
+ ]
223
393
  );
224
394
 
225
395
  const handleConfirmDeleteAllData = React.useCallback(
@@ -355,13 +525,15 @@ function ListView({
355
525
 
356
526
  if (reviewWorkflowColumns) {
357
527
  // Make sure the column header label is translated
358
- if (typeof reviewWorkflowColumns.metadatas.label !== 'string') {
359
- reviewWorkflowColumns.metadatas.label = formatMessage(
360
- reviewWorkflowColumns.metadatas.label
361
- );
362
- }
528
+ reviewWorkflowColumns.map((column) => {
529
+ if (typeof column.metadatas.label !== 'string') {
530
+ column.metadatas.label = formatMessage(column.metadatas.label);
531
+ }
532
+
533
+ return column;
534
+ });
363
535
 
364
- formattedHeaders.push(reviewWorkflowColumns);
536
+ formattedHeaders.push(...reviewWorkflowColumns);
365
537
  }
366
538
 
367
539
  return formattedHeaders;
@@ -455,7 +627,7 @@ function ListView({
455
627
  };
456
628
 
457
629
  // Block rendering until the review stage component is fully loaded in EE
458
- if (!ReviewWorkflowsStage) {
630
+ if (!ReviewWorkflowsColumns) {
459
631
  return null;
460
632
  }
461
633
 
@@ -482,25 +654,7 @@ function ListView({
482
654
  endActions={
483
655
  <>
484
656
  <InjectionZone area="contentManager.listView.actions" />
485
- <FieldPicker layout={layout} />
486
- <CheckPermissions
487
- permissions={permissions.contentManager.collectionTypesConfigurations}
488
- >
489
- <ConfigureLayoutBox paddingTop={1} paddingBottom={1}>
490
- <IconButton
491
- onClick={() => {
492
- trackUsage('willEditListLayout');
493
- }}
494
- forwardedAs={ReactRouterLink}
495
- to={{ pathname: `${slug}/configurations/list`, search: pluginsQueryParams }}
496
- icon={<Cog />}
497
- label={formatMessage({
498
- id: 'app.links.configure-view',
499
- defaultMessage: 'Configure the view',
500
- })}
501
- />
502
- </ConfigureLayoutBox>
503
- </CheckPermissions>
657
+ <ViewSettingsMenu slug={slug} layout={layout} />
504
658
  </>
505
659
  }
506
660
  startActions={
@@ -518,8 +672,12 @@ function ListView({
518
672
  trackedEvent="didSearch"
519
673
  />
520
674
  )}
521
- {isFilterable && (
522
- <AttributeFilter contentType={contentType} slug={slug} metadatas={metadatas} />
675
+ {isFilterable && !isLoadingAdminUsers && (
676
+ <Filter
677
+ displayedFilters={[...displayedAttributeFilters, ...reviewWorkflowFilter].sort(
678
+ (a, b) => formatter.compare(a.metadatas.label, b.metadatas.label)
679
+ )}
680
+ />
523
681
  )}
524
682
  </>
525
683
  }
@@ -605,19 +763,47 @@ function ListView({
605
763
  );
606
764
  }
607
765
 
608
- if (hasReviewWorkflows && name === 'strapi_stage') {
766
+ if (hasReviewWorkflows) {
767
+ if (name === 'strapi_stage') {
768
+ return (
769
+ <Td key={key}>
770
+ {rowData.strapi_stage ? (
771
+ <ReviewWorkflowsColumns.ReviewWorkflowsStageEE
772
+ color={
773
+ rowData.strapi_stage.color ?? lightTheme.colors.primary600
774
+ }
775
+ name={rowData.strapi_stage.name}
776
+ />
777
+ ) : (
778
+ <Typography textColor="neutral800">-</Typography>
779
+ )}
780
+ </Td>
781
+ );
782
+ }
783
+ if (name === 'strapi_assignee') {
784
+ return (
785
+ <Td key={key}>
786
+ {rowData.strapi_assignee ? (
787
+ <ReviewWorkflowsColumns.ReviewWorkflowsAssigneeEE
788
+ firstname={rowData.strapi_assignee.firstname}
789
+ lastname={rowData?.strapi_assignee?.lastname}
790
+ displayname={rowData?.strapi_assignee?.username}
791
+ />
792
+ ) : (
793
+ <Typography textColor="neutral800">-</Typography>
794
+ )}
795
+ </Td>
796
+ );
797
+ }
798
+ }
799
+
800
+ if (['createdBy', 'updatedBy'].includes(name.split('.')[0])) {
801
+ // Display the users full name
609
802
  return (
610
803
  <Td key={key}>
611
- {rowData.strapi_stage ? (
612
- <ReviewWorkflowsStage
613
- color={
614
- rowData.strapi_stage.color ?? lightTheme.colors.primary600
615
- }
616
- name={rowData.strapi_stage.name}
617
- />
618
- ) : (
619
- <Typography textColor="neutral800">-</Typography>
620
- )}
804
+ <Typography textColor="neutral800">
805
+ {getDisplayName(rowData[name.split('.')[0]], formatMessage)}
806
+ </Typography>
621
807
  </Td>
622
808
  );
623
809
  }
@@ -14,7 +14,7 @@ const ListViewLayout = ({ layout, ...props }) => {
14
14
  const dispatch = useDispatch();
15
15
  const { replace } = useHistory();
16
16
  const [{ query, rawQuery }] = useQueryParams();
17
- const permissions = useSyncRbac(query, props.slug, 'listView');
17
+ const { permissions, isValid: isValidPermissions } = useSyncRbac(query, props.slug, 'listView');
18
18
  const redirectionLink = useFindRedirectionLink(props.slug);
19
19
 
20
20
  useEffect(() => {
@@ -33,7 +33,7 @@ const ListViewLayout = ({ layout, ...props }) => {
33
33
  };
34
34
  }, [dispatch]);
35
35
 
36
- if (!permissions) {
36
+ if (!isValidPermissions) {
37
37
  return null;
38
38
  }
39
39
 
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Retrieves the display name of an admin panel user
3
+ * @typedef AdminUserNamesAttributes
4
+ * @property {string} firstname
5
+ * @property {string} lastname
6
+ * @property {string} username
7
+ * @property {string} email
8
+ *
9
+ * @type {(user: AdminUserNamesAttributes, formatMessage: import('react-intl').formatMessage) => string}
10
+ */
11
+ const getDisplayName = ({ firstname, lastname, username, email }, formatMessage) => {
12
+ if (username) {
13
+ return username;
14
+ }
15
+
16
+ // firstname is not required if the user is created with a username
17
+ if (firstname) {
18
+ return formatMessage(
19
+ {
20
+ id: 'global.fullname',
21
+ defaultMessage: '{firstname} {lastname}',
22
+ },
23
+ {
24
+ firstname,
25
+ lastname,
26
+ }
27
+ ).trim();
28
+ }
29
+
30
+ return email;
31
+ };
32
+
33
+ export { getDisplayName };
@@ -12,3 +12,4 @@ export { default as mergeMetasWithSchema } from './mergeMetasWithSchema';
12
12
  export { default as removeKeyInObject } from './removeKeyInObject';
13
13
  export { default as removePasswordFieldsFromData } from './removePasswordFieldsFromData';
14
14
  export { default as createYupSchema } from './schema';
15
+ export { getDisplayName } from './getDisplayName';
@@ -612,7 +612,7 @@
612
612
  "components.PageFooter.select": "Entries per page",
613
613
  "components.ProductionBlocker.description": "For safety purposes we have to disable this plugin in other environments.",
614
614
  "components.ProductionBlocker.header": "This plugin is only available in development.",
615
- "components.Search.placeholder": "Search...",
615
+ "components.ViewSettings.tooltip": "View settings",
616
616
  "components.TableHeader.sort": "Sort on {label}",
617
617
  "components.Wysiwyg.ToggleMode.markdown-mode": "Markdown mode",
618
618
  "components.Wysiwyg.ToggleMode.preview-mode": "Preview mode",
@@ -671,6 +671,7 @@
671
671
  "content-manager.components.FiltersPickWrapper.PluginHeader.description": "Set the conditions to apply to filter the entries",
672
672
  "content-manager.components.FiltersPickWrapper.PluginHeader.title.filter": "Filters",
673
673
  "content-manager.components.FiltersPickWrapper.hide": "Hide",
674
+ "content-manager.components.Filters.usersSelect.label": "Search and select a user to filter by",
674
675
  "content-manager.components.LeftMenu.Search.label": "Search for a content type",
675
676
  "content-manager.components.LeftMenu.collection-types": "Collection Types",
676
677
  "content-manager.components.LeftMenu.single-types": "Single Types",
@@ -914,6 +915,7 @@
914
915
  "global.settings": "Settings",
915
916
  "global.type": "Type",
916
917
  "global.users": "Users",
918
+ "global.fullname": "{firstname} {lastname}",
917
919
  "light": "Light",
918
920
  "notification.contentType.relations.conflict": "Content type has conflicting relations",
919
921
  "notification.default.title": "Information:",