diff --git a/backend/src/integration/resources/SHIBUI-1732-4.side b/backend/src/integration/resources/SHIBUI-1732-4.side
index 6aab66e67..214a49cbd 100644
--- a/backend/src/integration/resources/SHIBUI-1732-4.side
+++ b/backend/src/integration/resources/SHIBUI-1732-4.side
@@ -992,6 +992,13 @@
["xpath=//div[3]/div/div/div[3]/button", "xpath:position"]
],
"value": ""
+ }, {
+ "id": "14c486b1-bdff-4474-94e3-b4286303a8fd",
+ "comment": "",
+ "command": "pause",
+ "target": "5000",
+ "targets": [],
+ "value": ""
}, {
"id": "e3892564-1a1b-4ee6-bbab-49d3cb3079d7",
"comment": "",
@@ -999,21 +1006,21 @@
"target": "css=table > tbody > tr",
"targets": [],
"value": ""
- },{
- "id": "4ec2c493-85e4-403b-9b09-031c5728f498",
- "comment": "",
- "command": "open",
- "target": "/api/heheheheheheheWipeout",
- "targets": [],
- "value": ""
- }, {
- "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4",
- "comment": "",
- "command": "assertText",
- "target": "css=body",
- "targets": [],
- "value": "yes, you did it"
- }]
+ }, {
+ "id": "4ec2c493-85e4-403b-9b09-031c5728f498",
+ "comment": "",
+ "command": "open",
+ "target": "/api/heheheheheheheWipeout",
+ "targets": [],
+ "value": ""
+ }, {
+ "id": "e074980a-8f21-4c22-8412-c4b6fcdcd1a4",
+ "comment": "",
+ "command": "assertText",
+ "target": "css=body",
+ "targets": [],
+ "value": "yes, you did it"
+ }]
}],
"suites": [{
"id": "575d414c-556d-45f7-b2f2-c9971ad51348",
diff --git a/ui/src/app/core/components/Spinner.js b/ui/src/app/core/components/Spinner.js
new file mode 100644
index 000000000..880f53d2a
--- /dev/null
+++ b/ui/src/app/core/components/Spinner.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSpinner } from '@fortawesome/free-solid-svg-icons';
+
+export function Spinner ({ size, className }) {
+ return ()
+}
+
+export default Spinner;
\ No newline at end of file
diff --git a/ui/src/app/dashboard/view/ActionsTab.js b/ui/src/app/dashboard/view/ActionsTab.js
index 30bc9e371..eda40b751 100644
--- a/ui/src/app/dashboard/view/ActionsTab.js
+++ b/ui/src/app/dashboard/view/ActionsTab.js
@@ -1,11 +1,12 @@
import React from 'react';
import { MetadataActions } from '../../admin/container/MetadataActions';
import UserActions from '../../admin/container/UserActions';
+import Spinner from '../../core/components/Spinner';
import Translate from '../../i18n/components/translate';
import SourceList from '../../metadata/domain/source/component/SourceList';
-export function ActionsTab({ sources, users, reloadSources, reloadUsers }) {
+export function ActionsTab({ sources, users, reloadSources, reloadUsers, loadingSources, loadingUsers }) {
return (
<>
@@ -21,7 +22,9 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers }) {
{(enable) =>
- enable(s, e, reloadSources)} />
+ enable(s, e, reloadSources)}>
+ {loadingSources &&
}
+
}
@@ -36,7 +39,9 @@ export function ActionsTab({ sources, users, reloadSources, reloadUsers }) {
-
+
+ {loadingUsers &&
}
+
>
diff --git a/ui/src/app/dashboard/view/AdminTab.js b/ui/src/app/dashboard/view/AdminTab.js
index 5c116c242..ba71e0685 100644
--- a/ui/src/app/dashboard/view/AdminTab.js
+++ b/ui/src/app/dashboard/view/AdminTab.js
@@ -5,12 +5,13 @@ import UserMaintenance from '../../admin/component/UserMaintenance';
import API_BASE_PATH from '../../App.constant';
import Translate from '../../i18n/components/translate';
+import Spinner from '../../core/components/Spinner';
export function AdminTab () {
const [users, setUsers] = React.useState([]);
- const { get, response } = useFetch(`${API_BASE_PATH}/admin/users`, {
+ const { get, response, loading } = useFetch(`${API_BASE_PATH}/admin/users`, {
cachePolicy: 'no-cache'
}, []);
@@ -46,7 +47,9 @@ export function AdminTab () {
onChangeUserRole={onChangeUserRole}
onDeleteUser={onDeleteUser}
onChangeUserGroup={onChangeUserGroup} />}
+
+ {loading &&
}
diff --git a/ui/src/app/dashboard/view/Dashboard.js b/ui/src/app/dashboard/view/Dashboard.js
index b1d058eaf..7644b6def 100644
--- a/ui/src/app/dashboard/view/Dashboard.js
+++ b/ui/src/app/dashboard/view/Dashboard.js
@@ -26,13 +26,13 @@ export function Dashboard () {
const isAdmin = useIsAdmin();
- const loading = useCurrentUserLoading();
+ const loadingUser = useCurrentUserLoading();
const [actions, setActions] = React.useState(0);
const [users, setUsers] = React.useState([]);
const [sources, setSources] = React.useState([]);
- const { get, response } = useFetch(`${API_BASE_PATH}`, {
+ const { get, response, loading } = useFetch(`${API_BASE_PATH}`, {
cachePolicy: 'no-cache'
});
@@ -64,7 +64,7 @@ export function Dashboard () {
return (
- {loading ?
+ {loadingUser ?
@@ -109,7 +109,13 @@ export function Dashboard () {
} />
-
+
} />
diff --git a/ui/src/app/dashboard/view/ProvidersTab.js b/ui/src/app/dashboard/view/ProvidersTab.js
index 023a7f78a..d986f3f68 100644
--- a/ui/src/app/dashboard/view/ProvidersTab.js
+++ b/ui/src/app/dashboard/view/ProvidersTab.js
@@ -8,13 +8,14 @@ import { Ordered } from '../component/Ordered';
import { useIsAdmin } from '../../core/user/UserContext';
import Alert from 'react-bootstrap/Alert';
import { MetadataActions } from '../../admin/container/MetadataActions';
+import Spinner from '../../core/components/Spinner';
const searchProps = ['name', '@type', 'createdBy'];
export function ProvidersTab () {
const [providers, setProviders] = React.useState([]);
- const { get, response } = useMetadataEntities('provider', {
+ const { get, response, loading } = useMetadataEntities('provider', {
cachePolicy: 'no-cache'
});
@@ -54,7 +55,9 @@ export function ProvidersTab () {
last={last}
onEnable={(p, e) => enable(p, e, loadProviders)}
onOrderUp={onOrderUp}
- onOrderDown={onOrderDown}>
+ onOrderDown={onOrderDown}>
+ {loading &&
}
+
}
}
diff --git a/ui/src/app/dashboard/view/SourcesTab.js b/ui/src/app/dashboard/view/SourcesTab.js
index b564a3872..40ff4dd4f 100644
--- a/ui/src/app/dashboard/view/SourcesTab.js
+++ b/ui/src/app/dashboard/view/SourcesTab.js
@@ -5,9 +5,11 @@ import Translate from '../../i18n/components/translate';
import SourceList from '../../metadata/domain/source/component/SourceList';
import { useMetadataEntities, useMetadataEntity } from '../../metadata/hooks/api';
import { Search } from '../component/Search';
+import { Spinner } from '../../core/components/Spinner';
import { NotificationContext, createNotificationAction, NotificationTypes } from '../../notifications/hoc/Notifications';
+
const searchProps = ['serviceProviderName', 'entityId', 'createdBy'];
export function SourcesTab () {
@@ -16,7 +18,7 @@ export function SourcesTab () {
const [sources, setSources] = React.useState([]);
- const { get, response } = useMetadataEntities('source', {
+ const { get, response, loading } = useMetadataEntities('source', {
cachePolicy: 'no-cache'
});
@@ -68,7 +70,9 @@ export function SourcesTab () {
entities={searched}
onDelete={(id) => remove(id, loadSources)}
onEnable={(s, e) => enable(s, e, loadSources) }
- onChangeGroup={changeSourceGroup} />
+ onChangeGroup={changeSourceGroup}>
+ {loading &&
}
+
}
}
diff --git a/ui/src/app/metadata/domain/provider/component/ProviderList.js b/ui/src/app/metadata/domain/provider/component/ProviderList.js
index 3e559eb2a..9a9ac90d4 100644
--- a/ui/src/app/metadata/domain/provider/component/ProviderList.js
+++ b/ui/src/app/metadata/domain/provider/component/ProviderList.js
@@ -12,88 +12,92 @@ import { Scroller } from '../../../../dashboard/component/Scroller';
import { useIsAdmin } from '../../../../core/user/UserContext';
import { useTranslator } from '../../../../i18n/hooks';
-export function ProviderList({ entities, reorder = true, first, last, onEnable, onOrderUp, onOrderDown }) {
+export function ProviderList({ children, entities, reorder = true, first, last, onEnable, onOrderUp, onOrderDown }) {
const isAdmin = useIsAdmin();
const translator = useTranslator();
return (
-
- {(limited) =>
-
-
-
- | Order |
- Title |
- Provider Type |
- Author |
- Created Date |
- Enabled |
-
-
-
- {limited.map((provider, idx) =>
-
-
-
- {reorder ?
- {idx + 1}
+
+
+ {(limited) =>
+
+
+
+ | Order |
+ Title |
+ Provider Type |
+ Author |
+ Created Date |
+ Enabled |
+
+
+
+ {limited.map((provider, idx) =>
+
+
+
+ {reorder ?
+ {idx + 1}
+ :
+ —
+ }
+
+
+
+
+ |
+
+ {provider.name}
+ |
+ { provider['@type'] } |
+ { provider.createdBy } |
+ |
+
+
+ {onEnable && isAdmin ?
+ onEnable(provider, checked)}
+ checked={provider.enabled}
+ >
+
:
- —
+
+
+
}
-
-
-
-
- |
-
- {provider.name}
- |
- { provider['@type'] } |
- { provider.createdBy } |
- |
-
-
- {onEnable && isAdmin ?
- onEnable(provider, checked)}
- checked={provider.enabled}
- >
-
- :
-
-
-
- }
-
- |
-
- )}
-
-
-
- }
-
+
+ |
+
+ )}
+
+
+
+ }
+
+ {children}
+
+
);
}
diff --git a/ui/src/app/metadata/domain/source/component/SourceList.js b/ui/src/app/metadata/domain/source/component/SourceList.js
index 109396c2c..8e8a10825 100644
--- a/ui/src/app/metadata/domain/source/component/SourceList.js
+++ b/ui/src/app/metadata/domain/source/component/SourceList.js
@@ -16,14 +16,15 @@ import { useTranslator } from '../../../../i18n/hooks';
import { useCanEnable, useIsAdmin } from '../../../../core/user/UserContext';
import { GroupsProvider } from '../../../../admin/hoc/GroupsProvider';
-export default function SourceList({ entities, onDelete, onEnable, onChangeGroup }) {
+export default function SourceList({ entities, onDelete, onEnable, onChangeGroup, children }) {
const translator = useTranslator();
const isAdmin = useIsAdmin();
const canEnable = useCanEnable();
return (
-
+
+
{(limited) =>
@@ -124,6 +125,9 @@ export default function SourceList({ entities, onDelete, onEnable, onChangeGroup
}
-
+
+ {children}
+
+
);
}
diff --git a/ui/src/app/metadata/hoc/MetadataSchema.js b/ui/src/app/metadata/hoc/MetadataSchema.js
index 025e7a415..ea14361d9 100644
--- a/ui/src/app/metadata/hoc/MetadataSchema.js
+++ b/ui/src/app/metadata/hoc/MetadataSchema.js
@@ -5,10 +5,12 @@ import { useTranslator } from '../../i18n/hooks';
export const MetadataSchemaContext = React.createContext();
export const MetadataDefinitionContext = React.createContext();
+export const MetadataSchemaLoading = React.createContext();
export function MetadataSchema({ type, children, wizard = false }) {
const definition = React.useMemo(() => wizard ? getWizard(type) : getDefinition(type), [type, wizard]);
+ const [loading, setLoading] = React.useState(false);
const { get, response } = useFetch(``, {
cachePolicy: 'no-cache'
@@ -21,19 +23,23 @@ export function MetadataSchema({ type, children, wizard = false }) {
if (response.ok) {
setSchema(source);
}
+ setLoading(false);
}
/*eslint-disable react-hooks/exhaustive-deps*/
React.useEffect(() => {
setSchema(null);
loadSchema(definition);
+ setLoading(true);
}, [definition]);
return (
{type && definition && schema &&
-
- {children}
+
+
+ {children}
+
}
@@ -44,6 +50,10 @@ export function useMetadataSchemaContext () {
return React.useContext(MetadataSchemaContext);
}
+export function useMetadataSchemaLoading () {
+ return React.useContext(MetadataSchemaLoading);
+}
+
export function useMetadataDefinitionContext() {
return React.useContext(MetadataDefinitionContext);
}
diff --git a/ui/src/app/metadata/hoc/MetadataVersionsLoader.js b/ui/src/app/metadata/hoc/MetadataVersionsLoader.js
index 025cea743..d957903d9 100644
--- a/ui/src/app/metadata/hoc/MetadataVersionsLoader.js
+++ b/ui/src/app/metadata/hoc/MetadataVersionsLoader.js
@@ -4,12 +4,15 @@ import { getMetadataPath } from '../hooks/api';
import API_BASE_PATH from '../../App.constant';
import useFetch from 'use-http';
import { last } from 'lodash';
+import Spinner from '../../core/components/Spinner';
export function MetadataVersionsLoader ({versions, children}) {
const ref = React.useRef({});
const [list, setList] = React.useState({});
+ const [loading, setLoading] = React.useState(false);
+
const { type, id } = useParams();
const { get, response } = useFetch(`/${API_BASE_PATH}${getMetadataPath(type)}/${id}/Versions`, {
@@ -22,6 +25,8 @@ export function MetadataVersionsLoader ({versions, children}) {
addToList(v, l);
if (last(versions) !== v) {
loadNext(versions[versions.indexOf(v) + 1]);
+ } else {
+ setLoading(false);
}
}
}
@@ -36,14 +41,19 @@ export function MetadataVersionsLoader ({versions, children}) {
function loadNext (v) {
loadVersion(v);
+
}
/*eslint-disable react-hooks/exhaustive-deps*/
React.useEffect(() => {
loadNext(versions[0]);
+ setLoading(true);
}, [versions]);
- return (
- {children(versions.map(v => list[v]).filter(v => !!v))}
- );
+ return (
+
+ {loading &&
}
+ {children(versions.map(v => list[v]).filter(v => !!v))}
+
+ );
}
\ No newline at end of file
diff --git a/ui/src/app/metadata/view/MetadataHistory.js b/ui/src/app/metadata/view/MetadataHistory.js
index 719ce8dc3..6df6a7c2e 100644
--- a/ui/src/app/metadata/view/MetadataHistory.js
+++ b/ui/src/app/metadata/view/MetadataHistory.js
@@ -9,6 +9,7 @@ import Translate from '../../i18n/components/translate';
import { useMetadataHistory } from '../hooks/api';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faUndo } from '@fortawesome/free-solid-svg-icons';
+import Spinner from '../../core/components/Spinner';
const sortVersionsByDate = (versions) => {
return versions.sort((a, b) => {
@@ -107,16 +108,13 @@ export function MetadataHistory () {
)}
+ {loading &&
}
>
}
- {loading &&
-
- Loading...
-
}
>
);
}
\ No newline at end of file
diff --git a/ui/src/app/metadata/wizard/MetadataProviderWizard.js b/ui/src/app/metadata/wizard/MetadataProviderWizard.js
index 7193c0573..72df681fd 100644
--- a/ui/src/app/metadata/wizard/MetadataProviderWizard.js
+++ b/ui/src/app/metadata/wizard/MetadataProviderWizard.js
@@ -2,7 +2,7 @@ import React from 'react';
import { WizardNav } from './WizardNav';
import { MetadataWizardForm } from './MetadataWizardForm';
import { setWizardIndexAction, useCurrentIndex, useIsLastPage, useWizardDispatcher } from './Wizard';
-import { useMetadataDefinitionContext, useMetadataDefinitionValidator, useMetadataSchemaContext } from '../hoc/MetadataSchema';
+import { useMetadataDefinitionContext, useMetadataDefinitionValidator, useMetadataSchemaContext, useMetadataSchemaLoading } from '../hoc/MetadataSchema';
import { checkChanges, useMetadataSchema } from '../hooks/schema';
import { useMetadataFormDispatcher, setFormDataAction, setFormErrorAction, useMetadataFormData, useMetadataFormErrors } from '../hoc/MetadataFormContext';
import { MetadataConfiguration } from '../component/MetadataConfiguration';
@@ -12,6 +12,7 @@ import { useMetadataProviders } from '../hooks/api';
import { removeNull } from '../../core/utility/remove_null';
import { useUserGroup } from '../../core/user/UserContext';
+import Spinner from '../../core/components/Spinner';
export function MetadataProviderWizard({onSave, loading, block}) {
@@ -20,6 +21,7 @@ export function MetadataProviderWizard({onSave, loading, block}) {
const definition = useMetadataDefinitionContext();
const schema = useMetadataSchemaContext();
+ const schemaLoading = useMetadataSchemaLoading();
const processed = useMetadataSchema(definition, schema);
@@ -49,6 +51,7 @@ export function MetadataProviderWizard({onSave, loading, block}) {
return (
<>
+
+ {schemaLoading &&
}
{
@@ -75,6 +77,7 @@ export function MetadataSourceWizard ({ onShowNav, onSave, block, loading }) {
+ {schemaLoading &&
}
{warnings && warnings.hasOwnProperty(current) &&