diff --git a/packages/core/admin/admin/src/pages/Admin/index.js b/packages/core/admin/admin/src/pages/Admin/index.js
index 96d5659fe6..67f12b8d53 100644
--- a/packages/core/admin/admin/src/pages/Admin/index.js
+++ b/packages/core/admin/admin/src/pages/Admin/index.js
@@ -39,11 +39,6 @@ const SettingsPage = lazy(() =>
// import(
// /* webpackChunkName: "content-type-builder" */ '@strapi/plugin-content-type-builder/admin/src/pages/App'
// )
-// );
-// const Upload = lazy(() =>
-// import(/* webpackChunkName: "upload" */ '@strapi/plugin-upload/admin/src/pages/App')
-// );
-
// Simple hook easier for testing
const useTrackUsage = () => {
const { trackUsage } = useTracking();
@@ -88,9 +83,8 @@ const Admin = () => {
{/* TODO */}
{/* <
-
- */}
+ */}
{routes}
diff --git a/packages/core/admin/admin/src/translations/en.json b/packages/core/admin/admin/src/translations/en.json
index efaaa1fb5b..5cc062f409 100644
--- a/packages/core/admin/admin/src/translations/en.json
+++ b/packages/core/admin/admin/src/translations/en.json
@@ -617,5 +617,7 @@
"or": "OR",
"request.error.model.unknown": "This model doesn't exist",
"skipToContent": "Skip to content",
- "clearLabel": "Clear"
+ "clearLabel": "Clear",
+ "submit": "Submit",
+ "anErrorOccurred": "Woops! Something went wrong. Please, try again."
}
diff --git a/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/AnErrorOccurred.stories.mdx b/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/AnErrorOccurred.stories.mdx
new file mode 100644
index 0000000000..1b3f53a8c5
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/AnErrorOccurred.stories.mdx
@@ -0,0 +1,31 @@
+
+
+import { Meta, ArgsTable, Canvas, Story } from '@storybook/addon-docs';
+import { Main, Row, Button } from '@strapi/parts';
+import AnErrorOccurred from './index';
+
+
+
+# AnErrorOccurred
+
+This component is used to display an empty state.
+
+## Usage
+
+
+
+### Props
+
+
diff --git a/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/index.js b/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/index.js
new file mode 100644
index 0000000000..03831d8513
--- /dev/null
+++ b/packages/core/helper-plugin/lib/src/components/AnErrorOccurred/index.js
@@ -0,0 +1,38 @@
+import React from 'react';
+import AlertWarningIcon from '@strapi/icons/AlertWarningIcon';
+import { EmptyStateLayout } from '@strapi/parts/EmptyStateLayout';
+import PropTypes from 'prop-types';
+import { useIntl } from 'react-intl';
+
+const AnErrorOccurred = ({ content, ...rest }) => {
+ const { formatMessage } = useIntl();
+
+ return (
+ }
+ content={formatMessage(
+ { id: content.id, defaultMessage: content.defaultMessage },
+ content.values
+ )}
+ {...rest}
+ />
+ );
+};
+
+AnErrorOccurred.defaultProps = {
+ content: {
+ id: 'anErrorOccurred',
+ defaultMessage: 'Woops! Something went wrong. Please, try again.',
+ values: {},
+ },
+};
+
+AnErrorOccurred.propTypes = {
+ content: PropTypes.shape({
+ id: PropTypes.string,
+ defaultMessage: PropTypes.string,
+ values: PropTypes.object,
+ }),
+};
+
+export default AnErrorOccurred;
diff --git a/packages/core/helper-plugin/lib/src/components/Search/index.js b/packages/core/helper-plugin/lib/src/components/Search/index.js
index 17ec78da8b..113b05c1db 100644
--- a/packages/core/helper-plugin/lib/src/components/Search/index.js
+++ b/packages/core/helper-plugin/lib/src/components/Search/index.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useRef } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { SearchIcon } from '@strapi/icons';
@@ -7,11 +7,29 @@ import { IconButton } from '@strapi/parts/IconButton';
import useQueryParams from '../../hooks/useQueryParams';
const Search = ({ label }) => {
+ const wrapperRef = useRef(null);
+ const iconButtonRef = useRef(null);
+ const isMountedRef = useRef(false);
+
const [isOpen, setIsOpen] = useState(false);
const [{ query }, setQuery] = useQueryParams();
const [value, setValue] = useState(query?._q || '');
const { formatMessage } = useIntl();
+ const handleToggle = () => setIsOpen(prev => !prev);
+
+ useEffect(() => {
+ if (isMountedRef.current) {
+ if (isOpen) {
+ wrapperRef.current.querySelector('input').focus();
+ } else {
+ iconButtonRef.current.focus();
+ }
+ }
+
+ isMountedRef.current = true;
+ }, [isOpen]);
+
useEffect(() => {
const handler = setTimeout(() => {
if (value) {
@@ -25,26 +43,26 @@ const Search = ({ label }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [value]);
- const handleToggle = () => {
- setIsOpen(prev => !prev);
- };
-
if (isOpen) {
return (
- setValue(value)}
- onBlur={() => setIsOpen(false)}
- value={value}
- clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
- onClear={() => setValue('')}
- >
- {label}
-
+
+ setValue(value)}
+ onBlur={() => setIsOpen(false)}
+ value={value}
+ clearLabel={formatMessage({ id: 'clearLabel', defaultMessage: 'Clear' })}
+ onClear={() => setValue('')}
+ >
+ {label}
+
+
);
}
- return } label="Search" onClick={handleToggle} />;
+ return (
+ } label="Search" onClick={handleToggle} />
+ );
};
Search.propTypes = {
diff --git a/packages/core/helper-plugin/lib/src/index.js b/packages/core/helper-plugin/lib/src/index.js
index a65de24fc5..b2dae0043e 100644
--- a/packages/core/helper-plugin/lib/src/index.js
+++ b/packages/core/helper-plugin/lib/src/index.js
@@ -186,6 +186,7 @@ export { default as EmptyStateLayout } from './components/EmptyStateLayout';
export { default as NoContent } from './components/NoContent';
export { default as NoMedia } from './components/NoMedia';
export { default as NoPermissions } from './components/NoPermissions';
+export { default as AnErrorOccurred } from './components/AnErrorOccurred';
export { default as EmptyBodyTable } from './components/EmptyBodyTable';
export { default as GenericInput } from './components/GenericInput';
export * from './components/InjectionZone';
diff --git a/packages/core/upload/admin/src/hooks/useAssets.js b/packages/core/upload/admin/src/hooks/useAssets.js
new file mode 100644
index 0000000000..198fb3e59a
--- /dev/null
+++ b/packages/core/upload/admin/src/hooks/useAssets.js
@@ -0,0 +1,52 @@
+import { useEffect } from 'react';
+import { useQuery } from 'react-query';
+import { useNotifyAT } from '@strapi/parts/LiveRegions';
+import { useNotification, useQueryParams } from '@strapi/helper-plugin';
+import { useIntl } from 'react-intl';
+import { axiosInstance, getRequestUrl } from '../utils';
+
+export const useAssets = ({ skipWhen }) => {
+ const { formatMessage } = useIntl();
+ const toggleNotification = useNotification();
+ const { notifyStatus } = useNotifyAT();
+ const [{ rawQuery, query }, setQuery] = useQueryParams();
+ const dataRequestURL = getRequestUrl('files');
+
+ const { data, error, isLoading } = useQuery(
+ 'assets',
+ async () => {
+ const { data } = await axiosInstance.get(`${dataRequestURL}${rawQuery}`);
+
+ return data;
+ },
+ { enabled: !skipWhen }
+ );
+
+ useEffect(() => {
+ if (!query) {
+ setQuery({ sort: 'updatedAt:DESC', page: 1, pageSize: 10 });
+ }
+ }, [query, setQuery]);
+
+ useEffect(() => {
+ if (data) {
+ notifyStatus(
+ formatMessage({
+ id: 'list.asset.at.finished',
+ defaultMessage: 'The assets have finished loading.',
+ })
+ );
+ }
+ }, [data, notifyStatus, formatMessage]);
+
+ useEffect(() => {
+ if (error) {
+ toggleNotification({
+ type: 'warning',
+ message: { id: 'notification.error' },
+ });
+ }
+ }, [error, toggleNotification]);
+
+ return { data, error, isLoading };
+};
diff --git a/packages/core/upload/admin/src/index.js b/packages/core/upload/admin/src/index.js
index 32c2381aaa..d61716091c 100644
--- a/packages/core/upload/admin/src/index.js
+++ b/packages/core/upload/admin/src/index.js
@@ -23,7 +23,20 @@ export default {
register(app) {
// TODO update doc and guides
app.addComponents({ name: 'media-library', Component: InputModalStepper });
+ app.addMenuLink({
+ to: `/plugins/${pluginId}`,
+ icon,
+ intlLabel: {
+ id: `${pluginId}.plugin.name`,
+ defaultMessage: 'Media Library',
+ },
+ permissions: pluginPermissions.main,
+ Component: async () => {
+ const component = await import(/* webpackChunkName: "media-library-page" */ './pages/App');
+ return component;
+ },
+ });
// TODO
// app.addCorePluginMenuLink({
// to: `/plugins/${pluginId}`,
diff --git a/packages/core/upload/admin/src/pages/App/components/ListView.js b/packages/core/upload/admin/src/pages/App/components/ListView.js
new file mode 100644
index 0000000000..f48a6468b2
--- /dev/null
+++ b/packages/core/upload/admin/src/pages/App/components/ListView.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+// TODO: implement the view
+export const ListView = ({ assets }) => {
+ return Number of assets: {assets.length}
;
+};
+
+ListView.propTypes = {
+ assets: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
+};
diff --git a/packages/core/upload/admin/src/pages/App/index.js b/packages/core/upload/admin/src/pages/App/index.js
index 86c080afcf..480c071664 100644
--- a/packages/core/upload/admin/src/pages/App/index.js
+++ b/packages/core/upload/admin/src/pages/App/index.js
@@ -1,31 +1,133 @@
import React from 'react';
-import { Switch, Route, Redirect } from 'react-router-dom';
-import { LoadingIndicatorPage, useRBAC } from '@strapi/helper-plugin';
-import pluginId from '../../pluginId';
+import { Redirect } from 'react-router-dom';
+import { useIntl } from 'react-intl';
+import styled from 'styled-components';
+import {
+ LoadingIndicatorPage,
+ useRBAC,
+ useFocusWhenNavigate,
+ NoPermissions,
+ NoMedia,
+ AnErrorOccurred,
+ Search,
+} from '@strapi/helper-plugin';
+import { Layout, HeaderLayout, ContentLayout, ActionLayout } from '@strapi/parts/Layout';
+import { Main } from '@strapi/parts/Main';
+import { Button } from '@strapi/parts/Button';
+import AddIcon from '@strapi/icons/AddIcon';
+import { Box } from '@strapi/parts/Box';
+import { BaseCheckbox } from '@strapi/parts/BaseCheckbox';
+import { ListView } from './components/ListView';
+import { useAssets } from '../../hooks/useAssets';
+import { getTrad } from '../../utils';
import pluginPermissions from '../../permissions';
-import { AppContext } from '../../contexts';
-import HomePage from '../HomePage';
+const BoxWithHeight = styled(Box)`
+ height: ${32 / 16}rem;
+ display: flex;
+ align-items: center;
+`;
const App = () => {
const state = useRBAC(pluginPermissions);
+ const { formatMessage } = useIntl();
+ const { data, isLoading, error } = useAssets({
+ skipWhen: !state.allowedActions.canMain,
+ });
- // Show a loader while all permissions are being checked
- if (state.isLoading) {
- return ;
+ useFocusWhenNavigate();
+
+ const canRead = state.allowedActions.canMain;
+ const loading = state.isLoading || isLoading;
+
+ if (!loading && !canRead) {
+ return ;
}
- if (state.allowedActions.canMain) {
- return (
-
-
-
-
-
- );
- }
+ return (
+
+
+ 0
+ ? 'header.content.assets-multiple'
+ : 'header.content.assets.assets-single'
+ ),
+ defaultMessage: '0 assets',
+ },
+ { number: data?.length || 0 }
+ )}
+ primaryAction={
+ }>
+ {formatMessage({
+ id: getTrad('header.actions.upload-assets'),
+ defaultMessage: 'Upload new assets',
+ })}
+
+ }
+ />
- return ;
+
+
+
+
+
+ >
+ }
+ endActions={
+
+ }
+ />
+
+
+ {loading && }
+ {error && }
+ {!canRead && }
+ {canRead && data && data.length === 0 && (
+ }>
+ {formatMessage({
+ id: getTrad('modal.header.browse'),
+ defaultMessage: 'Upload assets',
+ })}
+
+ }
+ content={formatMessage({
+ id: getTrad('list.assets.empty'),
+ defaultMessage: 'Upload your first assets...',
+ })}
+ />
+ )}
+ {canRead && data && data.length > 0 && }
+
+
+
+ );
};
export default App;
diff --git a/packages/core/upload/admin/src/pages/App/tests/App.test.js b/packages/core/upload/admin/src/pages/App/tests/App.test.js
new file mode 100644
index 0000000000..3debc40174
--- /dev/null
+++ b/packages/core/upload/admin/src/pages/App/tests/App.test.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import { ThemeProvider, lightTheme } from '@strapi/parts';
+import { QueryClientProvider, QueryClient } from 'react-query';
+import { render as renderTL, screen, waitFor } from '@testing-library/react';
+import { useRBAC } from '@strapi/helper-plugin';
+import { Provider } from 'react-redux';
+import { combineReducers, createStore } from 'redux';
+import { MemoryRouter } from 'react-router-dom';
+import reducers from '../../../reducers';
+import en from '../../../translations/en.json';
+import server from './server';
+import MediaLibraryPage from '..';
+
+jest.mock('@strapi/helper-plugin', () => ({
+ ...jest.requireActual('@strapi/helper-plugin'),
+ useRBAC: jest.fn(),
+ useNotification: jest.fn(() => jest.fn()),
+}));
+
+jest.mock('../../../utils', () => ({
+ ...jest.requireActual('../../../utils'),
+ getTrad: x => x,
+}));
+
+jest.mock('react-intl', () => ({
+ FormattedMessage: ({ id }) => id,
+ useIntl: () => ({ formatMessage: jest.fn(({ id }) => en[id]) }),
+}));
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ refetchOnWindowFocus: false,
+ },
+ },
+});
+
+const store = createStore(combineReducers(reducers));
+
+const renderML = () =>
+ renderTL(
+
+
+
+
+
+
+
+
+
+ );
+
+describe('Media library homepage', () => {
+ beforeAll(() => server.listen());
+
+ beforeEach(() => {
+ useRBAC.mockReturnValue({ isLoading: false, allowedActions: { canMain: true } });
+ });
+
+ afterEach(() => {
+ server.resetHandlers();
+ jest.clearAllMocks();
+ });
+
+ afterAll(() => server.close());
+
+ describe('loading state', () => {
+ it('shows a loader when resolving the permissions', () => {
+ useRBAC.mockReturnValue({ isLoading: true, allowedActions: { canMain: false } });
+
+ renderML();
+
+ expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('true');
+ expect(screen.getByText('Loading content.')).toBeInTheDocument();
+ });
+
+ it('shows a loader when resolving the assets', () => {
+ renderML();
+
+ expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('true');
+ expect(screen.getByText('Loading content.')).toBeInTheDocument();
+ });
+ });
+
+ describe('empty state', () => {
+ it('shows an empty state when there are no assets found', async () => {
+ renderML();
+
+ await waitFor(() =>
+ expect(screen.getByText('Upload your first assets...')).toBeInTheDocument()
+ );
+
+ expect(screen.getByRole('main').getAttribute('aria-busy')).toBe('false');
+ });
+ });
+});
diff --git a/packages/core/upload/admin/src/pages/App/tests/server.js b/packages/core/upload/admin/src/pages/App/tests/server.js
new file mode 100644
index 0000000000..dea57b197c
--- /dev/null
+++ b/packages/core/upload/admin/src/pages/App/tests/server.js
@@ -0,0 +1,10 @@
+import { setupServer } from 'msw/node';
+import { rest } from 'msw';
+
+const server = setupServer(
+ rest.get('*/upload/files*', (req, res, ctx) => {
+ return res(ctx.json([]));
+ })
+);
+
+export default server;
diff --git a/packages/core/upload/admin/src/pages/HomePage/index.js b/packages/core/upload/admin/src/pages/HomePage/index.js
index 084570f825..9bf0f89ab7 100644
--- a/packages/core/upload/admin/src/pages/HomePage/index.js
+++ b/packages/core/upload/admin/src/pages/HomePage/index.js
@@ -17,10 +17,11 @@ import { useAppContext, useSelectTimestamps } from '../../hooks';
import Container from '../../components/Container';
import HomePageContent from './HomePageContent';
import HomePageModalStepper from '../../components/HomePageModalStepper';
-import { generateStringFromParams, getHeaderLabel } from './utils';
+import { getHeaderLabel } from './utils';
import init from './init';
import reducer, { initialState } from './reducer';
+// TODO: remove this file when ML is migrated
const HomePage = () => {
const toggleNotification = useNotification();
const { allowedActions } = useAppContext();
@@ -72,7 +73,7 @@ const HomePage = () => {
const fetchData = async () => {
const dataRequestURL = getRequestUrl('files');
- const params = generateStringFromParams(query);
+ const params = query;
const paramsToSend = params.includes('sort')
? params
@@ -98,7 +99,7 @@ const HomePage = () => {
};
const fetchDataCount = async () => {
- const params = generateStringFromParams(query, ['_limit', '_start']);
+ const params = query;
const requestURL = getRequestUrl('files/count');
try {
diff --git a/packages/core/upload/admin/src/pages/HomePage/utils/generateStringFromParams.js b/packages/core/upload/admin/src/pages/HomePage/utils/generateStringFromParams.js
deleted file mode 100644
index b67f9e999c..0000000000
--- a/packages/core/upload/admin/src/pages/HomePage/utils/generateStringFromParams.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import { isEmpty, toString } from 'lodash';
-import generateParamsFromQuery from './generateParamsFromQuery';
-
-const generateStringFromParams = (query, paramsToFilter = []) => {
- let paramsString = '';
- const paramsObject = generateParamsFromQuery(query);
-
- Object.keys(paramsObject)
- .filter(key => {
- return !paramsToFilter.includes(key) && !isEmpty(toString(paramsObject[key]));
- })
- .forEach(key => {
- const value = paramsObject[key];
-
- if (key.includes('mime') && value === 'file') {
- const revertedKey = key.includes('_ncontains') ? 'mime_contains' : 'mime_ncontains';
-
- paramsString += `&${revertedKey}=image&${revertedKey}=video`;
- } else {
- paramsString += `&${key}=${value}`;
- }
- });
-
- return paramsString.substring(1);
-};
-
-export default generateStringFromParams;
diff --git a/packages/core/upload/admin/src/pages/HomePage/utils/index.js b/packages/core/upload/admin/src/pages/HomePage/utils/index.js
index 45124e524b..31c65f3993 100644
--- a/packages/core/upload/admin/src/pages/HomePage/utils/index.js
+++ b/packages/core/upload/admin/src/pages/HomePage/utils/index.js
@@ -1,2 +1 @@
-export { default as generateStringFromParams } from './generateStringFromParams';
export { default as getHeaderLabel } from './getHeaderLabel';
diff --git a/packages/core/upload/admin/src/pages/HomePage/utils/tests/generateStringFromParams.test.js b/packages/core/upload/admin/src/pages/HomePage/utils/tests/generateStringFromParams.test.js
deleted file mode 100644
index 21a30bd9ea..0000000000
--- a/packages/core/upload/admin/src/pages/HomePage/utils/tests/generateStringFromParams.test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import generateStringFromParams from '../generateStringFromParams';
-
-describe('MEDIA LIBRARY | pages | HomePage | utils', () => {
- describe('generateStringFromParams', () => {
- it('should return a string with query params if query is empty', () => {
- const search = '';
- const query = new URLSearchParams(search);
-
- const expected = '_limit=10&_start=0';
-
- expect(generateStringFromParams(query)).toEqual(expected);
- });
-
- it('should return a string with query params if search is not empty', () => {
- const search = '_limit=20&_start=0&mime_contains=image';
- const query = new URLSearchParams(search);
-
- const expected = '_limit=20&_start=0&mime_contains=image';
-
- expect(generateStringFromParams(query)).toEqual(expected);
- });
-
- describe('return a string with converted filters if value is file', () => {
- it('should return _ncontains instead of _contains', () => {
- const search = '?mime_ncontains=file';
- const query = new URLSearchParams(search);
-
- const expected = '_limit=10&_start=0&mime_contains=image&mime_contains=video';
-
- expect(generateStringFromParams(query)).toEqual(expected);
- });
-
- it('should return _contains instead of _ncontains', () => {
- const search = '?_limit=20&_start=0&mime_contains=file';
- const query = new URLSearchParams(search);
-
- const expected = '_limit=20&_start=0&mime_ncontains=image&mime_ncontains=video';
-
- expect(generateStringFromParams(query)).toEqual(expected);
- });
- });
-
- describe('it should filter the defined params', () => {
- it('should return _ncontains instead of _contains', () => {
- const search = '?mime_ncontains=file&test=true';
- const query = new URLSearchParams(search);
-
- const expected = '_limit=10&_start=0&mime_contains=image&mime_contains=video&test=true';
-
- expect(generateStringFromParams(query, [])).toEqual(expected);
- });
-
- it('should not return the _limit param', () => {
- const search = '?mime_ncontains=file';
- const query = new URLSearchParams(search);
-
- const expected = '_start=0&mime_contains=image&mime_contains=video';
-
- expect(generateStringFromParams(query, ['_limit'])).toEqual(expected);
- });
- });
- });
-});
diff --git a/packages/core/upload/admin/src/translations/en.json b/packages/core/upload/admin/src/translations/en.json
index bba5fc51da..6235a99665 100644
--- a/packages/core/upload/admin/src/translations/en.json
+++ b/packages/core/upload/admin/src/translations/en.json
@@ -1,5 +1,6 @@
{
"button.next": "Next",
+ "bulk.select.label": "Select all assets",
"checkControl.crop-duplicate": "Duplicate & crop the asset",
"checkControl.crop-original": "Crop the original asset",
"control-card.add": "Add",
@@ -39,6 +40,8 @@
"list.assets.selected.plural": "{number} assets selected",
"list.assets.selected.singular": "{number} asset selected",
"list.assets.type-not-allowed": "This type of file is not allowed.",
+ "list.assets.empty": "Upload your first assets...",
+ "list.asset.at.finished": "The assets have finished loading.",
"modal.file-details.date": "Date",
"modal.file-details.dimensions": "Dimensions",
"modal.file-details.extension": "Extension",
@@ -62,7 +65,8 @@
"plugin.description.long": "Media file management.",
"plugin.description.short": "Media file management.",
"plugin.name": "Media Library",
- "search.placeholder": "Search for an asset...",
+ "search.label": "Search for an asset",
+ "search.placeholder": "e.g: the first dog on the moon",
"settings.form.autoOrientation.description": "Automatically rotate image according to EXIF orientation tag",
"settings.form.autoOrientation.label": "Enable auto orientation",
"settings.form.responsiveDimensions.description": "It automatically generates multiple formats (large, medium, small) of the uploaded asset",
diff --git a/packages/core/upload/admin/src/pages/HomePage/utils/generateParamsFromQuery.js b/packages/core/upload/admin/src/utils/generateParamsFromQuery.js
similarity index 100%
rename from packages/core/upload/admin/src/pages/HomePage/utils/generateParamsFromQuery.js
rename to packages/core/upload/admin/src/utils/generateParamsFromQuery.js
diff --git a/packages/core/upload/admin/src/pages/HomePage/utils/tests/generateParamsFromQuery.test.js b/packages/core/upload/admin/src/utils/tests/generateParamsFromQuery.test.js
similarity index 100%
rename from packages/core/upload/admin/src/pages/HomePage/utils/tests/generateParamsFromQuery.test.js
rename to packages/core/upload/admin/src/utils/tests/generateParamsFromQuery.test.js