Merge branch 'fix/dts-bypass-schema-check' into fix/dts-engine-test

This commit is contained in:
Christian 2023-05-09 14:38:30 +02:00 committed by GitHub
commit 1811a7ca36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 979 additions and 692 deletions

View File

@ -6,6 +6,7 @@ updates:
interval: weekly
day: sunday
time: '22:00'
open-pull-requests-limit: 10
versioning-strategy: increase
ignore:
# Only allow patch as minor babel versions need to be upgraded all together
@ -23,6 +24,7 @@ updates:
- 'source: dependencies'
- 'pr: chore'
- package-ecosystem: github-actions
open-pull-requests-limit: 10
directory: /
schedule:
interval: weekly

View File

@ -27,7 +27,7 @@
"better-sqlite3": "8.3.0",
"lodash": "4.17.21",
"mysql": "2.18.1",
"mysql2": "3.2.0",
"mysql2": "3.3.0",
"passport-google-oauth2": "0.2.0",
"pg": "8.8.0",
"react": "^17.0.2",

View File

@ -30,7 +30,7 @@ const moduleNameMapper = {
module.exports = {
rootDir: __dirname,
moduleNameMapper,
testPathIgnorePatterns: ['/node_modules/', '__tests__'],
testPathIgnorePatterns: ['node_modules/', '__tests__'],
globalSetup: '@strapi/admin-test-utils/global-setup',
setupFiles: ['@strapi/admin-test-utils/environment'],
setupFilesAfterEnv: ['@strapi/admin-test-utils/after-env'],
@ -60,7 +60,7 @@ module.exports = {
transformIgnorePatterns: [
'node_modules/(?!(react-dnd|dnd-core|react-dnd-html5-backend|@strapi/design-system|@strapi/icons|fractional-indexing)/)',
],
testMatch: ['/**/tests/**/?(*.)+(spec|test).[jt]s?(x)'],
testMatch: ['**/tests/**/?(*.)+(spec|test).[jt]s?(x)'],
testEnvironmentOptions: {
url: 'http://localhost:1337/admin',
},

View File

@ -4,7 +4,7 @@ module.exports = {
setupFilesAfterEnv: [__dirname + '/test/unit.setup.js'],
modulePathIgnorePatterns: ['.cache', 'dist'],
testPathIgnorePatterns: ['.testdata.js', '.test.utils.js'],
testMatch: ['/**/__tests__/**/*.[jt]s?(x)'],
testMatch: ['**/__tests__/**/*.[jt]s?(x)'],
// Use `jest-watch-typeahead` version 0.6.5. Newest version 1.0.0 does not support jest@26
// Reference: https://github.com/jest-community/jest-watch-typeahead/releases/tag/v1.0.0
watchPlugins: ['jest-watch-typeahead/filename', 'jest-watch-typeahead/testname'],

View File

@ -101,7 +101,7 @@
"jest-environment-jsdom": "29.0.3",
"jest-watch-typeahead": "2.2.2",
"lerna": "6.5.1",
"lint-staged": "13.0.3",
"lint-staged": "13.2.2",
"lodash": "4.17.21",
"nx": "15.8.3",
"plop": "2.7.6",

View File

@ -10,6 +10,7 @@ declare global {
isEnabled: (featureName?: string) => boolean;
};
projectType: string;
telemetryDisabled: boolean;
};
}
}

View File

@ -57,6 +57,7 @@ window.strapi = {
isEnabled: () => false,
},
projectType: 'Community',
telemetryDisabled: true,
};
/* -------------------------------------------------------------------------------------------------

View File

@ -17,7 +17,6 @@ jest.mock('@strapi/helper-plugin', () => ({
// eslint-disable-next-line
CheckPermissions: ({ children }) => <div>{children}</div>,
useNotification: jest.fn(),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const client = new QueryClient({

View File

@ -13,7 +13,6 @@ import ModelsContext from '../../../contexts/ModelsContext';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const client = new QueryClient({
@ -191,24 +190,26 @@ describe('ADMIN | CM | LV | Configure the view', () => {
screen.getByText("This value overrides the label displayed in the table's head")
).toBeInTheDocument();
});
it('should close edit modal onSubmit', async () => {
const history = createMemoryHistory();
const { queryByText } = render(makeApp(history));
await waitFor(() =>
expect(screen.getByText('Configure the view - Michka')).toBeInTheDocument()
);
fireEvent.click(screen.getByLabelText('Edit address'));
expect(
screen.getByText("This value overrides the label displayed in the table's head")
).toBeInTheDocument();
fireEvent.click(screen.getByText('Finish'));
expect(queryByText("This value overrides the label displayed in the table's head")).not.toBeInTheDocument();
expect(
queryByText("This value overrides the label displayed in the table's head")
).not.toBeInTheDocument();
});
it('should not show sortable toggle input if field not sortable', async () => {

View File

@ -44,6 +44,9 @@ const SettingsPage = lazy(() =>
);
// Simple hook easier for testing
/**
* TODO: remove this, it's bad.
*/
const useTrackUsage = () => {
const { trackUsage } = useTracking();
const dispatch = useDispatch();

View File

@ -15,9 +15,9 @@ jest.mock('react-redux', () => ({
}));
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
LoadingIndicatorPage: () => <div>Loading</div>,
useStrapiApp: jest.fn(() => ({ menu: [] })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
NotFound: () => <div>not found</div>,
CheckPagePermissions: ({ children }) => children,
useGuidedTour: jest.fn(() => ({
@ -34,7 +34,6 @@ jest.mock('@strapi/helper-plugin', () => ({
jest.mock('../../../hooks', () => ({
useMenu: jest.fn(() => ({ isLoading: true, generalSectionLinks: [], pluginsSectionLinks: [] })),
useTrackUsage: jest.fn(),
useReleaseNotification: jest.fn(),
useConfigurations: jest.fn(() => ({ showTutorials: false })),
}));

View File

@ -1,18 +1,18 @@
import { renderHook } from '@testing-library/react-hooks';
import { useTrackUsage } from '..';
import { useTrackUsage } from '../index';
const trackUsageMock = jest.fn();
jest.mock('@strapi/helper-plugin', () => ({
useTracking: jest.fn(() => ({ trackUsage: trackUsageMock })),
}));
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: jest.fn(() => jest.fn()),
useSelector: jest.fn(() => 'init'),
}));
jest.mock('@strapi/helper-plugin', () => ({
useTracking: jest.fn(() => ({ trackUsage: trackUsageMock })),
}));
describe('Admin | pages | Admin | useTrackUsage', () => {
it('should call the trackUsage method on mount with didAccessAuthenticatedAdministration', () => {
const { rerender } = renderHook(() => useTrackUsage());

View File

@ -149,7 +149,7 @@ const Register = ({ authType, fieldsToDisable, noSignin, onSubmit, schema }) =>
return (
<Form noValidate>
<Main>
<Flex direction="column" alignItems="stretch" gap={3}>
<Flex direction="column" alignItems="center" gap={3}>
<Logo />
<Typography as="h1" variant="alpha" textAlign="center">

View File

@ -11,7 +11,6 @@ import { useModels } from '../../../hooks';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useAppInfo: jest.fn(() => ({ communityEdition: true })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useGuidedTour: jest.fn(() => ({
isGuidedTourVisible: false,
guidedTourState: {

View File

@ -115,10 +115,14 @@ exports[`Marketplace page - layout renders the online layout 1`] = `
.c63 {
color: #666687;
width: 0.75rem;
height: 0.75rem;
}
.c65 {
color: #f29d41;
width: 0.75rem;
height: 0.75rem;
}
.c68 {
@ -1114,6 +1118,7 @@ exports[`Marketplace page - layout renders the online layout 1`] = `
}
.c70 {
width: 0.75rem;
-webkit-transform: rotate(90deg);
-ms-transform: rotate(90deg);
transform: rotate(90deg);

View File

@ -17,12 +17,11 @@ jest.mock('../../../hooks/useNavigatorOnLine', () => jest.fn(() => true));
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useNotification: jest.fn(() => {
return toggleNotification;
}),
pxToRem: jest.fn(),
CheckPagePermissions: ({ children }) => children,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useAppInfo: jest.fn(() => ({
autoReload: true,
dependencies: {

View File

@ -20,9 +20,7 @@ jest.mock('@strapi/helper-plugin', () => ({
useNotification: jest.fn(() => {
return toggleNotification;
}),
pxToRem: jest.fn(),
CheckPagePermissions: ({ children }) => children,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useAppInfo: jest.fn(() => ({
autoReload: true,
dependencies: {

View File

@ -22,9 +22,7 @@ jest.mock('@strapi/helper-plugin', () => ({
useNotification: jest.fn(() => {
return toggleNotification;
}),
pxToRem: jest.fn(),
CheckPagePermissions: ({ children }) => children,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useAppInfo: jest.fn(() => ({
autoReload: true,
dependencies: {

View File

@ -20,7 +20,6 @@ jest.mock('@strapi/helper-plugin', () => ({
useFocusWhenNavigate: jest.fn(),
useAppInfo: jest.fn(() => ({ setUserDisplayName: jest.fn() })),
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn, unlockApp: jest.fn() })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const client = new QueryClient({

View File

@ -6,11 +6,6 @@ import { createMemoryHistory } from 'history';
import { ThemeProvider, lightTheme } from '@strapi/design-system';
import SettingsNav from '../index';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const menu = [
{
id: 'global',

View File

@ -6,11 +6,6 @@ import { NotificationsProvider } from '@strapi/helper-plugin';
import DeleteButton from '../index';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
function ComponentToTest(props) {
return (
<IntlProvider locale="en" messages={{}}>

View File

@ -14,7 +14,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(),
useFocusWhenNavigate: jest.fn(),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useRBAC: jest.fn(() => ({
allowedActions: {
canCreate: true,

View File

@ -24,9 +24,6 @@ const PROJECT_SETTINGS_DATA_FIXTURES = {
},
};
jest.mock('@strapi/helper-plugin', () => ({
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
jest.mock('../../../../../../../hooks', () => ({
useConfigurations: jest.fn(() => ({
logos: {

View File

@ -19,7 +19,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(() => jest.fn()),
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const makeApp = (history) => (

View File

@ -19,7 +19,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(() => jest.fn()),
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const makeApp = (history) => (

View File

@ -14,7 +14,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(),
useFocusWhenNavigate: jest.fn(),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useRBAC: jest.fn(() => ({
allowedActions: {
canCreate: true,

View File

@ -219,6 +219,11 @@
"Settings.profile.form.section.experience.title": "Опыт",
"Settings.profile.form.section.helmet.title": "Профиль пользователя",
"Settings.profile.form.section.profile.page.title": "Страница профиля",
"Settings.review-workflows.page.title": "Просмотр рабочих процессов",
"Settings.review-workflows.page.subtitle": "{count, plural, =0 {# этапов} one {# этап} other {# этапа}}",
"Settings.review-workflows.page.isLoading": "Рабочие процессы загружаются",
"Settings.review-workflows.page.delete.confirm.body": "Все записи, назначенные удаленным этапам, будут перемещены на предыдущий этап. Вы уверены, что хотите сохранить?",
"Settings.review-workflows.stage.name.label": "Имя этапа",
"Settings.roles.create.description": "Определите права, предоставленные ролью",
"Settings.roles.create.title": "Создать роль",
"Settings.roles.created": "Роль создана",
@ -321,6 +326,7 @@
"Users.components.List.empty": "Нет пользователей...",
"Users.components.List.empty.withFilters": "Нет пользователей с применёнными фильтрами...",
"Users.components.List.empty.withSearch": "Нет пользователей, соответствующих запросу ({search})...",
"admin.pages.MarketPlacePage.sort.label": "Сортировка",
"admin.pages.MarketPlacePage.filters.categories": "Категории",
"admin.pages.MarketPlacePage.filters.categoriesSelected": "Выбрано {count, plural, =0 {# категорий} one {# категория} other {# категории}}",
"admin.pages.MarketPlacePage.filters.collections": "Коллекции",
@ -803,6 +809,7 @@
"content-manager.relation.notAvailable": "Нет отношений",
"content-manager.relation.publicationState.draft": "Черновик",
"content-manager.relation.publicationState.published": "Опубликовано",
"content-manager.reviewWorkflows.stage.label": "Просмотреть этап",
"content-manager.select.currently.selected": "{count} выбрано",
"content-manager.success.record.delete": "Удалено",
"content-manager.success.record.publish": "Опубликовано",

View File

@ -3,10 +3,21 @@ import PropTypes from 'prop-types';
import { Box, Flex, Typography } from '@strapi/design-system';
import { pxToRem } from '@strapi/helper-plugin';
import { getStageColorByHex } from '../../../../../pages/SettingsPage/pages/ReviewWorkflows/utils/colors';
export function ReviewWorkflowsStageEE({ color, name }) {
const { themeColorName } = getStageColorByHex(color);
return (
<Flex alignItems="center" gap={2} maxWidth={pxToRem(300)}>
<Box height={2} background={color} hasRadius shrink={0} width={2} />
<Box
height={2}
background={color}
borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
hasRadius
shrink={0}
width={2}
/>
<Typography fontWeight="regular" textColor="neutral700" ellipsis>
{name}

View File

@ -17,7 +17,7 @@ const setup = (props) => render(<ComponentFixture {...props} />);
describe('DynamicTable | ReviewWorkflowsStage', () => {
test('render stage name', () => {
const { getByText } = setup({ color: 'red', name: 'reviewed' });
const { getByText } = setup({ color: '#4945FF', name: 'reviewed' });
expect(getByText('reviewed')).toBeInTheDocument();
});

View File

@ -12,7 +12,7 @@ import { InformationBoxEE } from '../InformationBoxEE';
const STAGE_ATTRIBUTE_NAME = 'strapi_reviewWorkflows_stage';
const STAGE_FIXTURE = {
id: 1,
color: 'red',
color: '#4945FF',
name: 'Stage 1',
worklow: 1,
};
@ -36,11 +36,12 @@ jest.mock(
stages: [
{
id: 1,
color: '#4945FF',
name: 'Stage 1',
},
{
id: 2,
color: '#4945FF',
name: 'Stage 2',
},
],

View File

@ -3,13 +3,23 @@ import PropTypes from 'prop-types';
import { components } from 'react-select';
import { Flex, Typography } from '@strapi/design-system';
import { getStageColorByHex } from '../../../../../utils/colors';
export function OptionColor({ children, ...props }) {
const { color } = props.data;
const { themeColorName } = getStageColorByHex(color);
return (
<components.Option {...props}>
<Flex alignItems="center" gap={2}>
<Flex height={2} background={color} hasRadius shrink={0} width={2} />
<Flex
height={2}
background={color}
borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
hasRadius
shrink={0}
width={2}
/>
<Typography textColor="neutral800" ellipsis>
{children}

View File

@ -3,13 +3,26 @@ import PropTypes from 'prop-types';
import { components } from 'react-select';
import { Flex, Typography } from '@strapi/design-system';
import { getStageColorByHex } from '../../../../../utils/colors';
export function SingleValueColor({ children, ...props }) {
const { color } = props.data;
// in case an entity was not assigned to a stage (which displays an error)
// there is no color to display and we have to make sure the component does
// not crash
const { themeColorName } = color ? getStageColorByHex(color) : {};
return (
<components.SingleValue {...props}>
<Flex alignItems="center" gap={2}>
<Flex height={2} background={color} hasRadius shrink={0} width={2} />
<Flex
height={2}
background={color}
borderColor={themeColorName === 'neutral0' ? 'neutral150' : 'transparent'}
hasRadius
shrink={0}
width={2}
/>
<Typography textColor="neutral800" ellipsis>
{children}

View File

@ -15,11 +15,6 @@ import { reducer } from '../../../../reducer';
import { STAGE_COLOR_DEFAULT } from '../../../../constants';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn().mockReturnValue({ trackUsage: jest.fn() }),
}));
const STAGES_FIXTURE = {
id: 1,
index: 0,

View File

@ -21,11 +21,6 @@ jest.mock('../../../actions', () => ({
...jest.requireActual('../../../actions'),
}));
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn().mockReturnValue({ trackUsage: jest.fn() }),
}));
const STAGES_FIXTURE = [
{
id: 1,

View File

@ -14,12 +14,13 @@ const WORKFLOWS_FIXTURE = [
stages: [
{
id: 1,
color: 'red',
color: '#4945FF',
name: 'stage-1',
},
{
id: 2,
color: '#4945FF',
name: 'stage-2',
},
],
@ -353,7 +354,7 @@ describe('Admin | Settings | Review Workflows | reducer', () => {
stages: expect.arrayContaining([
{
id: 1,
color: 'red',
color: '#4945FF',
name: 'stage-1-modified',
},
]),

View File

@ -18,7 +18,6 @@ import { reducer } from '../reducer';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn().mockReturnValue(jest.fn()),
useTracking: jest.fn().mockReturnValue({ trackUsage: jest.fn() }),
// eslint-disable-next-line react/prop-types
CheckPagePermissions({ children }) {
return children;

View File

@ -3,10 +3,16 @@ import { lightTheme } from '@strapi/design-system';
import { STAGE_COLORS } from '../constants';
export function getStageColorByHex(hex) {
if (!hex) {
return null;
}
// there are multiple colors with the same hex code in the design tokens. In order to find
// the correct one we have to find all matching colors and then check, which ones are usable
// for stages.
const themeColors = Object.entries(lightTheme.colors).filter(([, value]) => value === hex);
const themeColors = Object.entries(lightTheme.colors).filter(
([, value]) => value.toUpperCase() === hex.toUpperCase()
);
const themeColorName = themeColors.reduce((acc, [name]) => {
if (STAGE_COLORS?.[name]) {
acc = name;

View File

@ -22,6 +22,11 @@ describe('Settings | Review Workflows | colors', () => {
themeColorName: 'primary600',
});
expect(getStageColorByHex('#4945FF')).toStrictEqual({
name: 'Blue',
themeColorName: 'primary600',
});
expect(getStageColorByHex('random')).toStrictEqual(null);
expect(getStageColorByHex()).toStrictEqual(null);
});

View File

@ -10,7 +10,6 @@ import { SingleSignOn } from '../index';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useNotification: jest.fn().mockImplementation(() => jest.fn()),
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
useRBAC: jest.fn(),

View File

@ -46,7 +46,7 @@
"@babel/preset-react": "^7.18.6",
"@babel/runtime": "^7.20.13",
"@casl/ability": "^5.4.3",
"@fingerprintjs/fingerprintjs": "3.3.6",
"@fingerprintjs/fingerprintjs": "3.4.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.10",
"@strapi/babel-plugin-switch-ee-ce": "4.10.2",
"@strapi/data-transfer": "4.10.2",
@ -134,9 +134,9 @@
"style-loader": "3.3.1",
"styled-components": "5.3.3",
"typescript": "5.0.4",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.13.1",
"webpack": "^5.82.0",
"webpack-cli": "^5.1.0",
"webpack-dev-server": "^4.15.0",
"webpackbar": "^5.0.2",
"yup": "^0.32.9"
},
@ -147,7 +147,7 @@
"@testing-library/user-event": "14.4.3",
"duplicate-dependencies-webpack-plugin": "^1.0.2",
"glob": "8.0.3",
"msw": "1.0.1",
"msw": "1.2.1",
"react-test-renderer": "^17.0.2",
"speed-measure-webpack-plugin": "1.5.0",
"webpack-bundle-analyzer": "^4.8.0"

View File

@ -25,7 +25,6 @@ const aliasExactMatch = [
'react-intl',
'react-query',
'react-redux',
'react-router',
'react-router-dom',
'react-window',
'react-select',

View File

@ -34,9 +34,6 @@ jest.mock('@strapi/helper-plugin', () => ({
get: jest.fn().mockReturnValue(mockCustomField),
getAll,
}),
useTracking: jest.fn(() => ({
trackUsage: jest.fn(),
})),
}));
const mockAttributes = [

View File

@ -3,12 +3,6 @@ import { INITIAL_STATE_DATA } from '../constants';
import FormModalNavigationProvider from '../index';
import useFormModalNavigation from '../../../hooks/useFormModalNavigation';
jest.mock('@strapi/helper-plugin', () => ({
useTracking: jest.fn(() => ({
trackUsage: jest.fn(),
})),
}));
const removeFunctionsFromObject = (state) => {
const stringified = JSON.stringify(state);
const parsed = JSON.parse(stringified);

View File

@ -32,7 +32,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
// eslint-disable-next-line
CheckPermissions: ({ children }) => <div>{children}</div>,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const makeApp = () => {

View File

@ -4,8 +4,8 @@ import { EOL } from 'os';
import { isEmpty, uniq, last, isNumber, difference, omit, set } from 'lodash/fp';
import { diff as semverDiff } from 'semver';
import type { Schema } from '@strapi/strapi';
import * as utils from '../utils';
import chalk from 'chalk';
import type {
IAsset,
IDestinationProvider,
@ -108,6 +108,8 @@ class TransferEngine<
#metadata: { source?: IMetadata; destination?: IMetadata } = {};
#ignoredDiffs: Record<string, utils.json.Diff[]> = {};
// Progress of the current stage
progress: {
// metrics on the progress such as size and record count
@ -147,8 +149,8 @@ class TransferEngine<
/**
* Report a fatal error and throw it
*/
#panic(error: Error) {
this.#reportError(error, 'fatal');
panic(error: Error) {
this.reportError(error, 'fatal');
throw error;
}
@ -156,7 +158,7 @@ class TransferEngine<
/**
* Report an error diagnostic
*/
#reportError(error: Error, severity: ErrorDiagnosticSeverity) {
reportError(error: Error, severity: ErrorDiagnosticSeverity) {
this.diagnostics.report({
kind: 'error',
details: {
@ -172,7 +174,7 @@ class TransferEngine<
/**
* Report a warning diagnostic
*/
#reportWarning(message: string, origin?: string) {
reportWarning(message: string, origin?: string) {
this.diagnostics.report({
kind: 'warning',
details: { createdAt: new Date(), message, origin },
@ -182,7 +184,7 @@ class TransferEngine<
/**
* Report an info diagnostic
*/
#reportInfo(message: string, params?: unknown) {
reportInfo(message: string, params?: unknown) {
this.diagnostics.report({
kind: 'info',
details: { createdAt: new Date(), message, params },
@ -517,7 +519,7 @@ class TransferEngine<
results.forEach((state) => {
if (state.status === 'rejected') {
this.#reportWarning(state.reason, `transfer(${stage})`);
this.reportWarning(state.reason, `transfer(${stage})`);
}
});
@ -544,7 +546,7 @@ class TransferEngine<
.on('error', (e) => {
updateEndTime();
this.#emitStageUpdate('error', stage);
this.#reportError(e, 'error');
this.reportError(e, 'error');
destination.destroy(e);
reject(e);
})
@ -591,7 +593,7 @@ class TransferEngine<
results.forEach((result) => {
if (result.status === 'rejected') {
this.#panic(result.reason);
this.panic(result.reason);
}
});
}
@ -607,7 +609,7 @@ class TransferEngine<
results.forEach((result) => {
if (result.status === 'rejected') {
this.#panic(result.reason);
this.panic(result.reason);
}
});
}
@ -644,77 +646,26 @@ class TransferEngine<
this.#assertSchemasMatching(sourceSchemas, destinationSchemas);
}
} catch (error) {
// if this is a schema matching error
// if this is a schema matching error, allow handlers to resolve it
if (error instanceof TransferEngineValidationError && error.details?.details?.diffs) {
const schemaDiffs = error.details?.details?.diffs as Record<string, Diff[]>;
const context = {
ignoreDiffs: {},
diffs: schemaDiffs,
source: this.sourceProvider,
destination: this.destinationProvider,
};
let workflowsStatus;
const source = 'Schema Integrity';
Object.entries(context.diffs).forEach(([uid, diffs]) => {
for (const diff of diffs) {
const path = `${uid}.${diff.path.join('.')}`;
const endPath = diff.path[diff.path.length - 1];
// Catch known features
// TODO: can this be moved outside of the engine?
if (
uid === 'admin::workflow' ||
uid === 'admin::workflow-stage' ||
endPath.startsWith('strapi_reviewWorkflows_')
) {
workflowsStatus = diff.kind;
}
// handle generic cases
else if (diff.kind === 'added') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} does not exist on destination`),
source
);
} else if (diff.kind === 'deleted') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} does not exist on source`),
source
);
} else if (diff.kind === 'modified') {
this.#reportWarning(
chalk.red(`${chalk.bold(path)} has a different data type`),
source
);
}
}
});
// output the known feature warnings
if (workflowsStatus === 'added') {
this.#reportWarning(
chalk.red(`Review workflows feature does not exist on destination`),
source
);
} else if (workflowsStatus === 'deleted') {
this.#reportWarning(
chalk.red(`Review workflows feature does not exist on source`),
source
);
} else if (workflowsStatus === 'modified') {
this.#panic(
new TransferEngineInitializationError(
'Unresolved differences in schema [review workflows]'
)
);
}
await runMiddleware<typeof context>(context, this.#handlers.schemaDiff);
if (Object.keys(context.diffs).length) {
this.#panic(new TransferEngineInitializationError('Unresolved differences in schema'));
this.#ignoredDiffs = context.ignoreDiffs;
// if there are any remaining diffs that weren't ignored
if (utils.json.diff(context.diffs, this.#ignoredDiffs).length) {
this.panic(new TransferEngineInitializationError('Unresolved differences in schema'));
}
return;
}
@ -756,7 +707,7 @@ class TransferEngine<
e instanceof Error &&
(!lastDiagnostic || lastDiagnostic.kind !== 'error' || lastDiagnostic.details.error !== e)
) {
this.#reportError(e, (e as DataTransferError).severity || 'fatal');
this.reportError(e, (e as DataTransferError).severity || 'fatal');
}
// Rollback the destination provider if an exception is thrown during the transfer
@ -780,9 +731,9 @@ class TransferEngine<
} catch (error) {
// Error happening during the before transfer step should be considered fatal errors
if (error instanceof Error) {
this.#panic(error);
this.panic(error);
} else {
this.#panic(
this.panic(
new Error(`Unknwon error when executing "beforeTransfer" on the ${origin} provider`)
);
}
@ -821,6 +772,7 @@ class TransferEngine<
return callback(null, entity);
}
// TODO: this would be safer if we only ignored things in ignoredDiffs, otherwise continue and let an error be thrown
const availableContentTypes = Object.entries(schemas)
.filter(([, schema]) => schema.modelType === 'contentType')
.map(([uid]) => uid);
@ -861,6 +813,7 @@ class TransferEngine<
return callback(null, link);
}
// TODO: this would be safer if we only ignored things in ignoredDiffs, otherwise continue and let an error be thrown
const availableContentTypes = Object.entries(schemas)
.filter(([, schema]) => schema.modelType === 'contentType')
.map(([uid]) => uid);

View File

@ -39,7 +39,7 @@
"devDependencies": {
"@strapi/helper-plugin": "4.10.2",
"@testing-library/react": "12.1.4",
"msw": "1.0.0",
"msw": "1.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.3.4",

View File

@ -77,8 +77,8 @@
"rimraf": "3.0.2",
"styled-components": "5.3.3",
"typescript": "5.0.4",
"webpack": "^5.76.0",
"webpack-cli": "^5.0.1"
"webpack": "^5.82.0",
"webpack-cli": "^5.1.0"
},
"peerDependencies": {
"@strapi/design-system": "^1.7.3",

View File

@ -3,21 +3,13 @@ import PropTypes from 'prop-types';
import { Box, Flex, Button, Typography, Table as TableCompo } from '@strapi/design-system';
import { useIntl } from 'react-intl';
import { Trash } from '@strapi/icons';
import styled from 'styled-components';
import useQueryParams from '../../hooks/useQueryParams';
import { useTracking } from '../../features/Tracking';
import ConfirmDialog from '../ConfirmDialog';
import EmptyBodyTable from '../EmptyBodyTable';
import TableHead from './TableHead';
const BlockActions = styled(Flex)`
& > * + * {
margin-left: ${({ theme }) => theme.spaces[2]};
}
margin-left: ${({ pullRight }) => (pullRight ? 'auto' : undefined)};
`;
const Table = ({
action,
children,
@ -133,7 +125,7 @@ const Table = ({
<Box>
<Box paddingBottom={4}>
<Flex justifyContent="space-between">
<BlockActions>
<Flex gap={3}>
<Typography variant="epsilon" textColor="neutral600">
{formatMessage(
{
@ -151,7 +143,7 @@ const Table = ({
>
{formatMessage({ id: 'global.delete', defaultMessage: 'Delete' })}
</Button>
</BlockActions>
</Flex>
</Flex>
</Box>
</Box>

View File

@ -21,7 +21,7 @@ import { useAppInfo } from './AppInfo';
* @typedef {Object} TrackingContextValue
* @property {string | boolean} uuid
* @property {string | undefined} deviceId
* @property {TelemetryProperties} telemetryProperties
* @property {TelemetryProperties | undefined} telemetryProperties
*/
/* -------------------------------------------------------------------------------------------------
@ -32,7 +32,11 @@ import { useAppInfo } from './AppInfo';
* @preserve
* @type {React.Context<TrackingContextValue>}
*/
const TrackingContext = React.createContext();
const TrackingContext = React.createContext({
uuid: false,
deviceId: undefined,
telemetryProperties: undefined,
});
/* -------------------------------------------------------------------------------------------------
* Provider

View File

@ -34,6 +34,10 @@ const setup = (props) =>
});
describe('useTracking', () => {
beforeAll(() => {
window.strapi.telemetryDisabled = false;
});
afterEach(() => {
jest.clearAllMocks();
});

View File

@ -75,6 +75,8 @@ describe('Export', () => {
};
}),
exitMessageText: jest.fn(),
getDiffHandler: jest.fn(),
setSignalHandler: jest.fn(),
};
jest.mock(
'../../../utils/data-transfer.js',

View File

@ -32,6 +32,7 @@ const createTransferEngine = jest.fn(() => {
on: jest.fn().mockReturnThis(),
onDiagnostic: jest.fn().mockReturnThis(),
},
onSchemaDiff: jest.fn(),
};
});
@ -81,6 +82,8 @@ describe('Import', () => {
};
}),
exitMessageText: jest.fn(),
getDiffHandler: jest.fn(),
setSignalHandler: jest.fn(),
};
jest.mock(
'../../../utils/data-transfer.js',

View File

@ -22,9 +22,9 @@ const {
abortTransfer,
getTransferTelemetryPayload,
setSignalHandler,
getDiffHandler,
} = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander');
/**
* @typedef {import('@strapi/data-transfer/src/file/providers').ILocalFileSourceProviderOptions} ILocalFileSourceProviderOptions
@ -113,30 +113,7 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => {
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
setSignalHandler(async () => {
await abortTransfer({ engine, strapi });
exitWith(1, exitMessageText('import', true));
});
const confirmed = await confirmMessage(
'There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want to continue?',
{
force: opts.force,
}
);
// reset handler back to normal
setSignalHandler(() => abortTransfer({ engine, strapi }));
if (confirmed) {
context.diffs = [];
return next(context);
}
return next(context);
});
engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force }));
progress.on(`stage::start`, ({ stage, data }) => {
updateLoader(stage, data).start();

View File

@ -24,6 +24,8 @@ describe('Transfer', () => {
};
}),
exitMessageText: jest.fn(),
getDiffHandler: jest.fn(),
setSignalHandler: jest.fn(),
};
jest.mock(
'../../../utils/data-transfer.js',
@ -66,6 +68,7 @@ describe('Transfer', () => {
on: jest.fn().mockReturnThis(),
onDiagnostic: jest.fn().mockReturnThis(),
},
onSchemaDiff: jest.fn(),
};
},
},

View File

@ -23,9 +23,9 @@ const {
abortTransfer,
getTransferTelemetryPayload,
setSignalHandler,
getDiffHandler,
} = require('../../utils/data-transfer');
const { exitWith } = require('../../utils/helpers');
const { confirmMessage } = require('../../utils/commander');
/**
* @typedef TransferCommandOptions Options given to the CLI transfer command
@ -148,30 +148,7 @@ module.exports = async (opts) => {
const { updateLoader } = loadersFactory();
engine.onSchemaDiff(async (context, next) => {
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
setSignalHandler(async () => {
await abortTransfer({ engine, strapi });
exitWith(1, exitMessageText('transfer', true));
});
const confirmed = await confirmMessage(
'There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want to continue?',
{
force: opts.force,
}
);
// reset handler back to normal
setSignalHandler(() => abortTransfer({ engine, strapi }));
if (confirmed) {
context.diffs = [];
return next(context);
}
return next(context);
});
engine.onSchemaDiff(getDiffHandler(engine, { force: opts.force }));
progress.on(`stage::start`, ({ stage, data }) => {
updateLoader(stage, data).start();

View File

@ -12,9 +12,11 @@ const {
createLogger,
} = require('@strapi/logger');
const ora = require('ora');
const { TransferEngineInitializationError } = require('@strapi/data-transfer/dist/engine/errors');
const { merge } = require('lodash/fp');
const { readableBytes, exitWith } = require('./helpers');
const strapi = require('../../index');
const { getParseListWithChoices, parseInteger } = require('./commander');
const { getParseListWithChoices, parseInteger, confirmMessage } = require('./commander');
const exitMessageText = (process, error = false) => {
const processCapitalized = process[0].toUpperCase() + process.slice(1);
@ -266,6 +268,79 @@ const getTransferTelemetryPayload = (engine) => {
};
};
/**
* Get a transfer engine schema diff handler that confirms with the user before bypassing a schema check
*/
const getDiffHandler = (engine, { force }) => {
return async (context, next) => {
// if we abort here, we need to actually exit the process because of conflict with inquirer prompt
setSignalHandler(async () => {
await abortTransfer({ engine, strapi });
exitWith(1, exitMessageText('import', true));
});
let workflowsStatus;
const source = 'Schema Integrity';
Object.entries(context.diffs).forEach(([uid, diffs]) => {
for (const diff of diffs) {
const path = `${uid}.${diff.path.join('.')}`;
const endPath = diff.path[diff.path.length - 1];
// Catch known features
if (
uid === 'admin::workflow' ||
uid === 'admin::workflow-stage' ||
endPath.startsWith('strapi_reviewWorkflows_')
) {
workflowsStatus = diff.kind;
}
// handle generic cases
else if (diff.kind === 'added') {
engine.reportWarning(
chalk.red(`${chalk.bold(path)} does not exist on destination`),
source
);
} else if (diff.kind === 'deleted') {
engine.reportWarning(chalk.red(`${chalk.bold(path)} does not exist on source`), source);
} else if (diff.kind === 'modified') {
engine.reportWarning(chalk.red(`${chalk.bold(path)} has a different data type`), source);
}
}
});
// output the known feature warnings
if (workflowsStatus === 'added') {
engine.reportWarning(
chalk.red(`Review workflows feature does not exist on destination`),
source
);
} else if (workflowsStatus === 'deleted') {
engine.reportWarning(chalk.red(`Review workflows feature does not exist on source`), source);
} else if (workflowsStatus === 'modified') {
engine.panic(
new TransferEngineInitializationError('Unresolved differences in schema [review workflows]')
);
}
const confirmed = await confirmMessage(
'There are differences in schema between the source and destination, and the data listed above will be lost. Are you sure you want to continue?',
{
force,
}
);
// reset handler back to normal
setSignalHandler(() => abortTransfer({ engine, strapi }));
if (confirmed) {
context.ignoreDiffs = merge(context.diffs, context.ignoredDiffs);
}
return next(context);
};
};
module.exports = {
loadersFactory,
buildTransferTable,
@ -281,4 +356,5 @@ module.exports = {
formatDiagnostic,
abortTransfer,
setSignalHandler,
getDiffHandler,
};

View File

@ -1,24 +1,18 @@
import React, { useMemo } from 'react';
import styled from 'styled-components';
import PropTypes from 'prop-types';
import { Flex } from '@strapi/design-system';
import { Box, Flex } from '@strapi/design-system';
import { PaginationContext } from './PaginationContext';
const PaginationWrapper = styled.nav``;
const PaginationList = styled(Flex)`
& > * + * {
margin-left: ${({ theme }) => theme.spaces[1]};
}
`;
export const Pagination = ({ children, label, activePage, pageCount }) => {
const paginationValue = useMemo(() => ({ activePage, pageCount }), [activePage, pageCount]);
return (
<PaginationContext.Provider value={paginationValue}>
<PaginationWrapper aria-label={label}>
<PaginationList as="ul">{children}</PaginationList>
</PaginationWrapper>
<Box as="nav" aria-label={label}>
<Flex as="ul" gap={1}>
{children}
</Flex>
</Box>
</PaginationContext.Provider>
);
};

View File

@ -12,11 +12,6 @@ import SearchAsset from '../index';
const handleChange = jest.fn();
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const makeApp = (queryValue) => (
<ThemeProvider theme={lightTheme}>
<IntlProvider locale="en">

View File

@ -40,17 +40,6 @@ import PageSize from './PageSize';
import SearchAsset from './SearchAsset';
import { isSelectable } from './utils/isSelectable';
const StartBlockActions = styled(Flex)`
& > * + * {
margin-left: ${({ theme }) => theme.spaces[2]};
}
margin-left: ${({ pullRight }) => (pullRight ? 'auto' : undefined)};
`;
const EndBlockActions = styled(StartBlockActions)`
flex-shrink: 0;
`;
const TypographyMaxWidth = styled(Typography)`
max-width: 100%;
`;
@ -131,7 +120,7 @@ export const BrowseStep = ({
<Box paddingBottom={4}>
<Flex justifyContent="space-between" alignItems="flex-start">
{(assetCount > 0 || folderCount > 0 || isFiltering) && (
<StartBlockActions wrap="wrap">
<Flex gap={2} wrap="wrap">
{multiple && isGridView && (
<Flex
paddingLeft={2}
@ -157,11 +146,11 @@ export const BrowseStep = ({
appliedFilters={queryObject?.filters?.$and}
onChangeFilters={onChangeFilters}
/>
</StartBlockActions>
</Flex>
)}
{(assetCount > 0 || folderCount > 0 || isSearching) && (
<EndBlockActions pullRight>
<Flex marginLeft="auto" shrink={0}>
<ActionContainer paddingTop={1} paddingBottom={1}>
<IconButton
icon={isGridView ? <List /> : <Grid />}
@ -180,7 +169,7 @@ export const BrowseStep = ({
/>
</ActionContainer>
<SearchAsset onChangeSearch={onChangeSearch} queryValue={queryObject._q || ''} />
</EndBlockActions>
</Flex>
)}
</Flex>
</Box>

View File

@ -14,7 +14,6 @@ jest.mock('../../../../hooks/useFolder');
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
usePersistentState: jest.fn().mockReturnValue([0, jest.fn()]),
}));

View File

@ -15,7 +15,6 @@ jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: jest.fn(() => jest.fn()),
useQueryParams: jest.fn(),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
jest.mock('../../../hooks/useMediaLibraryPermissions');

View File

@ -13,7 +13,6 @@ import { useMediaLibraryPermissions } from '../../../hooks/useMediaLibraryPermis
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useQueryParams: jest.fn().mockReturnValue([{ query: {} }]),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useFetchClient: jest.fn().mockReturnValue({
put: jest.fn().mockImplementation({}),
}),

View File

@ -11,7 +11,6 @@ jest.mock('@strapi/helper-plugin', () => ({
useNotification: jest.fn(() => ({
toggleNotification: jest.fn(),
})),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const ASSET_FIXTURES = [

View File

@ -8,7 +8,6 @@ import { TableList } from '..';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useQueryParams: jest.fn(() => [{ query: {} }]),
}));

View File

@ -9,7 +9,6 @@ jest.mock('../../../../utils/getTrad', () => (x) => x);
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const queryClient = new QueryClient({

View File

@ -25,7 +25,6 @@ const notificationStatusMock = jest.fn();
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useNotification: () => notificationStatusMock,
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useFetchClient: jest.fn().mockReturnValue({
put: jest.fn().mockResolvedValue({ data: { data: {} } }),
get: jest.fn(),

View File

@ -8,11 +8,6 @@ import { ThemeProvider, lightTheme } from '@strapi/design-system';
import useModalQueryParams from '../useModalQueryParams';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
}));
const refetchQueriesMock = jest.fn();
jest.mock('react-query', () => ({

View File

@ -58,7 +58,7 @@
"@testing-library/react": "12.1.4",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.4.3",
"msw": "1.0.1",
"msw": "1.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.3.4",

View File

@ -65,7 +65,7 @@
"@apidevtools/swagger-parser": "^10.1.0",
"@testing-library/react": "12.1.4",
"history": "^4.9.0",
"msw": "1.0.1",
"msw": "1.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.3.4",

View File

@ -0,0 +1,62 @@
{
"CMEditViewCopyLocale.copy-failure": "Не удалось скопировать перевод",
"CMEditViewCopyLocale.copy-success": "Перевод скопирована",
"CMEditViewCopyLocale.copy-text": "Заполните данные из другого языка",
"CMEditViewCopyLocale.submit-text": "Да, заполнить",
"CMListView.popover.display-locales.label": "Отображать переведенные языки",
"CheckboxConfirmation.Modal.body": "Вы хотите выключить это?",
"CheckboxConfirmation.Modal.button-confirm": "Да, выключить",
"CheckboxConfirmation.Modal.content": "Отключение перевода приведет к удалению всего вашего контента, кроме того, который связан с вашим языком по умолчанию (если вообще он существует).",
"Field.localized": "Это значение уникально для выбранного перевода",
"Field.not-localized": "Это значение является общим для всех переводов",
"Settings.list.actions.add": "Добавить новый перевод",
"Settings.list.actions.delete": "Удалить перевод",
"Settings.list.actions.deleteAdditionalInfos": "Это приведет к удалению активных версий перевода <em>(из плагина интернационализации)</em>",
"Settings.list.actions.edit": "Редактировать перевод",
"Settings.list.description": "Настройте параметры для плагина интернационализации",
"Settings.list.empty.description": "Это необычное поведение, означающее, что всё-таки вы изменили базу данных вручную. Убедитесь, что базе данных сохранён хотя бы один перевод, чтобы иметь возможность правильно использовать Strapi.",
"Settings.list.empty.title": "Переводов нет.",
"Settings.locales.default": "По умолчанию",
"Settings.locales.list.sort.default": "Сортировать переводы по умолчанию",
"Settings.locales.list.sort.displayName": "Сортировать по отображаемому имени",
"Settings.locales.list.sort.id": "Сортировать по ID",
"Settings.locales.modal.advanced": "Расширенные настройки",
"Settings.locales.modal.advanced.setAsDefault": "Установить в качестве перевода по умолчанию",
"Settings.locales.modal.advanced.setAsDefault.hint": "Необходим один перевод по умолчанию, вы можете изменить его выбрав другой перевод",
"Settings.locales.modal.advanced.settings": "Настройки",
"Settings.locales.modal.base": "Основные настройки",
"Settings.locales.modal.create.alreadyExist": "Этот перевод уже существует",
"Settings.locales.modal.create.defaultLocales.loading": "Загрузка доступных переводов...",
"Settings.locales.modal.create.success": "Перевод успешно добавлен",
"Settings.locales.modal.create.tab.label": "Переключение между основными настройками этого плагина и расширенными настройками",
"Settings.locales.modal.delete.confirm": "Да, удалить",
"Settings.locales.modal.delete.message": "Удаление этого перевода приведет к удалению всего связанного с ним содержимого. Если вы хотите сохранить какой-то контент, обязательно сначала перенесите его в другой язык (перераспределите в другой перевод).",
"Settings.locales.modal.delete.secondMessage": "Вы хотите удалить этот перевод?",
"Settings.locales.modal.delete.success": "Перевод успешно удалён",
"Settings.locales.modal.edit.confirmation": "Готово!",
"Settings.locales.modal.edit.locales.label": "Переводы",
"Settings.locales.modal.edit.success": "Перевод успешно отредактирован",
"Settings.locales.modal.edit.tab.label": "Переключение между основными настройками этого плагина и расширенными настройками",
"Settings.locales.modal.locales.displayName": "Отображаемое имя перевода",
"Settings.locales.modal.locales.displayName.description": "Перевод будет отображаться под этим именем в панели администратора",
"Settings.locales.modal.locales.displayName.error": "Отображаемое имя перевода может содержать не более 50 символов.",
"Settings.locales.modal.locales.label": "Переводы",
"Settings.locales.modal.locales.loaded": "Переводы были успешно загружены.",
"Settings.locales.modal.title": "Настройки",
"Settings.locales.row.default-locale": "Перевод по умолчанию",
"Settings.locales.row.displayName": "Отображаемое имя",
"Settings.locales.row.id": "ID",
"Settings.permissions.loading": "Разрешения на загрузку",
"Settings.permissions.read.denied.description": "Чтобы иметь возможность прочитать это, обязательно свяжитесь с администратором вашей системы.",
"Settings.permissions.read.denied.title": "У вас нет прав доступа к этому контенту.",
"actions.select-locale": "Выбрать перевод",
"components.Select.locales.not-available": "Нет доступного контента",
"plugin.description.long": "Этот плагин позволяет создавать, читать и обновлять контент (словом, производить всевозможные действия с контентом) на разных языках, как из панели администратора, так и через API.",
"plugin.description.short": "Этот плагин позволяет создавать, читать и обновлять контент на разных языках, как из панели администратора, так и через API.",
"plugin.name": "Интернационализация",
"plugin.schema.i18n.ensure-unique-localization": "Уникальные поля должны быть переведены",
"plugin.schema.i18n.localized.description-content-type": "Позволяет перевести запись на разные языки",
"plugin.schema.i18n.localized.description-field": "Поле может иметь разные значения на каждом языке",
"plugin.schema.i18n.localized.label-content-type": "Интернационализация",
"plugin.schema.i18n.localized.label-field": "Включить перевод для этого поля"
}

View File

@ -53,7 +53,7 @@
},
"devDependencies": {
"@testing-library/react": "12.1.4",
"msw": "1.0.1",
"msw": "1.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.3.4",

View File

@ -29,7 +29,7 @@
"@sentry/node": "6.19.7",
"@strapi/design-system": "1.7.3",
"@strapi/helper-plugin": "4.10.2",
"@strapi/icons": "1.6.5"
"@strapi/icons": "1.7.3"
},
"devDependencies": {
"react": "^17.0.2",

View File

@ -14,9 +14,6 @@ jest.mock('@strapi/helper-plugin', () => ({
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn, unlockApp: jest.fn() })),
useRBAC: jest.fn(),
CheckPagePermissions: ({ children }) => children,
useTracking: jest.fn(() => ({
trackUsage: jest.fn(),
})),
}));
const client = new QueryClient({

View File

@ -9,7 +9,6 @@ import { ProvidersPage } from '../index';
jest.mock('@strapi/helper-plugin', () => ({
...jest.requireActual('@strapi/helper-plugin'),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
useNotification: jest.fn(),
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
useRBAC: jest.fn(),

View File

@ -19,7 +19,6 @@ jest.mock('@strapi/helper-plugin', () => {
...jest.requireActual('@strapi/helper-plugin'),
useNotification: mockUseNotification,
useOverlayBlocker: jest.fn(() => ({ lockApp: jest.fn(), unlockApp: jest.fn() })),
useTracking: jest.fn(() => ({ trackUsage: jest.fn() })),
};
});

View File

@ -56,7 +56,7 @@
"@testing-library/react-hooks": "8.0.1",
"@testing-library/user-event": "14.4.3",
"history": "^4.9.0",
"msw": "1.0.1",
"msw": "1.2.1",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-router-dom": "5.3.4",

View File

@ -23,7 +23,7 @@
"dependencies": {
"@babel/template": "^7.20.7",
"reselect": "4.1.7",
"resolve": "1.20.0"
"resolve": "1.22.2"
},
"devDependencies": {
"@babel/cli": "^7.20.7",

581
yarn.lock

File diff suppressed because it is too large Load Diff