Compare commits

...

7 Commits

Author SHA1 Message Date
Raphael Krut-Landau
4e66d33233 Fix some type issues 2024-08-23 22:31:15 -04:00
Raphael Krut-Landau
e1127aa018 Rename LoadingAndErrorWrapper -> Loading
This commit also refactors a few components to use the RTK query
pattern. I might move those changes to a downstream PR
2024-08-23 22:30:48 -04:00
Raphael Krut-Landau
7cf78c3ed6 Add RTK query result prop to loading component 2024-08-23 22:30:01 -04:00
Raphael Krut-Landau
2974b493c7 try to fix unit tests 2024-08-23 22:30:00 -04:00
Raphael Krut-Landau
9aa4ee0890 Fix lint issue 2024-08-23 22:30:00 -04:00
Raphael Krut-Landau
82e85d1129 Fix lint issue 2024-08-23 22:30:00 -04:00
Raphael Krut-Landau
b753373456 fix: Improve loading and error wrapper
* Remove the unused loadingScenes prop
* Support custom loading indicators
* Improve unit tests
2024-08-23 22:30:00 -04:00
137 changed files with 823 additions and 929 deletions

View File

@@ -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

View File

@@ -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 (

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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>
);
};

View File

@@ -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"

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -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}

View File

@@ -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}>

View File

@@ -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} />;

View File

@@ -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);

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -60,7 +60,7 @@ export default _.compose(
Database.loadList({
reloadInterval: getReloadInterval,
query,
LoadingAndErrorWrapper: LoadingAndGenericErrorWrapper,
Loading: LoadingAndGenericErrorWrapper,
}),
connect(mapStateToProps, mapDispatchToProps),
)(DatabaseList);

View File

@@ -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>
);
}
}

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>
);
};

View File

@@ -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>

View File

@@ -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>
);
};

View File

@@ -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}`}
/>
);

View File

@@ -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,

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -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>
);
};

View File

@@ -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(

View File

@@ -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>
);
}
}

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 },

View File

@@ -88,7 +88,7 @@ export const EntityPickerSearchResults = <
selectedItem: Item | null;
}) => {
if (!searchResults) {
return <DelayedLoadingSpinner text={t`Loading…`} />;
return <DelayedLoadingSpinner />;
}
return (

View File

@@ -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>
);

View File

@@ -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} />} />;
};

View File

@@ -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>
);
};

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>;

View File

@@ -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>;

View File

@@ -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 (

View File

@@ -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>;

View File

@@ -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

View File

@@ -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>;

View File

@@ -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 (

View File

@@ -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>;

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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>;

View File

@@ -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 (

View File

@@ -0,0 +1,9 @@
import Loading, { type LoadingProps } from "./Loading";
export const DelayedLoading = (props: LoadingProps) => {
return (
<Loading delay {...props}>
{props.children}
</Loading>
);
};

View 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;

View File

@@ -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();
});
});
});

View File

@@ -0,0 +1,2 @@
export { default } from "./Loading";
export { LoadingProps } from "./Loading";

View 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[];
};

View 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);
};

View 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);
});
});

View File

@@ -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}</>;
};

View File

@@ -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();
});
});
});

View File

@@ -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>
);
}
}

View File

@@ -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();
});
});
});

View File

@@ -1 +0,0 @@
export { default } from "./LoadingAndErrorWrapper";

View File

@@ -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} />}
/>

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -108,7 +108,6 @@ export const QuestionList = memo(function QuestionList({
<QuestionListWrapper
className={cx(CS.flex, CS.flexFull, CS.overflowAuto)}
error={error}
noBackground
>
<QuestionListContainer>
{hasQuestionsToShow && (

View File

@@ -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,

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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()
);

View File

@@ -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()
);

View File

@@ -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) {

View File

@@ -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 };
};

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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) {

View File

@@ -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"

View File

@@ -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>
);
};

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
`;

View File

@@ -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>
);
}

View File

@@ -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