Compare commits
7 Commits
test-pytho
...
loading/re
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e66d33233 | ||
|
|
e1127aa018 | ||
|
|
7cf78c3ed6 | ||
|
|
2974b493c7 | ||
|
|
9aa4ee0890 | ||
|
|
82e85d1129 | ||
|
|
b753373456 |
@@ -99,7 +99,7 @@ Similar to the object loader's `id` argument you can also pass a `query` object
|
||||
|
||||
### Control over loading and error states
|
||||
|
||||
By default both `EntityObject` and `EntityList` loaders will handle loading state for you by using `LoadingAndErrorWrapper` under the hood. If for some reason you want to handle loading on your own you can disable this behavior by setting `loadingAndErrorWrapper: false`.
|
||||
By default both `EntityObject` and `EntityList` loaders will handle loading state for you by using `Loading` under the hood. If for some reason you want to handle loading on your own you can disable this behavior by setting `loadingAndErrorWrapper: false`.
|
||||
|
||||
### Wrapped objects
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
DataPermissionValue,
|
||||
} from "metabase/admin/permissions/types";
|
||||
import { useDatabaseQuery } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading/Loading";
|
||||
import { getParentPath } from "metabase/hoc/ModalRoute";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
import { updateImpersonation } from "metabase-enterprise/advanced_permissions/reducer";
|
||||
@@ -136,12 +136,7 @@ const _ImpersonationModal = ({ route, params }: ImpersonationModalProps) => {
|
||||
isDatabaseLoading || isImpersonationLoading || !attributes || !database;
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={isLoading}
|
||||
error={error ?? impersonationError}
|
||||
/>
|
||||
);
|
||||
return <Loading loading={isLoading} error={error ?? impersonationError} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from "prop-types";
|
||||
import { useEffect, useImperativeHandle, useRef } from "react";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import QuestionResultLoader from "metabase/containers/QuestionResultLoader";
|
||||
import Visualization from "metabase/visualizations/components/Visualization";
|
||||
|
||||
@@ -40,13 +40,12 @@ const QuestionLoadAndDisplay = ({
|
||||
reloadFnRef.current = reload;
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
loading={shouldShowLoader}
|
||||
error={error || resultProps?.result?.error}
|
||||
noWrapper
|
||||
>
|
||||
<Visualization {...props} {...resultProps} />
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
}}
|
||||
</QuestionResultLoader>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StrategyForm } from "metabase/admin/performance/components/StrategyForm
|
||||
import { useCacheConfigs } from "metabase/admin/performance/hooks/useCacheConfigs";
|
||||
import { useConfirmIfFormIsDirty } from "metabase/admin/performance/hooks/useConfirmIfFormIsDirty";
|
||||
import { useSaveStrategy } from "metabase/admin/performance/hooks/useSaveStrategy";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import type { DashboardSidebarPageProps } from "metabase/dashboard/components/DashboardInfoSidebar";
|
||||
import { color } from "metabase/lib/colors";
|
||||
import { Button, Flex, Icon, Title } from "metabase/ui";
|
||||
@@ -30,7 +30,7 @@ const DashboardStrategySidebar_Base = ({
|
||||
throw new Error("This dashboard has an invalid id");
|
||||
}
|
||||
const dashboardId: number = dashboard.id;
|
||||
const { configs, setConfigs, loading, error } = useCacheConfigs({
|
||||
const { configs, setConfigs, ...cacheConfigsResult } = useCacheConfigs({
|
||||
configurableModels,
|
||||
id: dashboardId,
|
||||
});
|
||||
@@ -87,11 +87,7 @@ const DashboardStrategySidebar_Base = ({
|
||||
Caching settings
|
||||
</Title>
|
||||
</Flex>
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
loadingMessages={[]}
|
||||
loading={loading}
|
||||
error={error}
|
||||
>
|
||||
<Loading delay result={cacheConfigsResult}>
|
||||
<StrategyForm
|
||||
targetId={dashboardId}
|
||||
targetModel="dashboard"
|
||||
@@ -104,7 +100,7 @@ const DashboardStrategySidebar_Base = ({
|
||||
shouldShowName={false}
|
||||
onReset={closeSidebar}
|
||||
/>
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
{confirmationModal}
|
||||
</DashboardStrategySidebarBody>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { StrategyForm } from "metabase/admin/performance/components/StrategyForm
|
||||
import { useCacheConfigs } from "metabase/admin/performance/hooks/useCacheConfigs";
|
||||
import { useConfirmIfFormIsDirty } from "metabase/admin/performance/hooks/useConfirmIfFormIsDirty";
|
||||
import { useSaveStrategy } from "metabase/admin/performance/hooks/useSaveStrategy";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import { DelayedLoading } from "metabase/components/Loading/DelayedLoading";
|
||||
import { color } from "metabase/lib/colors";
|
||||
import type { SidebarCacheFormProps } from "metabase/plugins";
|
||||
import { Button, Flex, Icon, Title } from "metabase/ui";
|
||||
@@ -24,7 +24,7 @@ const SidebarCacheForm_Base = ({
|
||||
}: SidebarCacheFormProps) => {
|
||||
const configurableModels = useMemo(() => [model], [model]);
|
||||
const id: number = getItemId(model, item);
|
||||
const { configs, setConfigs, loading, error } = useCacheConfigs({
|
||||
const { configs, setConfigs, ...cacheConfigsResult } = useCacheConfigs({
|
||||
configurableModels,
|
||||
id,
|
||||
});
|
||||
@@ -77,7 +77,7 @@ const SidebarCacheForm_Base = ({
|
||||
Caching settings
|
||||
</Title>
|
||||
</Flex>
|
||||
<DelayedLoadingAndErrorWrapper loading={loading} error={error}>
|
||||
<DelayedLoading result={cacheConfigsResult}>
|
||||
<StrategyForm
|
||||
targetId={id}
|
||||
targetModel={model}
|
||||
@@ -91,7 +91,7 @@ const SidebarCacheForm_Base = ({
|
||||
buttonLabels={{ save: t`Save`, discard: t`Cancel` }}
|
||||
isInSidebar
|
||||
/>
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</DelayedLoading>
|
||||
{confirmationModal}
|
||||
</SidebarCacheFormBody>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import _ from "underscore";
|
||||
|
||||
import { useCacheConfigs } from "metabase/admin/performance/hooks/useCacheConfigs";
|
||||
import { getShortStrategyLabel } from "metabase/admin/performance/utils";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import type { SidebarCacheSectionProps } from "metabase/plugins";
|
||||
import { Flex } from "metabase/ui";
|
||||
|
||||
@@ -20,7 +20,7 @@ export const SidebarCacheSection = ({
|
||||
const id = useMemo(() => getItemId(model, item), [model, item]);
|
||||
const configurableModels = useMemo(() => [model], [model]);
|
||||
|
||||
const { configs, loading, error } = useCacheConfigs({
|
||||
const { configs, ...cacheConfigsResult } = useCacheConfigs({
|
||||
configurableModels,
|
||||
id,
|
||||
});
|
||||
@@ -36,7 +36,7 @@ export const SidebarCacheSection = ({
|
||||
const labelId = "question-caching-policy-label";
|
||||
|
||||
return (
|
||||
<DelayedLoadingAndErrorWrapper delay={0} loading={loading} error={error}>
|
||||
<Loading result={cacheConfigsResult}>
|
||||
<Flex align="center" justify="space-between">
|
||||
<span id={labelId}>{t`Caching policy`}</span>
|
||||
<FormLauncher
|
||||
@@ -47,6 +47,6 @@ export const SidebarCacheSection = ({
|
||||
{shortStrategyLabel}
|
||||
</FormLauncher>
|
||||
</Flex>
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from "ttag";
|
||||
import _ from "underscore";
|
||||
|
||||
import { skipToken, useListStaleCollectionItemsQuery } from "metabase/api";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import { DelayedLoading } from "metabase/components/Loading/DelayedLoading";
|
||||
import { PaginationControls } from "metabase/components/PaginationControls";
|
||||
import Search from "metabase/entities/search";
|
||||
import { useListSelect } from "metabase/hooks/use-list-select";
|
||||
@@ -144,7 +144,7 @@ const _CleanupCollectionModal = ({
|
||||
onDateFilterChange={handleChangeDateFilter}
|
||||
onRecursiveFilterChange={handleChangeRecursiveFilter}
|
||||
/>
|
||||
<DelayedLoadingAndErrorWrapper loading={isLoading} error={error}>
|
||||
<DelayedLoading loading={isLoading} error={error}>
|
||||
{items.length === 0 ? (
|
||||
<CleanupEmptyState duration={getDateFilterLabel(dateFilter)} />
|
||||
) : (
|
||||
@@ -160,7 +160,7 @@ const _CleanupCollectionModal = ({
|
||||
onSelectNone={selection.clear}
|
||||
/>
|
||||
)}
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</DelayedLoading>
|
||||
</Modal.Body>
|
||||
<Flex
|
||||
px="2.5rem"
|
||||
|
||||
@@ -4,7 +4,7 @@ import { withRouter } from "react-router";
|
||||
import { push } from "react-router-redux";
|
||||
import _ from "underscore";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { getParentPath } from "metabase/hoc/ModalRoute";
|
||||
import {
|
||||
getGroupTableAccessPolicy,
|
||||
@@ -71,10 +71,7 @@ const EditSandboxingModalContainer = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={isLoading}
|
||||
error={policyRequestState?.error}
|
||||
>
|
||||
<Loading loading={isLoading} error={policyRequestState?.error}>
|
||||
<EditSandboxingModal
|
||||
policy={policy}
|
||||
attributes={attributes}
|
||||
@@ -82,7 +79,7 @@ const EditSandboxingModalContainer = ({
|
||||
onCancel={close}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
BulkActionBar,
|
||||
BulkActionButton,
|
||||
} from "metabase/components/BulkActionBar";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import { DelayedLoading } from "metabase/components/Loading/DelayedLoading";
|
||||
import Link from "metabase/core/components/Link";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
import * as Urls from "metabase/lib/urls";
|
||||
@@ -86,7 +86,7 @@ export function UploadManagementTable() {
|
||||
);
|
||||
|
||||
if (isLoading || error) {
|
||||
return <DelayedLoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <DelayedLoading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
if (!uploadTables?.length) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { AuthTabs } from "metabase/admin/settings/components/AuthTabs";
|
||||
import SettingToggle from "metabase/admin/settings/components/widgets/SettingToggle";
|
||||
import type { SettingElement } from "metabase/admin/settings/types";
|
||||
import { useSetting } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useModal } from "metabase/hooks/use-modal";
|
||||
import {
|
||||
Box,
|
||||
@@ -134,10 +134,7 @@ export const UserProvisioning = ({
|
||||
<>
|
||||
<AuthTabs activeKey="user-provisioning" />
|
||||
|
||||
<LoadingAndErrorWrapper
|
||||
loading={maskedTokenRequest.isLoading}
|
||||
error={scimTokenError}
|
||||
>
|
||||
<Loading loading={maskedTokenRequest.isLoading} error={scimTokenError}>
|
||||
<Stack pl="md" spacing="2.5rem">
|
||||
<Box maw="35rem">
|
||||
<div>
|
||||
@@ -240,7 +237,7 @@ export const UserProvisioning = ({
|
||||
</div>
|
||||
</Box>
|
||||
</Stack>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
|
||||
<UserProvisioningFirstEnabledModal
|
||||
opened={firstEnabledModal.opened}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { Button, Flex, Modal, type ModalProps, Stack, Text } from "metabase/ui";
|
||||
import { useRegenerateScimTokenMutation } from "metabase-enterprise/api";
|
||||
|
||||
@@ -118,7 +118,7 @@ export const UserProvisioningRegenerateTokenModal = ({
|
||||
title={t`Copy and save the SCIM token`}
|
||||
>
|
||||
<Stack spacing="lg" mt="0.5rem">
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
error={regenerateTokenReq.error}
|
||||
loading={regenerateTokenReq.isLoading}
|
||||
>
|
||||
@@ -130,7 +130,7 @@ export const UserProvisioningRegenerateTokenModal = ({
|
||||
<ScimTextWarning>
|
||||
{t`Please copy the token and save it somewhere safe. For security reasons, we can't show the token to you again.`}
|
||||
</ScimTextWarning>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
<Flex justify="end">
|
||||
{!regenerateTokenReq.error ? (
|
||||
<Button variant="filled" onClick={onClose}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetLoginHistoryQuery } from "metabase/api";
|
||||
import { LoadingAndErrorWrapper } from "metabase/public/containers/PublicAction/PublicAction.styled";
|
||||
import { Loading } from "metabase/public/containers/PublicAction/PublicAction.styled";
|
||||
|
||||
import LoginHistoryList from "../../components/LoginHistory";
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function LoginHistoryApp() {
|
||||
const { data: loginHistory, error, isLoading } = useGetLoginHistoryQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <LoginHistoryList loginHistory={loginHistory} />;
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { FormikHelpers } from "formik";
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { useActionQuery } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
import { checkNotNull } from "metabase/lib/types";
|
||||
@@ -83,7 +83,7 @@ export const ActionExecuteModal = ({
|
||||
isLoadingAction || (isLoadingInitialValues && !hasPrefetchedValues);
|
||||
|
||||
if (error || isLoading) {
|
||||
return <LoadingAndErrorWrapper error={error} loading={isLoading} />;
|
||||
return <Loading error={error} loading={isLoading} />;
|
||||
}
|
||||
|
||||
const loadedAction = checkNotNull(action);
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useState } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import { jt, t } from "ttag";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import Alert from "metabase/core/components/Alert";
|
||||
import Button from "metabase/core/components/Button";
|
||||
@@ -102,7 +102,7 @@ const DeleteDatabaseModal = ({
|
||||
title={t`Delete the ${database.name} database?`}
|
||||
onClose={onClose}
|
||||
>
|
||||
<LoadingAndErrorWrapper loading={loading}>
|
||||
<Loading loading={loading}>
|
||||
<DeleteDatabaseModalRoot
|
||||
onSubmit={canDelete ? handleSubmit : undefined}
|
||||
>
|
||||
@@ -168,7 +168,7 @@ const DeleteDatabaseModal = ({
|
||||
</Button>
|
||||
</DeleteDatabaseModalFooter>
|
||||
</DeleteDatabaseModalRoot>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</ModalContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import Sidebar from "metabase/admin/databases/components/DatabaseEditApp/Sidebar
|
||||
import Breadcrumbs from "metabase/components/Breadcrumbs";
|
||||
import { GenericError } from "metabase/components/ErrorPages";
|
||||
import { LeaveConfirmationModal } from "metabase/components/LeaveConfirmationModal";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { DatabaseForm } from "metabase/databases/components/DatabaseForm";
|
||||
import title from "metabase/hoc/Title";
|
||||
@@ -164,10 +164,7 @@ function DatabaseEditApp(props: DatabaseEditAppProps) {
|
||||
<ErrorBoundary errorComponent={GenericError as ComponentType}>
|
||||
<div>
|
||||
<div className={CS.pt0}>
|
||||
<LoadingAndErrorWrapper
|
||||
loading={!database}
|
||||
error={initializeError}
|
||||
>
|
||||
<Loading loading={!database} error={initializeError}>
|
||||
<DatabaseEditContent>
|
||||
<DatabaseEditForm>
|
||||
<DatabaseForm
|
||||
@@ -180,7 +177,7 @@ function DatabaseEditApp(props: DatabaseEditAppProps) {
|
||||
</DatabaseEditForm>
|
||||
<div>{addingNewDatabase && <DatabaseEditHelp />}</div>
|
||||
</DatabaseEditContent>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</div>
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -60,7 +60,7 @@ export default _.compose(
|
||||
Database.loadList({
|
||||
reloadInterval: getReloadInterval,
|
||||
query,
|
||||
LoadingAndErrorWrapper: LoadingAndGenericErrorWrapper,
|
||||
Loading: LoadingAndGenericErrorWrapper,
|
||||
}),
|
||||
connect(mapStateToProps, mapDispatchToProps),
|
||||
)(DatabaseList);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Component } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import Breadcrumbs from "metabase/components/Breadcrumbs";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { assignUserColors } from "metabase/lib/formatting";
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class RevisionHistory extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={!segment || !revisions}>
|
||||
<Loading loading={!segment || !revisions}>
|
||||
{() => (
|
||||
<div className={CS.wrapper}>
|
||||
<Breadcrumbs
|
||||
@@ -64,7 +64,7 @@ export default class RevisionHistory extends Component {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import _ from "underscore";
|
||||
import { AdminLayout } from "metabase/components/AdminLayout";
|
||||
import Breadcrumbs from "metabase/components/Breadcrumbs";
|
||||
import { LeftNavPane, LeftNavPaneItem } from "metabase/components/LeftNavPane";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import Databases from "metabase/entities/databases";
|
||||
import Fields from "metabase/entities/fields";
|
||||
@@ -92,7 +92,7 @@ const MetadataFieldSettings = ({
|
||||
}: MetadataFieldSettingsProps) => {
|
||||
const schema = schemas.find(schema => schema.id === schemaId);
|
||||
if (!schema || (!fetched && loading)) {
|
||||
return <LoadingAndErrorWrapper loading />;
|
||||
return <Loading loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from "ttag";
|
||||
|
||||
import { useListApiKeysQuery } from "metabase/api";
|
||||
import AdminContentTable from "metabase/components/AdminContentTable";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { PaginationControls } from "metabase/components/PaginationControls";
|
||||
import Link from "metabase/core/components/Link";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
@@ -95,7 +95,7 @@ function GroupMembersTable({
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} />;
|
||||
return <Loading loading={isLoading} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@ import { useListApiKeysQuery } from "metabase/api";
|
||||
import AdminContentTable from "metabase/components/AdminContentTable";
|
||||
import { AdminPaneLayout } from "metabase/components/AdminPaneLayout";
|
||||
import Alert from "metabase/components/Alert";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import ModalWithTrigger from "metabase/components/ModalWithTrigger";
|
||||
import PopoverWithTrigger from "metabase/components/PopoverWithTrigger";
|
||||
@@ -278,7 +278,7 @@ function GroupsTable({
|
||||
const { isLoading, data: apiKeys } = useListApiKeysQuery();
|
||||
|
||||
if (isLoading) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} />;
|
||||
return <Loading loading={isLoading} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
import type { Params } from "react-router/lib/Router";
|
||||
|
||||
import { skipToken, useGetUserQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import Users from "metabase/entities/users";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
@@ -37,7 +37,7 @@ export const EditUserModal = ({ onClose, params }: EditUserModalProps) => {
|
||||
|
||||
return (
|
||||
<ModalContent title="Edit user" onClose={onClose}>
|
||||
<LoadingAndErrorWrapper loading={isLoading}>
|
||||
<Loading loading={isLoading}>
|
||||
{() => {
|
||||
return (
|
||||
<UserForm
|
||||
@@ -47,7 +47,7 @@ export const EditUserModal = ({ onClose, params }: EditUserModalProps) => {
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</ModalContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { c, t } from "ttag";
|
||||
|
||||
import { ModelCachingScheduleWidget } from "metabase/admin/settings/components/widgets/ModelCachingScheduleWidget/ModelCachingScheduleWidget";
|
||||
import { useSetting } from "metabase/common/hooks";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ExternalLink from "metabase/core/components/ExternalLink";
|
||||
import { useDispatch, useSelector } from "metabase/lib/redux";
|
||||
import MetabaseSettings from "metabase/lib/settings";
|
||||
@@ -161,10 +161,7 @@ export const ModelPersistenceConfiguration = () => {
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
error={null}
|
||||
loading={modelPersistenceEnabled === undefined}
|
||||
>
|
||||
<Loading loading={modelPersistenceEnabled === undefined}>
|
||||
<Switch
|
||||
mt="sm"
|
||||
label={
|
||||
@@ -175,7 +172,7 @@ export const ModelPersistenceConfiguration = () => {
|
||||
onChange={onSwitchChanged}
|
||||
checked={modelPersistenceEnabled}
|
||||
/>
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</Box>
|
||||
{modelPersistenceEnabled && (
|
||||
<div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import { findWhere } from "underscore";
|
||||
|
||||
import { UpsellCacheConfig } from "metabase/admin/upsells";
|
||||
import { useListDatabasesQuery } from "metabase/api";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { PLUGIN_CACHING } from "metabase/plugins";
|
||||
import { Flex, Stack } from "metabase/ui";
|
||||
import type { CacheableModel } from "metabase-types/api";
|
||||
@@ -53,8 +53,7 @@ const StrategyEditorForDatabases_Base = ({
|
||||
setConfigs,
|
||||
rootStrategyOverriddenOnce,
|
||||
rootStrategyRecentlyOverridden,
|
||||
error: configsError,
|
||||
loading: areConfigsLoading,
|
||||
...cacheConfigsResult
|
||||
} = useCacheConfigs({ configurableModels });
|
||||
|
||||
const databasesResult = useListDatabasesQuery();
|
||||
@@ -120,12 +119,6 @@ const StrategyEditorForDatabases_Base = ({
|
||||
"database",
|
||||
);
|
||||
|
||||
const error = configsError || databasesResult.error;
|
||||
const loading = areConfigsLoading || databasesResult.isLoading;
|
||||
if (error || loading) {
|
||||
return <DelayedLoadingAndErrorWrapper error={error} loading={loading} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TabWrapper role="region" aria-label={t`Data caching settings`}>
|
||||
<Stack spacing="xl" lh="1.5rem" maw="32rem" mb="1.5rem">
|
||||
@@ -135,36 +128,38 @@ const StrategyEditorForDatabases_Base = ({
|
||||
</aside>
|
||||
</Stack>
|
||||
{confirmationModal}
|
||||
<Flex gap="xl" style={{ overflow: "hidden" }}>
|
||||
<RoundedBox twoColumns={canOverrideRootStrategy}>
|
||||
{canOverrideRootStrategy && (
|
||||
<PLUGIN_CACHING.StrategyFormLauncherPanel
|
||||
configs={configs}
|
||||
setConfigs={setConfigs}
|
||||
targetId={targetId}
|
||||
updateTargetId={updateTargetId}
|
||||
databases={databases}
|
||||
isStrategyFormDirty={isStrategyFormDirty}
|
||||
shouldShowResetButton={shouldShowResetButton}
|
||||
/>
|
||||
)}
|
||||
<Panel hasLeftBorder={canOverrideRootStrategy}>
|
||||
{targetId !== null && (
|
||||
<StrategyForm
|
||||
<Loading delay result={[cacheConfigsResult, databasesResult]}>
|
||||
<Flex gap="xl" style={{ overflow: "hidden" }}>
|
||||
<RoundedBox twoColumns={canOverrideRootStrategy}>
|
||||
{canOverrideRootStrategy && (
|
||||
<PLUGIN_CACHING.StrategyFormLauncherPanel
|
||||
configs={configs}
|
||||
setConfigs={setConfigs}
|
||||
targetId={targetId}
|
||||
targetModel="database"
|
||||
targetName={targetDatabase?.name || t`Untitled database`}
|
||||
setIsDirty={setIsStrategyFormDirty}
|
||||
saveStrategy={saveStrategy}
|
||||
savedStrategy={savedStrategy}
|
||||
shouldAllowInvalidation={shouldAllowInvalidation}
|
||||
shouldShowName={targetId !== rootId}
|
||||
updateTargetId={updateTargetId}
|
||||
databases={databases}
|
||||
isStrategyFormDirty={isStrategyFormDirty}
|
||||
shouldShowResetButton={shouldShowResetButton}
|
||||
/>
|
||||
)}
|
||||
</Panel>
|
||||
</RoundedBox>
|
||||
<UpsellCacheConfig source="performance-data_cache" />
|
||||
</Flex>
|
||||
<Panel hasLeftBorder={canOverrideRootStrategy}>
|
||||
{targetId !== null && (
|
||||
<StrategyForm
|
||||
targetId={targetId}
|
||||
targetModel="database"
|
||||
targetName={targetDatabase?.name || t`Untitled database`}
|
||||
setIsDirty={setIsStrategyFormDirty}
|
||||
saveStrategy={saveStrategy}
|
||||
savedStrategy={savedStrategy}
|
||||
shouldAllowInvalidation={shouldAllowInvalidation}
|
||||
shouldShowName={targetId !== rootId}
|
||||
/>
|
||||
)}
|
||||
</Panel>
|
||||
</RoundedBox>
|
||||
<UpsellCacheConfig source="performance-data_cache" />
|
||||
</Flex>
|
||||
</Loading>
|
||||
</TabWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { c, t } from "ttag";
|
||||
import _ from "underscore";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { Schedule } from "metabase/components/Schedule/Schedule";
|
||||
import type { FormTextInputProps } from "metabase/forms";
|
||||
import {
|
||||
@@ -332,7 +332,7 @@ const ScheduleStrategyFormFields = () => {
|
||||
);
|
||||
if (!initialSchedule) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
error={t`Error: Cannot interpret schedule: ${scheduleInCronFormat}`}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { type Dispatch, type SetStateAction, useEffect, useState } from "react";
|
||||
import { useAsync } from "react-use";
|
||||
import _ from "underscore";
|
||||
|
||||
import type { LoadableResult } from "metabase/components/Loading/types";
|
||||
import { CacheConfigApi } from "metabase/services";
|
||||
import type {
|
||||
CacheConfig,
|
||||
@@ -14,13 +15,21 @@ import { translateConfigFromAPI } from "../utils";
|
||||
|
||||
import { useRecentlyTrue } from "./useRecentlyTrue";
|
||||
|
||||
type CacheConfigsData = LoadableResult & {
|
||||
configs: CacheConfig[];
|
||||
setConfigs: Dispatch<SetStateAction<CacheConfig[]>>;
|
||||
configsFromAPI?: CacheConfig[];
|
||||
rootStrategyOverriddenOnce: boolean;
|
||||
rootStrategyRecentlyOverridden: boolean;
|
||||
};
|
||||
|
||||
export const useCacheConfigs = ({
|
||||
configurableModels,
|
||||
id,
|
||||
}: {
|
||||
configurableModels: CacheableModel[];
|
||||
id?: number;
|
||||
}) => {
|
||||
}): CacheConfigsData => {
|
||||
const configsApiResult = useAsync(async () => {
|
||||
const configsForEachModel = await Promise.all(
|
||||
configurableModels.map(model =>
|
||||
@@ -53,7 +62,7 @@ export const useCacheConfigs = ({
|
||||
// has been loaded from the API _and_ has been copied into local state
|
||||
const [areConfigsInitialized, setAreConfigsInitialized] =
|
||||
useState<boolean>(false);
|
||||
const loading = configsApiResult.loading || !areConfigsInitialized;
|
||||
const isLoading = configsApiResult.loading || !areConfigsInitialized;
|
||||
|
||||
useEffect(() => {
|
||||
if (configsFromAPI) {
|
||||
@@ -64,7 +73,7 @@ export const useCacheConfigs = ({
|
||||
|
||||
return {
|
||||
error,
|
||||
loading,
|
||||
isLoading,
|
||||
configs,
|
||||
setConfigs,
|
||||
configsFromAPI,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
import { PermissionsEditorRoot } from "./PermissionsEditor.styled";
|
||||
import {
|
||||
@@ -17,9 +17,9 @@ export const permissionEditorPropTypes = {
|
||||
export const PermissionsEditor = ({ isLoading, error, ...contentProps }) => {
|
||||
return (
|
||||
<PermissionsEditorRoot>
|
||||
<LoadingAndErrorWrapper loading={isLoading} error={error} noWrapper>
|
||||
<Loading loading={isLoading} error={error}>
|
||||
<PermissionsEditorContent {...contentProps} />
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</PermissionsEditorRoot>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
import { SidebarRoot } from "./PermissionsSidebar.styled";
|
||||
import type { PermissionsSidebarContentProps } from "./PermissionsSidebarContent";
|
||||
@@ -16,9 +16,9 @@ export const PermissionsSidebar = ({
|
||||
}: PermissionsSidebarProps) => {
|
||||
return (
|
||||
<SidebarRoot>
|
||||
<LoadingAndErrorWrapper loading={isLoading} error={error} noWrapper>
|
||||
<Loading loading={isLoading} error={error}>
|
||||
<PermissionsSidebarContent {...contentProps} />
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</SidebarRoot>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from "ttag";
|
||||
import { useListApiKeysQuery } from "metabase/api";
|
||||
import { ClientSortableTable } from "metabase/common/components/Table";
|
||||
import { useLocale } from "metabase/common/hooks/use-locale/use-locale";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import { DelayedLoading } from "metabase/components/Loading/DelayedLoading";
|
||||
import { Ellipsified } from "metabase/core/components/Ellipsified";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { formatDateTimeWithUnit } from "metabase/lib/formatting/date";
|
||||
@@ -70,7 +70,7 @@ function ApiKeysTable({
|
||||
const locale = useLocale();
|
||||
|
||||
if (loading || error) {
|
||||
return <DelayedLoadingAndErrorWrapper loading={loading} error={error} />;
|
||||
return <DelayedLoading loading={loading} error={error} />;
|
||||
}
|
||||
|
||||
if (apiKeys?.length === 0 || !apiKeys || !flatApiKeys) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useGetCloudMigrationQuery,
|
||||
} from "metabase/api";
|
||||
import { useSetting } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
import { refreshSiteSettings } from "metabase/redux/settings";
|
||||
import { Box, Text } from "metabase/ui";
|
||||
@@ -87,7 +87,7 @@ export const CloudPanel = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={isLoading} error={error}>
|
||||
<Loading loading={isLoading} error={error}>
|
||||
<Box maw="36rem">
|
||||
<Text fw="bold" size="1.5rem" mb="2rem">{t`Migrate to Cloud`}</Text>
|
||||
|
||||
@@ -124,6 +124,6 @@ export const CloudPanel = ({
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Component, memo } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import Confirm from "metabase/components/Confirm";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Modal from "metabase/components/Modal";
|
||||
import { Ellipsified } from "metabase/core/components/Ellipsified";
|
||||
import Select, { Option } from "metabase/core/components/Select";
|
||||
@@ -424,7 +424,7 @@ const EditMap = ({
|
||||
)}
|
||||
>
|
||||
{geoJson || geoJsonLoading || geoJsonError ? (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
className={cx(CS.flex, CS.fullHeight, CS.fullWidth)}
|
||||
loading={geoJsonLoading}
|
||||
error={geoJsonError}
|
||||
@@ -434,7 +434,7 @@ const EditMap = ({
|
||||
<ChoroplethPreview geoJson={geoJson} />
|
||||
</div>
|
||||
)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
) : (
|
||||
<div
|
||||
className={cx(
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from "react-redux";
|
||||
import { t } from "ttag";
|
||||
|
||||
import Confirm from "metabase/components/Confirm";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ExternalLink from "metabase/core/components/ExternalLink";
|
||||
import Link from "metabase/core/components/Link";
|
||||
import AdminS from "metabase/css/admin.module.css";
|
||||
@@ -60,7 +60,7 @@ class PublicLinksListing extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={!list} error={error}>
|
||||
<Loading loading={!list} error={error}>
|
||||
{() => (
|
||||
<table
|
||||
data-testId={this.props["data-testId"]}
|
||||
@@ -121,7 +121,7 @@ class PublicLinksListing extends Component {
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { connect } from "react-redux";
|
||||
import { t } from "ttag";
|
||||
|
||||
import { UpsellHosting } from "metabase/admin/upsells";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { color } from "metabase/lib/colors";
|
||||
import { isSameOrSiteUrlOrigin } from "metabase/lib/dom";
|
||||
@@ -133,10 +133,7 @@ class SetupCheckList extends Component {
|
||||
<p
|
||||
className={CS.mt1}
|
||||
>{t`A few things you can do to get the most out of Metabase.`}</p>
|
||||
<LoadingAndErrorWrapper
|
||||
loading={!this.state.tasks}
|
||||
error={this.state.error}
|
||||
>
|
||||
<Loading loading={!this.state.tasks} error={this.state.error}>
|
||||
{() => (
|
||||
<div style={{ maxWidth: 468 }}>
|
||||
{nextTask && (
|
||||
@@ -150,7 +147,7 @@ class SetupCheckList extends Component {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</div>
|
||||
</SetupListRoot>
|
||||
<Box>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { css } from "@emotion/react";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { color } from "metabase/lib/colors";
|
||||
|
||||
const LOG_PREFIX = ".react-ansi-style";
|
||||
@@ -18,7 +18,7 @@ const LOG_COLORS = {
|
||||
cyan: "cyan",
|
||||
};
|
||||
|
||||
export const LogsContainer = styled(LoadingAndErrorWrapper)`
|
||||
export const LogsContainer = styled(Loading)`
|
||||
height: 100%;
|
||||
padding-left: 1rem;
|
||||
display: flex;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from "ttag";
|
||||
|
||||
import { useGetTasksInfoQuery } from "metabase/api";
|
||||
import AdminHeader from "metabase/components/AdminHeader";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Link from "metabase/core/components/Link";
|
||||
import AdminS from "metabase/css/admin.module.css";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
@@ -60,7 +60,7 @@ export const JobInfoApp = ({ children }) => {
|
||||
const { data, error, isFetching } = useGetTasksInfoQuery();
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={isFetching} error={error}>
|
||||
<Loading loading={isFetching} error={error}>
|
||||
<Box pl="md">
|
||||
<Flex align="center">
|
||||
<AdminHeader title={t`Scheduler Info`} />
|
||||
@@ -72,6 +72,6 @@ export const JobInfoApp = ({ children }) => {
|
||||
children
|
||||
}
|
||||
</Box>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { t } from "ttag";
|
||||
import _ from "underscore";
|
||||
|
||||
import { useGetTasksInfoQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import AdminS from "metabase/css/admin.module.css";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
@@ -64,9 +64,9 @@ export const JobTriggersModal = props => {
|
||||
title={t`Triggers for ${jobKey}`}
|
||||
onClose={() => dispatch(goBack())}
|
||||
>
|
||||
<LoadingAndErrorWrapper loading={isFetching} error={error}>
|
||||
<Loading loading={isFetching} error={error}>
|
||||
{() => renderTriggersTable(job?.triggers)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</ModalContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import { t } from "ttag";
|
||||
|
||||
import { useGetTaskQuery } from "metabase/api";
|
||||
import Code from "metabase/components/Code";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading/Loading";
|
||||
import ModalContent from "metabase/components/ModalContent";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
|
||||
@@ -16,7 +16,7 @@ export const TaskModal = ({ params }: TaskModalProps) => {
|
||||
const { data, isLoading, error } = useGetTaskQuery(params.taskId);
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
|
||||
@@ -6,7 +6,8 @@ import _ from "underscore";
|
||||
|
||||
import { useListDatabasesQuery, useListTasksQuery } from "metabase/api";
|
||||
import AdminHeader from "metabase/components/AdminHeader";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading/Loading";
|
||||
import { unready } from "metabase/components/Loading/utils";
|
||||
import { PaginationControls } from "metabase/components/PaginationControls";
|
||||
import Link from "metabase/core/components/Link";
|
||||
import AdminS from "metabase/css/admin.module.css";
|
||||
@@ -30,32 +31,19 @@ export const TasksApp = ({ children }: TasksAppProps) => {
|
||||
const [page, setPage] = useState(0);
|
||||
const pageSize = 50;
|
||||
|
||||
const {
|
||||
data: tasksData,
|
||||
isFetching: isLoadingTasks,
|
||||
error: tasksError,
|
||||
} = useListTasksQuery({
|
||||
const { data: tasksData, ...tasksResult } = useListTasksQuery({
|
||||
limit: pageSize,
|
||||
offset: page * pageSize,
|
||||
});
|
||||
|
||||
const {
|
||||
data: databasesData,
|
||||
isFetching: isLoadingDatabases,
|
||||
error: databasesError,
|
||||
} = useListDatabasesQuery();
|
||||
const databasesResult = useListDatabasesQuery();
|
||||
|
||||
if (unready([tasksResult, databasesResult])) {
|
||||
return <Loading result={[tasksResult, databasesResult]} />;
|
||||
}
|
||||
|
||||
const tasks = tasksData?.data;
|
||||
const databases = databasesData?.data;
|
||||
|
||||
if (isLoadingTasks || isLoadingDatabases || tasksError || databasesError) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={isLoadingTasks || isLoadingDatabases}
|
||||
error={tasksError || databasesError}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const databases = databasesResult.data?.data;
|
||||
|
||||
if (!tasks || !databases) {
|
||||
return null;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { t } from "ttag";
|
||||
|
||||
import NoResults from "assets/img/no_results.svg";
|
||||
import { useListDatabasesQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { color } from "metabase/lib/colors";
|
||||
import * as Urls from "metabase/lib/urls";
|
||||
@@ -26,11 +26,11 @@ export const BrowseDatabases = () => {
|
||||
const databases = data?.data;
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error />;
|
||||
return <Loading error />;
|
||||
}
|
||||
|
||||
if (!databases && isLoading) {
|
||||
return <LoadingAndErrorWrapper loading />;
|
||||
return <Loading loading />;
|
||||
}
|
||||
|
||||
if (!databases?.length) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { t } from "ttag";
|
||||
import NoResults from "assets/img/no_results.svg";
|
||||
import { useListRecentsQuery } from "metabase/api";
|
||||
import { useFetchModels } from "metabase/common/hooks/use-fetch-models";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import {
|
||||
PLUGIN_COLLECTIONS,
|
||||
PLUGIN_CONTENT_VERIFICATION,
|
||||
@@ -126,7 +126,7 @@ export const BrowseModels = () => {
|
||||
) : (
|
||||
<>
|
||||
<ModelExplanationBanner />
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
<Loading
|
||||
error={recentModelsResult.error}
|
||||
loading={
|
||||
// If the main models result is still pending, the list of recently viewed
|
||||
@@ -135,20 +135,18 @@ export const BrowseModels = () => {
|
||||
recentModelsResult.isLoading || modelsResult.isLoading
|
||||
}
|
||||
style={{ flex: 1 }}
|
||||
delay={0}
|
||||
loader={<RecentModels skeleton />}
|
||||
>
|
||||
<RecentModels models={recentModels} />
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
</Loading>
|
||||
<Loading
|
||||
error={modelsResult.error}
|
||||
loading={modelsResult.isLoading}
|
||||
style={{ flex: 1 }}
|
||||
delay={0}
|
||||
loader={<ModelsTable skeleton />}
|
||||
>
|
||||
<ModelsTable models={filteredModels} />
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
useCollectionQuery,
|
||||
useDatabaseListQuery,
|
||||
} from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Bookmark from "metabase/entities/bookmarks";
|
||||
import Databases from "metabase/entities/databases";
|
||||
import { useDispatch, useSelector } from "metabase/lib/redux";
|
||||
@@ -76,11 +76,11 @@ export function CollectionContent({
|
||||
bookmarksError || databasesError || collectionsError || collectionError;
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} />;
|
||||
return <Loading error={error} />;
|
||||
}
|
||||
|
||||
if (!bookmarks || !databases || !collections || !collection) {
|
||||
return <LoadingAndErrorWrapper loading />;
|
||||
return <Loading loading />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { t } from "ttag";
|
||||
|
||||
import type { OnMoveWithSourceAndDestination } from "metabase/collections/types";
|
||||
import { useCollectionQuery } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { MoveModal } from "metabase/containers/MoveModal";
|
||||
import Collections from "metabase/entities/collections";
|
||||
import { useDispatch } from "metabase/lib/redux";
|
||||
@@ -63,7 +63,7 @@ export const MoveCollectionModal = ({
|
||||
});
|
||||
|
||||
if (!collection || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useGetCollectionQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
import { CollectionContent } from "../CollectionContent";
|
||||
|
||||
@@ -7,8 +7,8 @@ export const TrashCollectionLanding = () => {
|
||||
const { data, isLoading, error } = useGetCollectionQuery({ id: "trash" });
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={isLoading} error={error} noWrapper>
|
||||
<Loading loading={isLoading} error={error}>
|
||||
{data && <CollectionContent collectionId={data.id} />}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useDeepCompareEffect } from "react-use";
|
||||
|
||||
import { isValidCollectionId } from "metabase/collections/utils";
|
||||
import { useCollectionQuery } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useSelector } from "metabase/lib/redux";
|
||||
import { getUserPersonalCollectionId } from "metabase/selectors/user";
|
||||
import type {
|
||||
@@ -180,7 +180,7 @@ export const CollectionPickerInner = (
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} />;
|
||||
return <Loading error={error} />;
|
||||
}
|
||||
|
||||
if (loadingCurrentCollection) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
useGetDashboardQuery,
|
||||
} from "metabase/api";
|
||||
import { isValidCollectionId } from "metabase/collections/utils";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useSelector } from "metabase/lib/redux";
|
||||
import { getUserPersonalCollectionId } from "metabase/selectors/user";
|
||||
import type {
|
||||
@@ -202,7 +202,7 @@ const DashboardPickerInner = (
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} />;
|
||||
return <Loading error={error} />;
|
||||
}
|
||||
|
||||
if (loadingCurrentCollection) {
|
||||
|
||||
@@ -95,6 +95,8 @@ export function EntityPickerModal<
|
||||
recentsContext = ["selections", "views"],
|
||||
}: EntityPickerModalProps<Model, Item>) {
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
|
||||
// FIXME: Does an error need to be handled here?
|
||||
const { data: recentItems, isLoading: isLoadingRecentItems } =
|
||||
useListRecentsQuery(
|
||||
{ context: recentsContext },
|
||||
|
||||
@@ -88,7 +88,7 @@ export const EntityPickerSearchResults = <
|
||||
selectedItem: Item | null;
|
||||
}) => {
|
||||
if (!searchResults) {
|
||||
return <DelayedLoadingSpinner text={t`Loading…`} />;
|
||||
return <DelayedLoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import { VirtualizedList } from "metabase/components/VirtualizedList";
|
||||
import { LoadingAndErrorWrapper } from "metabase/public/containers/PublicAction/PublicAction.styled";
|
||||
import { Loading } from "metabase/public/containers/PublicAction/PublicAction.styled";
|
||||
import { Box, Center, Icon, NavLink } from "metabase/ui";
|
||||
|
||||
import type { TypeWithModel } from "../../types";
|
||||
@@ -54,14 +54,14 @@ export const ItemList = <
|
||||
}, [filteredItems, selectedItem]);
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} />;
|
||||
return <Loading error={error} />;
|
||||
}
|
||||
|
||||
if (isLoading && !filteredItems) {
|
||||
return (
|
||||
<Box miw={310} h="100%" aria-label={t`Loading...`}>
|
||||
<Center p="lg" h="100%">
|
||||
<DelayedLoadingSpinner delay={300} />
|
||||
<DelayedLoadingSpinner />
|
||||
</Center>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useState } from "react";
|
||||
import { useMount } from "react-use";
|
||||
|
||||
import type { LoadingProps } from "metabase/components/Loading";
|
||||
import { DelayedLoading } from "metabase/components/Loading/DelayedLoading";
|
||||
import { Flex, Loader, Text } from "metabase/ui";
|
||||
|
||||
export const LoadingSpinner = ({ text }: { text?: string }) => (
|
||||
@@ -20,24 +19,9 @@ export const LoadingSpinner = ({ text }: { text?: string }) => (
|
||||
// will only appear if the component is still loading after a certain delay
|
||||
export const DelayedLoadingSpinner = ({
|
||||
text,
|
||||
delay = 100,
|
||||
...props
|
||||
}: {
|
||||
text?: string;
|
||||
delay?: number;
|
||||
}) => {
|
||||
const [show, setShow] = useState(false);
|
||||
|
||||
useMount(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setShow(true);
|
||||
}, delay);
|
||||
return () => clearTimeout(timeout);
|
||||
});
|
||||
|
||||
if (!show) {
|
||||
// make tests aware that things are loading
|
||||
return <span data-testid="loading-indicator" />;
|
||||
}
|
||||
|
||||
return <LoadingSpinner text={text} />;
|
||||
} & LoadingProps) => {
|
||||
return <DelayedLoading {...props} loader={<LoadingSpinner text={text} />} />;
|
||||
};
|
||||
|
||||
@@ -56,15 +56,16 @@ const useGetInitialCollection = (
|
||||
: "root"
|
||||
: undefined;
|
||||
|
||||
const { data: currentCollection, isLoading: isCollectionLoading } =
|
||||
const { data: currentCollection, ...collectionResult } =
|
||||
useGetCollectionQuery(collectionId ? { id: collectionId } : skipToken);
|
||||
|
||||
const { data: currentQuestion, isLoading: isQuestionLoading } =
|
||||
useGetCardQuery(cardId ? { id: cardId } : skipToken);
|
||||
const { data: currentQuestion, ...currentQuestionResult } = useGetCardQuery(
|
||||
cardId ? { id: cardId } : skipToken,
|
||||
);
|
||||
|
||||
const {
|
||||
data: currentQuestionCollection,
|
||||
isLoading: isCurrentQuestionCollectionLoading,
|
||||
...currentQuestionCollectionResult
|
||||
} = useGetCollectionQuery(
|
||||
currentQuestion
|
||||
? { id: currentQuestion.collection_id ?? "root" }
|
||||
@@ -74,10 +75,11 @@ const useGetInitialCollection = (
|
||||
return {
|
||||
currentQuestion: currentQuestion,
|
||||
currentCollection: currentQuestionCollection ?? currentCollection,
|
||||
isLoading:
|
||||
isCollectionLoading ||
|
||||
isQuestionLoading ||
|
||||
isCurrentQuestionCollectionLoading,
|
||||
results: [
|
||||
collectionResult,
|
||||
currentQuestionResult,
|
||||
currentQuestionCollectionResult,
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -97,7 +99,7 @@ export const QuestionPicker = ({
|
||||
}),
|
||||
);
|
||||
|
||||
const { currentCollection, currentQuestion, isLoading } =
|
||||
const { currentCollection, currentQuestion, results } =
|
||||
useGetInitialCollection(initialValue);
|
||||
|
||||
const userPersonalCollectionId = useSelector(getUserPersonalCollectionId);
|
||||
@@ -161,19 +163,17 @@ export const QuestionPicker = ({
|
||||
[currentCollection, userPersonalCollectionId],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return <DelayedLoadingSpinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<NestedItemPicker
|
||||
isFolder={(item: QuestionPickerItem) => isFolder(item, models)}
|
||||
options={options}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onItemSelect={handleItemSelect}
|
||||
path={path}
|
||||
listResolver={CollectionItemPickerResolver}
|
||||
shouldShowItem={shouldShowItem}
|
||||
/>
|
||||
<DelayedLoadingSpinner result={results}>
|
||||
<NestedItemPicker
|
||||
isFolder={(item: QuestionPickerItem) => isFolder(item, models)}
|
||||
options={options}
|
||||
onFolderSelect={onFolderSelect}
|
||||
onItemSelect={handleItemSelect}
|
||||
path={path}
|
||||
listResolver={CollectionItemPickerResolver}
|
||||
shouldShowItem={shouldShowItem}
|
||||
/>
|
||||
</DelayedLoadingSpinner>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockImplicitQueryAction } from "metabase-types/api/mocks";
|
||||
|
||||
import { useActionListQuery } from "./use-action-list-query";
|
||||
@@ -15,7 +15,7 @@ const TestComponent = () => {
|
||||
const { data = [], isLoading, error } = useActionListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockImplicitQueryAction } from "metabase-types/api/mocks";
|
||||
|
||||
import { useActionQuery } from "./use-action-query";
|
||||
@@ -19,7 +19,7 @@ const TestComponent = () => {
|
||||
} = useActionQuery({ id: IMPLICIT_ACTION.id });
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
within,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockBookmark } from "metabase-types/api/mocks";
|
||||
|
||||
import { useBookmarkListQuery } from "./use-bookmark-list-query";
|
||||
@@ -19,7 +19,7 @@ const TestComponent = () => {
|
||||
const { data = [], metadata, isLoading, error } = useBookmarkListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockCollection } from "metabase-types/api/mocks";
|
||||
|
||||
import { useCollectionQuery } from "./use-collection-query";
|
||||
@@ -21,7 +21,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{data?.name}</div>;
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockDashboard } from "metabase-types/api/mocks";
|
||||
|
||||
import { useDashboardQuery } from "./use-dashboard-query";
|
||||
@@ -20,7 +20,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{data?.name}</div>;
|
||||
|
||||
@@ -4,7 +4,8 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { unready } from "metabase/components/Loading/utils";
|
||||
import { createMockDatabase } from "metabase-types/api/mocks";
|
||||
|
||||
import { useDatabaseListQuery } from "./use-database-list-query";
|
||||
@@ -12,10 +13,10 @@ import { useDatabaseListQuery } from "./use-database-list-query";
|
||||
const TEST_DB = createMockDatabase();
|
||||
|
||||
const TestComponent = () => {
|
||||
const { data = [], isLoading, error } = useDatabaseListQuery();
|
||||
const { data = [], ...result } = useDatabaseListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
if (unready(result)) {
|
||||
return <Loading result={result} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockDatabase } from "metabase-types/api/mocks";
|
||||
|
||||
import { useDatabaseQuery } from "./use-database-query";
|
||||
@@ -21,7 +21,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error || !data) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{data.name}</div>;
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
waitFor,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Databases from "metabase/entities/databases";
|
||||
import Tables from "metabase/entities/tables";
|
||||
import { delay } from "metabase/lib/promise";
|
||||
@@ -48,13 +48,7 @@ const TestComponent = ({ testId }: { testId?: string }) => {
|
||||
const handleInvalidate = () => dispatch(Databases.actions.invalidateLists());
|
||||
|
||||
if (isLoading || error) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={isLoading}
|
||||
error={error}
|
||||
data-testid={testId}
|
||||
/>
|
||||
);
|
||||
return <Loading loading={isLoading} error={error} testId={testId} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -91,7 +85,7 @@ const TestInnerComponent = () => {
|
||||
const handleInvalidate = () => dispatch(Tables.actions.invalidateLists());
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -114,7 +108,9 @@ describe("useEntityListQuery", () => {
|
||||
it("should be initially loading", () => {
|
||||
setup();
|
||||
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
expect(screen.getAllByTestId("loading-indicator").length).toBeGreaterThan(
|
||||
0,
|
||||
);
|
||||
});
|
||||
|
||||
it("should initially load data only once the reload flag in a nested component tree", async () => {
|
||||
@@ -204,8 +200,12 @@ describe("useEntityListQuery", () => {
|
||||
expect(fetchMock.calls("path:/api/database")).toHaveLength(1);
|
||||
|
||||
expect(
|
||||
within(screen.getByTestId("test2")).getByTestId("loading-indicator"),
|
||||
).toBeInTheDocument();
|
||||
(
|
||||
await within(screen.getByTestId("test2")).findAllByTestId(
|
||||
"loading-indicator",
|
||||
)
|
||||
).length,
|
||||
).toBeGreaterThan(0);
|
||||
|
||||
await delay(100); // trigger fetch request to be resolved
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Databases from "metabase/entities/databases";
|
||||
import Tables from "metabase/entities/tables";
|
||||
import type Database from "metabase-lib/v1/metadata/Database";
|
||||
@@ -40,7 +40,7 @@ const TestComponent = () => {
|
||||
);
|
||||
|
||||
if (isLoading || error || !database) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -70,7 +70,7 @@ const TestInnerComponent = () => {
|
||||
);
|
||||
|
||||
if (isLoading || error || !table) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{table.name}</div>;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
within,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockCard } from "metabase-types/api/mocks";
|
||||
|
||||
import { useQuestionListQuery } from "./use-question-list-query";
|
||||
@@ -16,7 +16,7 @@ const TestComponent = () => {
|
||||
const { data = [], metadata, isLoading, error } = useQuestionListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockCard } from "metabase-types/api/mocks";
|
||||
|
||||
import { useQuestionQuery } from "./use-question-query";
|
||||
@@ -17,7 +17,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error || !data) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{data.displayName()}</div>;
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
within,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading/Loading";
|
||||
import { createMockRevision } from "metabase-types/api/mocks/revision";
|
||||
|
||||
import { useRevisionListQuery } from "./use-revision-list-query";
|
||||
@@ -16,7 +16,7 @@ function TestComponent() {
|
||||
const { data = [], metadata, isLoading, error } = useRevisionListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { checkNotNull } from "metabase/lib/types";
|
||||
import {
|
||||
createMockCollection,
|
||||
@@ -48,7 +48,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
within,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockTable } from "metabase-types/api/mocks";
|
||||
|
||||
import { useTableListQuery } from "./use-table-list-query";
|
||||
@@ -16,7 +16,7 @@ const TestComponent = () => {
|
||||
const { data = [], metadata, isLoading, error } = useTableListQuery();
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
screen,
|
||||
waitForLoaderToBeRemoved,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { createMockTable } from "metabase-types/api/mocks";
|
||||
|
||||
import { useTableQuery } from "./use-table-query";
|
||||
@@ -17,7 +17,7 @@ const TestComponent = () => {
|
||||
});
|
||||
|
||||
if (isLoading || error || !data) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return <div>{data.name}</div>;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
waitForLoaderToBeRemoved,
|
||||
within,
|
||||
} from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading/Loading";
|
||||
import { createMockUserListResult } from "metabase-types/api/mocks";
|
||||
|
||||
import { useUserListQuery } from "./use-user-list-query";
|
||||
@@ -32,7 +32,7 @@ function TestComponent({ getRecipients = false }: TestComponentProps) {
|
||||
});
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import Loading, { type LoadingProps } from "./Loading";
|
||||
|
||||
export const DelayedLoading = (props: LoadingProps) => {
|
||||
return (
|
||||
<Loading delay {...props}>
|
||||
{props.children}
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
158
frontend/src/metabase/components/Loading/Loading.tsx
Normal file
158
frontend/src/metabase/components/Loading/Loading.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import cx from "classnames";
|
||||
import type { CSSProperties, ReactNode } from "react";
|
||||
import { Children, useEffect, useState } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import LoadingSpinner from "metabase/components/LoadingSpinner";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { Box, Title } from "metabase/ui";
|
||||
|
||||
import type { CoreLoadingPropsVariant } from "./types";
|
||||
import { getErrorAndLoading, getErrorMessage } from "./utils";
|
||||
|
||||
type DelayProps =
|
||||
| {
|
||||
/**
|
||||
* Whether to wait before showing the loading indicator
|
||||
* @see https://metaboat.slack.com/archives/C02H619CJ8K/p1709558533499399
|
||||
*/
|
||||
delay?: true;
|
||||
delayLength?: number;
|
||||
}
|
||||
| { delay: false; delayLength: never };
|
||||
|
||||
export type LoadingProps = {
|
||||
/** Component that indicates that data is loading, for example a spinner */
|
||||
loader?: ReactNode;
|
||||
/** Component to show when the loading indicator is hidden. The default value
|
||||
* includes a data-testid attribute, to make tests aware that something is
|
||||
* loading */
|
||||
blankComponent?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
showSpinner?: boolean;
|
||||
renderError?: (error: any) => ReactNode;
|
||||
testId?: string;
|
||||
noWrapper?: boolean;
|
||||
noBackground?: boolean;
|
||||
children?: ReactNode | (() => ReactNode);
|
||||
} & DelayProps &
|
||||
CoreLoadingPropsVariant;
|
||||
|
||||
/** Show a loading indicator, error message, or - if both loading and error are falsy - the children */
|
||||
export const Loading = ({
|
||||
blankComponent = <span data-testid="loading-indicator" />,
|
||||
className,
|
||||
delay,
|
||||
delayLength = 300,
|
||||
loader,
|
||||
renderError,
|
||||
showSpinner = true,
|
||||
style,
|
||||
testId,
|
||||
noWrapper,
|
||||
noBackground,
|
||||
children,
|
||||
...props
|
||||
}: LoadingProps) => {
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
|
||||
// If there is no delay, show the wrapper immediately. Otherwise, apply a timeout
|
||||
const [isLoadingIndicatorShown, setIsLoadingIndicatorShown] = useState(
|
||||
!delay,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setIsLoadingIndicatorShown(true);
|
||||
}, delayLength);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [delayLength]);
|
||||
|
||||
const contentClassName = cx(
|
||||
CS.wrapper,
|
||||
CS.py4,
|
||||
CS.textBrand,
|
||||
CS.textCentered,
|
||||
CS.flexFull,
|
||||
CS.flex,
|
||||
CS.flexColumn,
|
||||
CS.layoutCentered,
|
||||
{ [CS.bgWhite]: !noBackground },
|
||||
);
|
||||
|
||||
const getChildren = (child = children): ReactNode => {
|
||||
if (Array.isArray(child)) {
|
||||
return child.map(getChildren);
|
||||
} else if (typeof child === "function") {
|
||||
return (child as () => ReactNode)();
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
};
|
||||
|
||||
const renderErrorMessage = () =>
|
||||
renderError ? (
|
||||
<Box py="2rem">{renderError(getErrorMessage(error))}</Box>
|
||||
) : (
|
||||
<div className={contentClassName}>
|
||||
<Title order={2} c={"var(--mb-color-text-light)"}>
|
||||
{getErrorMessage(error)}
|
||||
</Title>
|
||||
</div>
|
||||
);
|
||||
|
||||
// Handle error condition
|
||||
if (error) {
|
||||
return (
|
||||
<div className={className} style={style} data-testid={testId}>
|
||||
{renderErrorMessage()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle loading condition
|
||||
else if (loading) {
|
||||
if (isLoadingIndicatorShown) {
|
||||
return (
|
||||
<div className={className} style={style} data-testid={testId}>
|
||||
{loader ?? (
|
||||
<DefaultLoadingIndicator
|
||||
showSpinner={showSpinner}
|
||||
message={t`Loading…`}
|
||||
contentClassName={contentClassName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <>{blankComponent}</>;
|
||||
}
|
||||
}
|
||||
|
||||
// Happy path
|
||||
else {
|
||||
const theChildren = getChildren();
|
||||
return theChildren ? Children.only(theChildren) : null;
|
||||
}
|
||||
};
|
||||
|
||||
const DefaultLoadingIndicator = ({
|
||||
showSpinner,
|
||||
message,
|
||||
contentClassName,
|
||||
}: {
|
||||
showSpinner?: boolean;
|
||||
message: string;
|
||||
contentClassName?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className={contentClassName} data-testid="loading-indicator-wrapper">
|
||||
{showSpinner && <LoadingSpinner />}
|
||||
<h2 className={cx(CS.textNormal, CS.textLight, CS.mt1)}>{message}</h2>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line import/no-default-export -- deprecated usage
|
||||
export default Loading;
|
||||
@@ -0,0 +1,80 @@
|
||||
import { render, screen } from "__support__/ui";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
describe("Loading", () => {
|
||||
const Data = () => <div>Data</div>;
|
||||
const error = {
|
||||
type: 500,
|
||||
message: "Big error here folks",
|
||||
};
|
||||
describe("loading condition", () => {
|
||||
it("should display a loading indicator if given a true loading prop", () => {
|
||||
render(<Loading loading />);
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display a given component child if loading is false", () => {
|
||||
render(
|
||||
<Loading loading={false} error={null}>
|
||||
<Data />
|
||||
</Loading>,
|
||||
);
|
||||
expect(screen.getByText("Data")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display a given function child if loading is false", () => {
|
||||
render(
|
||||
<Loading loading={false} error={null}>
|
||||
{() => <Data />}
|
||||
</Loading>,
|
||||
);
|
||||
expect(screen.getByText("Data")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shouldn't fail if loaded with null children and no wrapper", () => {
|
||||
expect(() => render(<Loading loading={false} />)).not.toThrow();
|
||||
});
|
||||
|
||||
it("can receive a result object", () => {
|
||||
const result = { isLoading: true, error: null };
|
||||
render(
|
||||
<Loading result={result}>
|
||||
<Data />
|
||||
</Loading>,
|
||||
);
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Data")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("can receive a results array", () => {
|
||||
const results = [
|
||||
{ isLoading: true, error: null },
|
||||
{ isLoading: false, error },
|
||||
];
|
||||
render(
|
||||
<Loading result={results}>
|
||||
<Data />
|
||||
</Loading>,
|
||||
);
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("loading-indicator")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText("Data")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("error condition", () => {
|
||||
it("should display an error message if given an error object", () => {
|
||||
render(<Loading error={error} />);
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("both conditions", () => {
|
||||
it("should display an error message if given an error object and loading is true", () => {
|
||||
render(<Loading loading error={error} />);
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
2
frontend/src/metabase/components/Loading/index.jsx
Normal file
2
frontend/src/metabase/components/Loading/index.jsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default } from "./Loading";
|
||||
export { LoadingProps } from "./Loading";
|
||||
15
frontend/src/metabase/components/Loading/types.tsx
Normal file
15
frontend/src/metabase/components/Loading/types.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
export type CoreLoadingProps = {
|
||||
error?: any;
|
||||
loading?: boolean | boolean[];
|
||||
};
|
||||
|
||||
export type LoadableResult = {
|
||||
error?: any;
|
||||
isLoading?: boolean;
|
||||
};
|
||||
|
||||
export type CoreLoadingPropsVariant =
|
||||
| CoreLoadingProps
|
||||
| {
|
||||
result?: LoadableResult | LoadableResult[];
|
||||
};
|
||||
51
frontend/src/metabase/components/Loading/utils.tsx
Normal file
51
frontend/src/metabase/components/Loading/utils.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { t } from "ttag";
|
||||
|
||||
import type {
|
||||
CoreLoadingProps,
|
||||
CoreLoadingPropsVariant,
|
||||
LoadableResult,
|
||||
} from "./types";
|
||||
|
||||
/** Return the first truthy value in the input, or false if there are no truthy values */
|
||||
export const or = <T,>(
|
||||
/** The input can be either an array or a single value */
|
||||
arg: T | T[],
|
||||
) => (Array.isArray(arg) ? arg.find(Boolean) : arg) || false;
|
||||
|
||||
export const getErrorAndLoading = (
|
||||
props: CoreLoadingPropsVariant,
|
||||
): [any, any] => {
|
||||
let error: any = false;
|
||||
let loading = false;
|
||||
if ("result" in props) {
|
||||
const { result } = props;
|
||||
const results = Array.isArray(result) ? result : [result];
|
||||
error = or(results.map(r => r?.error));
|
||||
loading = or(results.map(r => r?.isLoading));
|
||||
} else {
|
||||
const coreProps = props as CoreLoadingProps;
|
||||
error = or(coreProps.error);
|
||||
loading = or(coreProps.loading);
|
||||
}
|
||||
return [error, loading];
|
||||
};
|
||||
|
||||
export const getErrorMessage = (error: any) => {
|
||||
// NOTE: Dashboard API endpoint returns the error as JSON with `message` field
|
||||
const message =
|
||||
error?.data?.message ||
|
||||
error?.data ||
|
||||
error?.statusText ||
|
||||
error?.message ||
|
||||
error;
|
||||
if (message && typeof message === "string") {
|
||||
return message;
|
||||
} else {
|
||||
return t`An error occurred`;
|
||||
}
|
||||
};
|
||||
|
||||
export const unready = (resultOrResults: LoadableResult | LoadableResult[]) => {
|
||||
const [error, loading] = getErrorAndLoading({ result: resultOrResults });
|
||||
return Boolean(error || loading);
|
||||
};
|
||||
87
frontend/src/metabase/components/Loading/utils.unit.spec.tsx
Normal file
87
frontend/src/metabase/components/Loading/utils.unit.spec.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { CoreLoadingProps } from "./types";
|
||||
import { getErrorAndLoading, or } from "./utils";
|
||||
|
||||
describe("getErrorAndLoading", () => {
|
||||
it("returns [false, false] by default", () => {
|
||||
const [error, loading] = getErrorAndLoading({});
|
||||
expect(error).toBe(false);
|
||||
expect(loading).toBe(false);
|
||||
});
|
||||
|
||||
it("extracts error and loading fields from props", () => {
|
||||
const props: CoreLoadingProps = { error: false, loading: true };
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
expect(error).toBe(false);
|
||||
expect(loading).toBe(true);
|
||||
});
|
||||
|
||||
it("handles loading array with values true and false", () => {
|
||||
const props: CoreLoadingProps = {
|
||||
error: false,
|
||||
loading: [true, false],
|
||||
};
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
expect(error).toBe(false);
|
||||
expect(loading).toBe(true);
|
||||
});
|
||||
|
||||
it("handles loading array with values false and false", () => {
|
||||
const props: CoreLoadingProps = {
|
||||
error: true,
|
||||
loading: [false, false],
|
||||
};
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
expect(error).toBe(true);
|
||||
expect(loading).toBe(false);
|
||||
});
|
||||
|
||||
it("handles error array with one truthy value", () => {
|
||||
const props: CoreLoadingProps = {
|
||||
error: [false, { message: "Error!" }],
|
||||
loading: [false, false],
|
||||
};
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
expect(error).toEqual({ message: "Error!" });
|
||||
expect(loading).toBe(false);
|
||||
});
|
||||
|
||||
it("handles error array with no truthy values", () => {
|
||||
const props: CoreLoadingProps = {
|
||||
error: [false, null, undefined],
|
||||
loading: [true, true],
|
||||
};
|
||||
const [error, loading] = getErrorAndLoading(props);
|
||||
expect(error).toEqual(false);
|
||||
expect(loading).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("or", () => {
|
||||
it("returns the single truthy value if the input is a non-array truthy value", () => {
|
||||
expect(or(42)).toBe(42);
|
||||
});
|
||||
|
||||
it("returns false if the input is a non-array falsy value", () => {
|
||||
expect(or(0)).toBe(false);
|
||||
});
|
||||
|
||||
it("returns the first truthy value from an array of mixed values", () => {
|
||||
expect(or([0, null, undefined, false, 5, true])).toBe(5);
|
||||
});
|
||||
|
||||
it("returns false if all elements in an array are falsy", () => {
|
||||
expect(or([0, null, undefined, false, ""])).toBe(false);
|
||||
});
|
||||
|
||||
it("works with an empty array", () => {
|
||||
expect(or([])).toBe(false);
|
||||
});
|
||||
|
||||
it("returns the truthy value from an array with one truthy item", () => {
|
||||
expect(or([true])).toBe(true);
|
||||
});
|
||||
|
||||
it("returns the single falsy value if the array has only one falsy item", () => {
|
||||
expect(or([0])).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import LoadingAndErrorWrapper from "./LoadingAndErrorWrapper";
|
||||
|
||||
export type LoadingAndErrorWrapperProps = {
|
||||
className?: string;
|
||||
error: any;
|
||||
loading: any;
|
||||
/** Component that indicates that data is loading, for example a spinner */
|
||||
loader?: ReactNode;
|
||||
noBackground?: boolean;
|
||||
noWrapper?: boolean;
|
||||
children?: ReactNode;
|
||||
style?: object;
|
||||
showSpinner?: boolean;
|
||||
loadingMessages?: string[];
|
||||
messageInterval?: number;
|
||||
loadingScenes?: string[];
|
||||
renderError?: (error: any) => ReactNode;
|
||||
"data-testid"?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A loading/error display component that waits a bit before appearing
|
||||
* @see https://metaboat.slack.com/archives/C02H619CJ8K/p1709558533499399
|
||||
*/
|
||||
export const DelayedLoadingAndErrorWrapper = ({
|
||||
error,
|
||||
loading,
|
||||
delay = 300,
|
||||
loader,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
delay?: number;
|
||||
} & LoadingAndErrorWrapperProps) => {
|
||||
// If delay is zero show the wrapper immediately. Otherwise, apply a timeout
|
||||
const [showWrapper, setShowWrapper] = useState(delay === 0);
|
||||
|
||||
props.loadingMessages ??= [];
|
||||
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => {
|
||||
setShowWrapper(true);
|
||||
}, delay);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [delay]);
|
||||
|
||||
// Handle error condition
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} {...props} />;
|
||||
}
|
||||
|
||||
// Handle loading condition
|
||||
if (loading) {
|
||||
if (!showWrapper) {
|
||||
// Don't show the wrapper yet, but make tests aware that things are loading
|
||||
return <span data-testid="loading-indicator" />;
|
||||
}
|
||||
if (loader) {
|
||||
return loader;
|
||||
}
|
||||
return (
|
||||
<LoadingAndErrorWrapper error={error} loading={loading} {...props}>
|
||||
{children}
|
||||
</LoadingAndErrorWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
// Happy path
|
||||
return <>{children}</>;
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
import { render, screen } from "__support__/ui";
|
||||
|
||||
import { DelayedLoadingAndErrorWrapper } from "./DelayedLoadingAndErrorWrapper";
|
||||
|
||||
describe("DelayedLoadingAndErrorWrapper", () => {
|
||||
describe("Loading", () => {
|
||||
it("should display a loading message if given a true loading prop", () => {
|
||||
render(<DelayedLoadingAndErrorWrapper error={false} loading={true} />);
|
||||
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
});
|
||||
it("should display a delayed loader if given a true loading prop", async () => {
|
||||
render(
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
loader={<div data-testid="loader"></div>}
|
||||
delay={100}
|
||||
error={false}
|
||||
loading={true}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
await screen.findByTestId("loader", undefined, { timeout: 150 });
|
||||
});
|
||||
|
||||
it("should display a given child if loading is false", () => {
|
||||
render(
|
||||
<DelayedLoadingAndErrorWrapper loading={false} error={null}>
|
||||
<div>Hey</div>
|
||||
</DelayedLoadingAndErrorWrapper>,
|
||||
);
|
||||
expect(screen.getByText("Hey")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shouldn't fail if loaded with null children and no wrapper", () => {
|
||||
expect(() =>
|
||||
render(
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
error={false}
|
||||
loading={false}
|
||||
noWrapper
|
||||
/>,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Errors", () => {
|
||||
it("should display an error message if given an error object and loading is true", () => {
|
||||
const error = {
|
||||
type: 500,
|
||||
message: "Big error here folks",
|
||||
};
|
||||
|
||||
render(<DelayedLoadingAndErrorWrapper loading={true} error={error} />);
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,166 +0,0 @@
|
||||
/* eslint "react/prop-types": "warn" */
|
||||
import cx from "classnames";
|
||||
import PropTypes from "prop-types";
|
||||
import { Children, Component } from "react";
|
||||
import { t } from "ttag";
|
||||
|
||||
import LoadingSpinner from "metabase/components/LoadingSpinner";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
|
||||
export default class LoadingAndErrorWrapper extends Component {
|
||||
state = {
|
||||
messageIndex: 0,
|
||||
sceneIndex: 0,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
error: PropTypes.any,
|
||||
loading: PropTypes.any,
|
||||
noBackground: PropTypes.bool,
|
||||
noWrapper: PropTypes.bool,
|
||||
children: PropTypes.any,
|
||||
style: PropTypes.object,
|
||||
showSpinner: PropTypes.bool,
|
||||
loadingMessages: PropTypes.array,
|
||||
messageInterval: PropTypes.number,
|
||||
loadingScenes: PropTypes.array,
|
||||
renderError: PropTypes.func,
|
||||
"data-testid": PropTypes.string,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
error: false,
|
||||
loading: false,
|
||||
noBackground: true,
|
||||
noWrapper: false,
|
||||
showSpinner: true,
|
||||
loadingMessages: [t`Loading...`],
|
||||
messageInterval: 6000,
|
||||
};
|
||||
|
||||
renderError(contentClassName) {
|
||||
if (this.props.renderError) {
|
||||
return (
|
||||
<div className={CS.py4}>
|
||||
{this.props.renderError(this.getErrorMessage())}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={contentClassName}>
|
||||
<h2 className={cx(CS.textNormal, CS.textLight, CS.ieWrapContentFix)}>
|
||||
{this.getErrorMessage()}
|
||||
</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getErrorMessage() {
|
||||
const { error } = this.props;
|
||||
let errorMessage =
|
||||
// NOTE Atte Keinänen 5/10/17 Dashboard API endpoint returns the error as JSON with `message` field
|
||||
(error.data && (error.data.message ? error.data.message : error.data)) ||
|
||||
error.statusText ||
|
||||
error.message ||
|
||||
error;
|
||||
|
||||
if (!errorMessage || typeof errorMessage !== "string") {
|
||||
errorMessage = t`An error occurred`;
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { loadingMessages, messageInterval } = this.props;
|
||||
// only start cycling if multiple messages are provided
|
||||
if (loadingMessages.length > 1) {
|
||||
this.cycle = setInterval(this.loadingInterval, messageInterval);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.cycle);
|
||||
}
|
||||
|
||||
loadingInterval = () => {
|
||||
if (this.props.loading) {
|
||||
this.cycleLoadingMessage();
|
||||
}
|
||||
};
|
||||
|
||||
getChildren(child = this.props.children) {
|
||||
if (Array.isArray(child)) {
|
||||
return child.map(this.getChildren);
|
||||
} else if (typeof child === "function") {
|
||||
return child();
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
cycleLoadingMessage = () => {
|
||||
this.setState({
|
||||
messageIndex:
|
||||
this.state.messageIndex + 1 < this.props.loadingMessages.length
|
||||
? this.state.messageIndex + 1
|
||||
: 0,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
loading,
|
||||
error,
|
||||
noBackground,
|
||||
noWrapper,
|
||||
showSpinner,
|
||||
loadingMessages,
|
||||
loadingScenes,
|
||||
style,
|
||||
className,
|
||||
"data-testid": testId,
|
||||
} = this.props;
|
||||
|
||||
const { messageIndex, sceneIndex } = this.state;
|
||||
|
||||
const contentClassName = cx(
|
||||
CS.wrapper,
|
||||
CS.py4,
|
||||
CS.textBrand,
|
||||
CS.textCentered,
|
||||
CS.flexFull,
|
||||
CS.flex,
|
||||
CS.flexColumn,
|
||||
CS.layoutCentered,
|
||||
{ [CS.bgWhite]: !noBackground },
|
||||
);
|
||||
|
||||
if (noWrapper && !error && !loading) {
|
||||
const children = this.getChildren();
|
||||
// special case for loading wrapper with null/undefined child
|
||||
if (children == null) {
|
||||
return null;
|
||||
}
|
||||
return Children.only(children);
|
||||
}
|
||||
return (
|
||||
<div className={className} style={style} data-testid={testId}>
|
||||
{error ? (
|
||||
this.renderError(contentClassName)
|
||||
) : loading ? (
|
||||
<div className={contentClassName}>
|
||||
{loadingScenes && loadingScenes[sceneIndex]}
|
||||
{!loadingScenes && showSpinner && <LoadingSpinner />}
|
||||
<h2 className={cx(CS.textNormal, CS.textLight, CS.mt1)}>
|
||||
{loadingMessages[messageIndex]}
|
||||
</h2>
|
||||
</div>
|
||||
) : (
|
||||
this.getChildren()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
import { act, render, screen } from "__support__/ui";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
|
||||
describe("LoadingAndErrorWrapper", () => {
|
||||
describe("Loading", () => {
|
||||
it("should display a loading message if given a true loading prop", () => {
|
||||
render(<LoadingAndErrorWrapper loading={true} />);
|
||||
|
||||
expect(screen.getByTestId("loading-indicator")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display a given child if loading is false", () => {
|
||||
const Child = () => <div>Hey</div>;
|
||||
|
||||
render(
|
||||
<LoadingAndErrorWrapper loading={false} error={null}>
|
||||
{() => <Child />}
|
||||
</LoadingAndErrorWrapper>,
|
||||
);
|
||||
expect(screen.getByText("Hey")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shouldn't fail if loaded with null children and no wrapper", () => {
|
||||
expect(() =>
|
||||
render(<LoadingAndErrorWrapper loading={false} noWrapper />),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it("should display a given scene during loading", () => {
|
||||
const Scene = () => <div>Fun load animation</div>;
|
||||
|
||||
render(
|
||||
<LoadingAndErrorWrapper
|
||||
loading={true}
|
||||
error={null}
|
||||
loadingScenes={[<Scene key="0" />]}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Fun load animation")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("cycling", () => {
|
||||
it("should cycle through loading messages if provided", () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const interval = 6000;
|
||||
|
||||
render(
|
||||
<LoadingAndErrorWrapper
|
||||
loading={true}
|
||||
error={null}
|
||||
loadingMessages={["One", "Two", "Three"]}
|
||||
messageInterval={interval}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText("One")).toBeInTheDocument();
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(interval);
|
||||
});
|
||||
|
||||
expect(screen.getByText("Two")).toBeInTheDocument();
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(interval);
|
||||
});
|
||||
|
||||
expect(screen.getByText("Three")).toBeInTheDocument();
|
||||
act(() => {
|
||||
jest.advanceTimersByTime(interval);
|
||||
});
|
||||
|
||||
expect(screen.getByText("One")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Errors", () => {
|
||||
it("should display an error message if given an error object", () => {
|
||||
const error = {
|
||||
type: 500,
|
||||
message: "Big error here folks",
|
||||
};
|
||||
|
||||
render(<LoadingAndErrorWrapper loading={true} error={error} />);
|
||||
expect(screen.getByText(error.message)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from "./LoadingAndErrorWrapper";
|
||||
@@ -1,9 +1,9 @@
|
||||
import { GenericError } from "metabase/components/ErrorPages";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
export default function LoadingAndGenericErrorWrapper(props) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
{...props}
|
||||
renderError={details => <GenericError details={details} />}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from "ttag";
|
||||
|
||||
import { DashboardPickerModal } from "metabase/common/components/DashboardPicker";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { ROOT_COLLECTION } from "metabase/entities/collections";
|
||||
import * as Urls from "metabase/lib/urls";
|
||||
import type { Card, Dashboard } from "metabase-types/api";
|
||||
@@ -60,7 +60,7 @@ export const AddToDashSelectDashModal = ({
|
||||
};
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
const questionCollection = card.collection ?? ROOT_COLLECTION;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useAsync } from "react-use";
|
||||
import { jt, t } from "ttag";
|
||||
|
||||
import { NotFound } from "metabase/components/ErrorPages";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import LogoIcon from "metabase/components/LogoIcon";
|
||||
import {
|
||||
CheckmarkIcon,
|
||||
@@ -67,7 +67,7 @@ export const UnsubscribePage = ({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<UnsubscribeRoot>
|
||||
<LoadingAndErrorWrapper loading={isLoading} />
|
||||
<Loading loading={isLoading} />
|
||||
</UnsubscribeRoot>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -117,13 +117,6 @@
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* Contents of elements inside flex items might not be wrapped correctly on IE11,
|
||||
set max-width manually to enforce wrapping
|
||||
*/
|
||||
.ieWrapContentFix {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.rowGap1 {
|
||||
row-gap: var(--gap-1);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Input from "metabase/core/components/Input";
|
||||
|
||||
export const QuestionListWrapper = styled(LoadingAndErrorWrapper)`
|
||||
export const QuestionListWrapper = styled(Loading)`
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
@@ -108,7 +108,6 @@ export const QuestionList = memo(function QuestionList({
|
||||
<QuestionListWrapper
|
||||
className={cx(CS.flex, CS.flexFull, CS.overflowAuto)}
|
||||
error={error}
|
||||
noBackground
|
||||
>
|
||||
<QuestionListContainer>
|
||||
{hasQuestionsToShow && (
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { css } from "@emotion/react";
|
||||
import styled from "@emotion/styled";
|
||||
import cx from "classnames";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import type { LoadingProps } from "metabase/components/Loading/Loading";
|
||||
import ColorS from "metabase/css/core/colors.module.css";
|
||||
import DashboardS from "metabase/css/dashboard.module.css";
|
||||
import { isEmbeddingSdk } from "metabase/env";
|
||||
@@ -12,15 +12,20 @@ import { FullWidthContainer } from "metabase/styled-components/layout/FullWidthC
|
||||
import { breakpointMaxSmall, space } from "metabase/styled-components/theme";
|
||||
import { SAVING_DOM_IMAGE_CLASS } from "metabase/visualizations/lib/save-chart-image";
|
||||
|
||||
export const DashboardLoadingAndErrorWrapper = styled(
|
||||
export const DashboardLoading = styled(
|
||||
({
|
||||
isFullHeight,
|
||||
isFullscreen,
|
||||
isNightMode,
|
||||
className,
|
||||
...props
|
||||
}: ComponentPropsWithoutRef<typeof LoadingAndErrorWrapper>) => {
|
||||
}: {
|
||||
isFullscreen: boolean;
|
||||
isNightMode: boolean;
|
||||
isFullHeight: boolean;
|
||||
} & LoadingProps) => {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
className={cx(className, DashboardS.Dashboard, {
|
||||
[DashboardS.DashboardFullscreen]: isFullscreen,
|
||||
[DashboardS.DashboardNight]: isNightMode,
|
||||
|
||||
@@ -53,7 +53,7 @@ import {
|
||||
CardsContainer,
|
||||
DashboardBody,
|
||||
DashboardHeaderContainer,
|
||||
DashboardLoadingAndErrorWrapper,
|
||||
DashboardLoading,
|
||||
DashboardStyled,
|
||||
ParametersAndCardsContainer,
|
||||
} from "./Dashboard.styled";
|
||||
@@ -386,7 +386,7 @@ function Dashboard(props: DashboardProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLoadingAndErrorWrapper
|
||||
<DashboardLoading
|
||||
isFullHeight={isEditing || isSharing}
|
||||
isFullscreen={isFullscreen}
|
||||
isNightMode={shouldRenderAsNightMode}
|
||||
@@ -504,7 +504,7 @@ function Dashboard(props: DashboardProps) {
|
||||
</DashboardStyled>
|
||||
);
|
||||
}}
|
||||
</DashboardLoadingAndErrorWrapper>
|
||||
</DashboardLoading>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,18 +2,17 @@
|
||||
|
||||
import cx from "classnames";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import DashboardS from "metabase/css/dashboard.module.css";
|
||||
import { DashboardGridConnected } from "metabase/dashboard/components/DashboardGrid";
|
||||
|
||||
export function Dashboard({ dashboard, className, style, ...props }) {
|
||||
return (
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
className={cx(DashboardS.Dashboard, CS.p1, CS.flexFull, className)}
|
||||
style={style}
|
||||
loading={!dashboard}
|
||||
noBackground
|
||||
>
|
||||
{() => (
|
||||
<DashboardGridConnected
|
||||
@@ -22,6 +21,6 @@ export function Dashboard({ dashboard, className, style, ...props }) {
|
||||
className={CS.spread}
|
||||
/>
|
||||
)}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import _ from "underscore";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import paginationState from "metabase/hoc/PaginationState";
|
||||
import { capitalize } from "metabase/lib/formatting";
|
||||
|
||||
@@ -21,7 +21,7 @@ const propTypes = {
|
||||
wrapped: PropTypes.bool,
|
||||
debounced: PropTypes.bool,
|
||||
loadingAndErrorWrapper: PropTypes.bool,
|
||||
LoadingAndErrorWrapper: PropTypes.elementType,
|
||||
Loading: PropTypes.elementType,
|
||||
keepListWhileLoading: PropTypes.bool,
|
||||
listName: PropTypes.string,
|
||||
selectorName: PropTypes.string,
|
||||
@@ -47,7 +47,7 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
loadingAndErrorWrapper: true,
|
||||
LoadingAndErrorWrapper: LoadingAndErrorWrapper,
|
||||
Loading: Loading,
|
||||
keepListWhileLoading: false,
|
||||
reload: false,
|
||||
wrapped: false,
|
||||
@@ -63,7 +63,7 @@ const CONSUMED_PROPS = [
|
||||
"wrapped",
|
||||
"debounced",
|
||||
"loadingAndErrorWrapper",
|
||||
"LoadingAndErrorWrapper",
|
||||
"Loading",
|
||||
"selectorName",
|
||||
];
|
||||
|
||||
@@ -210,22 +210,14 @@ class EntityListLoaderInner extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
allFetched,
|
||||
allError,
|
||||
loadingAndErrorWrapper,
|
||||
LoadingAndErrorWrapper,
|
||||
} = this.props;
|
||||
const { allFetched, allError, loadingAndErrorWrapper, Loading } =
|
||||
this.props;
|
||||
const { isReloading } = this.state;
|
||||
|
||||
return loadingAndErrorWrapper ? (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={!allFetched || isReloading}
|
||||
error={allError}
|
||||
noWrapper
|
||||
>
|
||||
<Loading loading={!allFetched || isReloading} error={allError}>
|
||||
{this.renderChildren}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
) : (
|
||||
this.renderChildren()
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Component } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import _ from "underscore";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
import entityType from "./EntityType";
|
||||
|
||||
@@ -18,7 +18,7 @@ const CONSUMED_PROPS = [
|
||||
"wrapped",
|
||||
"properties",
|
||||
"loadingAndErrorWrapper",
|
||||
"LoadingAndErrorWrapper",
|
||||
"Loading",
|
||||
"selectorName",
|
||||
"requestType",
|
||||
"fetchType",
|
||||
@@ -41,7 +41,7 @@ class EntityObjectLoaderInner extends Component {
|
||||
fetchType: "fetch",
|
||||
requestType: "fetch",
|
||||
loadingAndErrorWrapper: true,
|
||||
LoadingAndErrorWrapper: LoadingAndErrorWrapper,
|
||||
Loading: Loading,
|
||||
reload: false,
|
||||
wrapped: false,
|
||||
dispatchApiErrorEvent: true,
|
||||
@@ -115,22 +115,13 @@ class EntityObjectLoaderInner extends Component {
|
||||
});
|
||||
};
|
||||
render() {
|
||||
const {
|
||||
entityId,
|
||||
fetched,
|
||||
error,
|
||||
loadingAndErrorWrapper,
|
||||
LoadingAndErrorWrapper,
|
||||
} = this.props;
|
||||
const { entityId, fetched, error, loadingAndErrorWrapper, Loading } =
|
||||
this.props;
|
||||
|
||||
return loadingAndErrorWrapper ? (
|
||||
<LoadingAndErrorWrapper
|
||||
loading={!fetched && entityId != null}
|
||||
error={error}
|
||||
noWrapper
|
||||
>
|
||||
<Loading loading={!fetched && entityId != null} error={error}>
|
||||
{this.renderChildren}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
) : (
|
||||
this.renderChildren()
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useMemo } from "react";
|
||||
|
||||
import { useListPopularItemsQuery, useListRecentsQuery } from "metabase/api";
|
||||
import { useDatabaseListQuery, useSetting } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useSelector } from "metabase/lib/redux";
|
||||
import { isSyncCompleted } from "metabase/lib/syncing";
|
||||
import { getUser } from "metabase/selectors/user";
|
||||
@@ -35,11 +35,11 @@ export const HomeContent = (): JSX.Element | null => {
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return <LoadingAndErrorWrapper error={error} />;
|
||||
return <Loading error={error} />;
|
||||
}
|
||||
|
||||
if (!user || isLoading(user, databases, recentItems, popularItems)) {
|
||||
return <LoadingAndErrorWrapper loading />;
|
||||
return <Loading loading />;
|
||||
}
|
||||
|
||||
if (embeddingHomepage === "visible" && user.is_superuser) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useSearchListQuery,
|
||||
} from "metabase/common/hooks";
|
||||
import { useHomepageDashboard } from "metabase/common/hooks/use-homepage-dashboard";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { useDispatch, useSelector } from "metabase/lib/redux";
|
||||
import { canUseMetabotOnDatabase } from "metabase/metabot/utils";
|
||||
import { updateUserSetting } from "metabase/redux/settings";
|
||||
@@ -23,27 +23,17 @@ import { HomeLayout } from "../HomeLayout";
|
||||
const SEARCH_QUERY = { models: ["dataset" as const], limit: 1 };
|
||||
|
||||
export const HomePage = (): JSX.Element => {
|
||||
const {
|
||||
databases,
|
||||
models,
|
||||
isMetabotEnabled,
|
||||
isLoading: isLoadingMetabot,
|
||||
error,
|
||||
} = useMetabot();
|
||||
const { isLoadingDash } = useDashboardRedirect();
|
||||
|
||||
if ((isLoadingMetabot || error) && isMetabotEnabled) {
|
||||
return <LoadingAndErrorWrapper loading={isLoadingMetabot} error={error} />;
|
||||
}
|
||||
|
||||
if (isLoadingDash) {
|
||||
return <LoadingAndErrorWrapper loading={isLoadingDash} error={error} />;
|
||||
}
|
||||
|
||||
const { databases, models, isMetabotEnabled, ...metabotResult } =
|
||||
useMetabot();
|
||||
const dashboardResult = useDashboardRedirect();
|
||||
return (
|
||||
<HomeLayout hasMetabot={getHasMetabot(databases, models, isMetabotEnabled)}>
|
||||
<HomeContent />
|
||||
</HomeLayout>
|
||||
<Loading result={[metabotResult, dashboardResult]}>
|
||||
<HomeLayout
|
||||
hasMetabot={getHasMetabot(databases, models, isMetabotEnabled)}
|
||||
>
|
||||
<HomeContent />
|
||||
</HomeLayout>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -116,7 +106,5 @@ const useDashboardRedirect = () => {
|
||||
isLoading,
|
||||
]);
|
||||
|
||||
return {
|
||||
isLoadingDash: isLoading,
|
||||
};
|
||||
return { isLoading };
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { t } from "ttag";
|
||||
import _ from "underscore";
|
||||
|
||||
import { useListPopularItemsQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { getIcon } from "metabase/lib/icon";
|
||||
import { getName } from "metabase/lib/name";
|
||||
import * as Urls from "metabase/lib/urls";
|
||||
@@ -22,7 +22,7 @@ export const HomePopularSection = (): JSX.Element => {
|
||||
} = useListPopularItemsQuery(undefined, { refetchOnMountOrArgChange: true });
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { t } from "ttag";
|
||||
|
||||
import { useListRecentsQuery } from "metabase/api";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import { getIcon } from "metabase/lib/icon";
|
||||
import { getName } from "metabase/lib/name";
|
||||
import { useSelector } from "metabase/lib/redux";
|
||||
@@ -23,7 +23,7 @@ export const HomeRecentSection = () => {
|
||||
user != null && user.is_installer && isWithinWeeks(user.first_login, 2);
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,7 +5,7 @@ import _ from "underscore";
|
||||
|
||||
import { skipToken, useListDatabaseXraysQuery } from "metabase/api";
|
||||
import { useDatabaseListQuery } from "metabase/common/hooks";
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Select from "metabase/core/components/Select";
|
||||
import { useSelector } from "metabase/lib/redux";
|
||||
import { isSyncCompleted } from "metabase/lib/syncing";
|
||||
@@ -38,7 +38,7 @@ export const HomeXraySection = () => {
|
||||
const error = databaseListState.error ?? candidateListState.error;
|
||||
|
||||
if (isLoading || error) {
|
||||
return <LoadingAndErrorWrapper loading={isLoading} error={error} />;
|
||||
return <Loading loading={isLoading} error={error} />;
|
||||
}
|
||||
|
||||
if (!database) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { c, t } from "ttag";
|
||||
import { useUserSetting } from "metabase/common/hooks";
|
||||
import { useHasModels } from "metabase/common/hooks/use-has-models";
|
||||
import CollapseSection from "metabase/components/CollapseSection";
|
||||
import { DelayedLoadingAndErrorWrapper } from "metabase/components/LoadingAndErrorWrapper/DelayedLoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import { Flex, Skeleton } from "metabase/ui";
|
||||
|
||||
@@ -49,7 +49,7 @@ export const BrowseNavSection = ({
|
||||
headerClass={CS.mb1}
|
||||
onToggle={setExpandBrowse}
|
||||
>
|
||||
<DelayedLoadingAndErrorWrapper
|
||||
<Loading
|
||||
loading={areModelsLoading}
|
||||
error={modelsError}
|
||||
loader={
|
||||
@@ -58,7 +58,6 @@ export const BrowseNavSection = ({
|
||||
<Skeleton radius="xs" w="4rem" h="1.2rem" />
|
||||
</Flex>
|
||||
}
|
||||
delay={0}
|
||||
>
|
||||
{!noModelsExist && (
|
||||
<PaddedSidebarLink
|
||||
@@ -71,7 +70,7 @@ export const BrowseNavSection = ({
|
||||
{t`Models`}
|
||||
</PaddedSidebarLink>
|
||||
)}
|
||||
</DelayedLoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
{hasDataAccess && (
|
||||
<PaddedSidebarLink
|
||||
icon="database"
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { ChangeEventHandler } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { jt, t } from "ttag";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import Fields from "metabase/entities/fields";
|
||||
import Tables from "metabase/entities/tables";
|
||||
import { Box, Switch } from "metabase/ui";
|
||||
@@ -248,7 +248,7 @@ const LinkedFieldList = ({
|
||||
const { data, error, loading } = useFilterFields(parameter, otherParameter);
|
||||
|
||||
return (
|
||||
<LoadingAndErrorWrapper loading={loading} error={error}>
|
||||
<Loading loading={loading} error={error}>
|
||||
<FieldListRoot>
|
||||
{data && data.length > 0 && (
|
||||
<FieldListHeader>
|
||||
@@ -263,7 +263,7 @@ const LinkedFieldList = ({
|
||||
</FieldListItem>
|
||||
))}
|
||||
</FieldListRoot>
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
|
||||
export const ModalBody = styled.div`
|
||||
height: 50vh;
|
||||
`;
|
||||
|
||||
export const ModalLoadingAndErrorWrapper = styled(LoadingAndErrorWrapper)`
|
||||
export const ModalLoading = styled(Loading)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
@@ -41,7 +41,7 @@ import type { State } from "metabase-types/store";
|
||||
import type { FetchParameterValuesOpts } from "../../actions";
|
||||
import { fetchParameterValues } from "../../actions";
|
||||
|
||||
import { ModalLoadingAndErrorWrapper } from "./ValuesSourceModal.styled";
|
||||
import { ModalLoading } from "./ValuesSourceModal.styled";
|
||||
import S from "./ValuesSourceTypeModal.module.css";
|
||||
import {
|
||||
ModalBodyWithPane,
|
||||
@@ -565,11 +565,11 @@ export default _.compose(
|
||||
card_id ? getQuestionVirtualTableId(card_id) : undefined,
|
||||
fetchType: "fetchMetadataDeprecated",
|
||||
requestType: "fetchMetadataDeprecated",
|
||||
LoadingAndErrorWrapper: ModalLoadingAndErrorWrapper,
|
||||
Loading: ModalLoading,
|
||||
}),
|
||||
Questions.load({
|
||||
id: (state: State, { sourceConfig: { card_id } }: ModalOwnProps) => card_id,
|
||||
LoadingAndErrorWrapper: ModalLoadingAndErrorWrapper,
|
||||
Loading: ModalLoading,
|
||||
}),
|
||||
connect(null, mapDispatchToProps),
|
||||
)(ValuesSourceTypeModal);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { css } from "@emotion/react";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
import BaseLoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import BaseLoading from "metabase/components/Loading";
|
||||
import FormSubmitButton from "metabase/core/components/FormSubmitButton";
|
||||
import { breakpointMaxSmall } from "metabase/styled-components/theme";
|
||||
|
||||
export const LoadingAndErrorWrapper = styled(BaseLoadingAndErrorWrapper)`
|
||||
export const Loading = styled(BaseLoading)`
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
`;
|
||||
|
||||
@@ -10,10 +10,7 @@ import type { WritebackAction } from "metabase-types/api";
|
||||
import type { AppErrorDescriptor } from "metabase-types/store";
|
||||
|
||||
import PublicAction from "./PublicAction";
|
||||
import {
|
||||
ContentContainer,
|
||||
LoadingAndErrorWrapper,
|
||||
} from "./PublicAction.styled";
|
||||
import { ContentContainer, Loading } from "./PublicAction.styled";
|
||||
|
||||
interface OwnProps {
|
||||
params: { uuid: string };
|
||||
@@ -62,9 +59,7 @@ function PublicActionLoader({ params, setErrorPage }: Props) {
|
||||
|
||||
return (
|
||||
<SyncedEmbedFrame footerVariant="large">
|
||||
<LoadingAndErrorWrapper loading={!action}>
|
||||
{renderContent}
|
||||
</LoadingAndErrorWrapper>
|
||||
<Loading loading={!action}>{renderContent}</Loading>
|
||||
</SyncedEmbedFrame>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { assoc } from "icepick";
|
||||
import type { HandleThunkActionCreator } from "react-redux";
|
||||
import _ from "underscore";
|
||||
|
||||
import LoadingAndErrorWrapper from "metabase/components/LoadingAndErrorWrapper";
|
||||
import Loading from "metabase/components/Loading";
|
||||
import ColorS from "metabase/css/core/colors.module.css";
|
||||
import CS from "metabase/css/core/index.css";
|
||||
import DashboardS from "metabase/css/dashboard.module.css";
|
||||
@@ -161,7 +161,7 @@ export function PublicOrEmbeddedDashboardView({
|
||||
hide_parameters={hideParameters}
|
||||
downloadsEnabled={downloadsEnabled}
|
||||
>
|
||||
<LoadingAndErrorWrapper
|
||||
<Loading
|
||||
className={cx({
|
||||
[DashboardS.DashboardFullscreen]: isFullscreen,
|
||||
[DashboardS.DashboardNight]: isNightMode,
|
||||
@@ -204,7 +204,7 @@ export function PublicOrEmbeddedDashboardView({
|
||||
</DashboardContainer>
|
||||
);
|
||||
}}
|
||||
</LoadingAndErrorWrapper>
|
||||
</Loading>
|
||||
</EmbedFrame>
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user