mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-02 20:35:25 +00:00
feat(ui): store user preferences to localStorage (#21128)
* feat(ui): store user preferences to localStorage * fix domain spec * fix sidebar causing unwanted failures * fix playwright adjust expandable cards * fix empty condition for cards * update condition (cherry picked from commit 3490a06ca2569bbf01f8c65948a21b7a0e59c0c8)
This commit is contained in:
parent
bb8673c0a7
commit
064d91b7eb
@ -513,6 +513,10 @@ test.describe('Domains', () => {
|
|||||||
await domain.create(apiContext);
|
await domain.create(apiContext);
|
||||||
await page.reload();
|
await page.reload();
|
||||||
await sidebarClick(page, SidebarItem.DOMAIN);
|
await sidebarClick(page, SidebarItem.DOMAIN);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForSelector(`[data-testid="loader"]`, {
|
||||||
|
state: 'hidden',
|
||||||
|
});
|
||||||
await selectDomain(page, domain.data);
|
await selectDomain(page, domain.data);
|
||||||
|
|
||||||
await addTagsAndGlossaryToDomain(page, {
|
await addTagsAndGlossaryToDomain(page, {
|
||||||
@ -522,6 +526,10 @@ test.describe('Domains', () => {
|
|||||||
|
|
||||||
await redirectToHomePage(page);
|
await redirectToHomePage(page);
|
||||||
await sidebarClick(page, SidebarItem.DOMAIN);
|
await sidebarClick(page, SidebarItem.DOMAIN);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await page.waitForSelector(`[data-testid="loader"]`, {
|
||||||
|
state: 'hidden',
|
||||||
|
});
|
||||||
await selectDomain(page, domain.data);
|
await selectDomain(page, domain.data);
|
||||||
|
|
||||||
await page.waitForLoadState('networkidle');
|
await page.waitForLoadState('networkidle');
|
||||||
|
@ -625,7 +625,13 @@ export const addTagsAndGlossaryToDomain = async (
|
|||||||
const input = page.locator(`${container} #tagsForm_tags`);
|
const input = page.locator(`${container} #tagsForm_tags`);
|
||||||
await input.click();
|
await input.click();
|
||||||
await input.fill(value);
|
await input.fill(value);
|
||||||
await page.getByTestId(`tag-${value}`).click();
|
const tag = page.getByTestId(`tag-${value}`);
|
||||||
|
if (containerType === 'glossary') {
|
||||||
|
// To avoid clicking on white space between checkbox and text
|
||||||
|
await tag.locator('.ant-select-tree-checkbox').click();
|
||||||
|
} else {
|
||||||
|
await tag.click();
|
||||||
|
}
|
||||||
|
|
||||||
// Save and wait for response
|
// Save and wait for response
|
||||||
const updateResponse = page.waitForResponse(
|
const updateResponse = page.waitForResponse(
|
||||||
|
@ -45,5 +45,9 @@ export const loginAsAdmin = async (page: Page, admin: AdminClass) => {
|
|||||||
await admin.logout(page);
|
await admin.logout(page);
|
||||||
await page.waitForURL('**/signin');
|
await page.waitForURL('**/signin');
|
||||||
await admin.login(page);
|
await admin.login(page);
|
||||||
|
|
||||||
|
// Close the leftside bar to run tests smoothly
|
||||||
|
await page.getByTestId('sidebar-toggle').click();
|
||||||
|
|
||||||
await page.waitForURL('**/my-data');
|
await page.waitForURL('**/my-data');
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
*/
|
*/
|
||||||
import { Layout } from 'antd';
|
import { Layout } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { useLimitStore } from '../../context/LimitsProvider/useLimitsStore';
|
import { useLimitStore } from '../../context/LimitsProvider/useLimitsStore';
|
||||||
import { LineageSettings } from '../../generated/configuration/lineageSettings';
|
import { LineageSettings } from '../../generated/configuration/lineageSettings';
|
||||||
import { SettingType } from '../../generated/settings/settings';
|
import { SettingType } from '../../generated/settings/settings';
|
||||||
@ -36,7 +36,6 @@ const AppContainer = () => {
|
|||||||
const AuthenticatedRouter = applicationRoutesClass.getRouteElements();
|
const AuthenticatedRouter = applicationRoutesClass.getRouteElements();
|
||||||
const ApplicationExtras = applicationsClassBase.getApplicationExtension();
|
const ApplicationExtras = applicationsClassBase.getApplicationExtension();
|
||||||
const { isAuthenticated } = useApplicationStore();
|
const { isAuthenticated } = useApplicationStore();
|
||||||
const [isSidebarCollapsed, setIsSidebarCollapsed] = useState<boolean>(true);
|
|
||||||
|
|
||||||
const { setConfig, bannerDetails } = useLimitStore();
|
const { setConfig, bannerDetails } = useLimitStore();
|
||||||
|
|
||||||
@ -71,16 +70,13 @@ const AppContainer = () => {
|
|||||||
['extra-banner']: Boolean(bannerDetails),
|
['extra-banner']: Boolean(bannerDetails),
|
||||||
})}>
|
})}>
|
||||||
{/* Render left side navigation */}
|
{/* Render left side navigation */}
|
||||||
<LeftSidebar isSidebarCollapsed={isSidebarCollapsed} />
|
<LeftSidebar />
|
||||||
|
|
||||||
{/* Render main content */}
|
{/* Render main content */}
|
||||||
<Layout>
|
<Layout>
|
||||||
{/* Render Appbar */}
|
{/* Render Appbar */}
|
||||||
{isProtectedRoute(location.pathname) && isAuthenticated ? (
|
{isProtectedRoute(location.pathname) && isAuthenticated ? (
|
||||||
<NavBar
|
<NavBar />
|
||||||
isSidebarCollapsed={isSidebarCollapsed}
|
|
||||||
toggleSideBar={() => setIsSidebarCollapsed(!isSidebarCollapsed)}
|
|
||||||
/>
|
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{/* Render main content */}
|
{/* Render main content */}
|
||||||
|
@ -182,7 +182,8 @@ export const DomainLabelV2 = <
|
|||||||
{selectableList}
|
{selectableList}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}}>
|
}}
|
||||||
|
isExpandDisabled={!Array.isArray(domainLink)}>
|
||||||
<div className="d-flex items-center gap-1 flex-wrap">
|
<div className="d-flex items-center gap-1 flex-wrap">
|
||||||
{domainLink}
|
{domainLink}
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||||
import { TabSpecificField } from '../../../enums/entity.enum';
|
import { TabSpecificField } from '../../../enums/entity.enum';
|
||||||
@ -76,7 +77,8 @@ export const OwnerLabelV2 = <
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId={dataTestId}>
|
dataTestId={dataTestId}
|
||||||
|
isExpandDisabled={isEmpty(data.owners)}>
|
||||||
{getOwnerVersionLabel(
|
{getOwnerVersionLabel(
|
||||||
data,
|
data,
|
||||||
isVersionView ?? false,
|
isVersionView ?? false,
|
||||||
|
@ -100,7 +100,8 @@ export const ReviewerLabelV2 = <
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId="glossary-reviewer">
|
dataTestId="glossary-reviewer"
|
||||||
|
isExpandDisabled={!hasReviewers}>
|
||||||
<div data-testid="glossary-reviewer-name">
|
<div data-testid="glossary-reviewer-name">
|
||||||
{getOwnerVersionLabel(
|
{getOwnerVersionLabel(
|
||||||
data,
|
data,
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { cloneDeep, includes, isEqual } from 'lodash';
|
import { cloneDeep, includes, isEmpty, isEqual } from 'lodash';
|
||||||
import { default as React, useMemo } from 'react';
|
import { default as React, useMemo } from 'react';
|
||||||
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
|
||||||
import { TabSpecificField } from '../../../enums/entity.enum';
|
import { TabSpecificField } from '../../../enums/entity.enum';
|
||||||
@ -100,7 +100,7 @@ export const DomainExpertWidget = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{editOwnerPermission && domain.experts && domain.experts.length === 0 && (
|
{editOwnerPermission && domain.experts?.length === 0 && (
|
||||||
<UserSelectableList
|
<UserSelectableList
|
||||||
hasPermission={editOwnerPermission}
|
hasPermission={editOwnerPermission}
|
||||||
popoverProps={{ placement: 'topLeft' }}
|
popoverProps={{ placement: 'topLeft' }}
|
||||||
@ -123,7 +123,8 @@ export const DomainExpertWidget = () => {
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId="domain-expert-name">
|
dataTestId="domain-expert-name"
|
||||||
|
isExpandDisabled={isEmpty(domain.experts)}>
|
||||||
{content}
|
{content}
|
||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
);
|
);
|
||||||
|
@ -147,7 +147,8 @@ const GlossaryTermReferences = () => {
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId="references-container">
|
dataTestId="references-container"
|
||||||
|
isExpandDisabled={isEmpty(references)}>
|
||||||
{isVersionView ? (
|
{isVersionView ? (
|
||||||
getVersionReferenceElements()
|
getVersionReferenceElements()
|
||||||
) : (
|
) : (
|
||||||
|
@ -193,7 +193,8 @@ const GlossaryTermSynonyms = () => {
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId="synonyms-container">
|
dataTestId="synonyms-container"
|
||||||
|
isExpandDisabled={isEmpty(synonyms)}>
|
||||||
{isViewMode ? (
|
{isViewMode ? (
|
||||||
getSynonymsContainer()
|
getSynonymsContainer()
|
||||||
) : (
|
) : (
|
||||||
|
@ -250,7 +250,8 @@ const RelatedTerms = () => {
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId="related-term-container">
|
dataTestId="related-term-container"
|
||||||
|
isExpandDisabled={selectedOption.length === 0}>
|
||||||
{isIconVisible ? (
|
{isIconVisible ? (
|
||||||
relatedTermsContainer
|
relatedTermsContainer
|
||||||
) : (
|
) : (
|
||||||
|
@ -219,7 +219,8 @@ const RelatedMetrics: FC<RelatedMetricsProps> = ({
|
|||||||
<ExpandableCard
|
<ExpandableCard
|
||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}>
|
}}
|
||||||
|
isExpandDisabled={isEmpty(relatedMetrics)}>
|
||||||
<Row gutter={[0, 8]}>{content}</Row>
|
<Row gutter={[0, 8]}>{content}</Row>
|
||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
);
|
);
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
SIDEBAR_NESTED_KEYS,
|
SIDEBAR_NESTED_KEYS,
|
||||||
} from '../../../constants/LeftSidebar.constants';
|
} from '../../../constants/LeftSidebar.constants';
|
||||||
import { SidebarItem } from '../../../enums/sidebar.enum';
|
import { SidebarItem } from '../../../enums/sidebar.enum';
|
||||||
|
import { useCurrentUserPreferences } from '../../../hooks/currentUserStore/useCurrentUserStore';
|
||||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||||
import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation';
|
import useCustomLocation from '../../../hooks/useCustomLocation/useCustomLocation';
|
||||||
import { useCustomPages } from '../../../hooks/useCustomPages';
|
import { useCustomPages } from '../../../hooks/useCustomPages';
|
||||||
@ -32,18 +33,16 @@ import BrandImage from '../../common/BrandImage/BrandImage';
|
|||||||
import './left-sidebar.less';
|
import './left-sidebar.less';
|
||||||
import { LeftSidebarItem as LeftSidebarItemType } from './LeftSidebar.interface';
|
import { LeftSidebarItem as LeftSidebarItemType } from './LeftSidebar.interface';
|
||||||
import LeftSidebarItem from './LeftSidebarItem.component';
|
import LeftSidebarItem from './LeftSidebarItem.component';
|
||||||
|
|
||||||
const { Sider } = Layout;
|
const { Sider } = Layout;
|
||||||
|
|
||||||
const LeftSidebar = ({
|
const LeftSidebar = () => {
|
||||||
isSidebarCollapsed,
|
|
||||||
}: {
|
|
||||||
isSidebarCollapsed: boolean;
|
|
||||||
}) => {
|
|
||||||
const location = useCustomLocation();
|
const location = useCustomLocation();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { onLogoutHandler } = useApplicationStore();
|
const { onLogoutHandler } = useApplicationStore();
|
||||||
const [showConfirmLogoutModal, setShowConfirmLogoutModal] = useState(false);
|
const [showConfirmLogoutModal, setShowConfirmLogoutModal] = useState(false);
|
||||||
|
const {
|
||||||
|
preferences: { isSidebarCollapsed },
|
||||||
|
} = useCurrentUserPreferences();
|
||||||
|
|
||||||
const { i18n } = useTranslation();
|
const { i18n } = useTranslation();
|
||||||
const isDirectionRTL = useMemo(() => i18n.dir() === 'rtl', [i18n]);
|
const isDirectionRTL = useMemo(() => i18n.dir() === 'rtl', [i18n]);
|
||||||
|
@ -19,7 +19,7 @@ describe('LeftSidebar', () => {
|
|||||||
it('renders sidebar links correctly', () => {
|
it('renders sidebar links correctly', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<LeftSidebar isSidebarCollapsed={false} />
|
<LeftSidebar />
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -57,6 +57,7 @@ import { useWebSocketConnector } from '../../context/WebSocketProvider/WebSocket
|
|||||||
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||||
import { EntityReference } from '../../generated/entity/type';
|
import { EntityReference } from '../../generated/entity/type';
|
||||||
import { BackgroundJob, JobType } from '../../generated/jobs/backgroundJob';
|
import { BackgroundJob, JobType } from '../../generated/jobs/backgroundJob';
|
||||||
|
import { useCurrentUserPreferences } from '../../hooks/currentUserStore/useCurrentUserStore';
|
||||||
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
import useCustomLocation from '../../hooks/useCustomLocation/useCustomLocation';
|
||||||
import { useDomainStore } from '../../hooks/useDomainStore';
|
import { useDomainStore } from '../../hooks/useDomainStore';
|
||||||
import { getVersion } from '../../rest/miscAPI';
|
import { getVersion } from '../../rest/miscAPI';
|
||||||
@ -96,13 +97,7 @@ import popupAlertsCardsClassBase from './PopupAlertClassBase';
|
|||||||
|
|
||||||
const cookieStorage = new CookieStorage();
|
const cookieStorage = new CookieStorage();
|
||||||
|
|
||||||
const NavBar = ({
|
const NavBar = () => {
|
||||||
isSidebarCollapsed = true,
|
|
||||||
toggleSideBar,
|
|
||||||
}: {
|
|
||||||
isSidebarCollapsed?: boolean;
|
|
||||||
toggleSideBar?: () => void;
|
|
||||||
}) => {
|
|
||||||
const { isTourOpen: isTourRoute } = useTourProvider();
|
const { isTourOpen: isTourRoute } = useTourProvider();
|
||||||
const { onUpdateCSVExportJob } = useEntityExportModalProvider();
|
const { onUpdateCSVExportJob } = useEntityExportModalProvider();
|
||||||
const { handleDeleteEntityWebsocketResponse } = useAsyncDeleteProvider();
|
const { handleDeleteEntityWebsocketResponse } = useAsyncDeleteProvider();
|
||||||
@ -123,6 +118,10 @@ const NavBar = ({
|
|||||||
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
|
const [isFeatureModalOpen, setIsFeatureModalOpen] = useState<boolean>(false);
|
||||||
const [version, setVersion] = useState<string>();
|
const [version, setVersion] = useState<string>();
|
||||||
const [isDomainDropdownOpen, setIsDomainDropdownOpen] = useState(false);
|
const [isDomainDropdownOpen, setIsDomainDropdownOpen] = useState(false);
|
||||||
|
const {
|
||||||
|
preferences: { isSidebarCollapsed },
|
||||||
|
setPreference,
|
||||||
|
} = useCurrentUserPreferences();
|
||||||
|
|
||||||
const fetchOMVersion = async () => {
|
const fetchOMVersion = async () => {
|
||||||
try {
|
try {
|
||||||
@ -423,7 +422,9 @@ const NavBar = ({
|
|||||||
}
|
}
|
||||||
size="middle"
|
size="middle"
|
||||||
type="text"
|
type="text"
|
||||||
onClick={toggleSideBar}
|
onClick={() =>
|
||||||
|
setPreference({ isSidebarCollapsed: !isSidebarCollapsed })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<GlobalSearchBar />
|
<GlobalSearchBar />
|
||||||
|
@ -427,7 +427,8 @@ const TagsContainerV2 = ({
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}
|
}}
|
||||||
dataTestId={isGlossaryType ? 'glossary-container' : 'tags-container'}>
|
dataTestId={isGlossaryType ? 'glossary-container' : 'tags-container'}
|
||||||
|
isExpandDisabled={isEmpty(tags?.[tagType])}>
|
||||||
{suggestionDataRender ?? (
|
{suggestionDataRender ?? (
|
||||||
<>
|
<>
|
||||||
{tagBody}
|
{tagBody}
|
||||||
|
@ -273,7 +273,7 @@ h2.rotated-header {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.ant-card-head {
|
& > .ant-card-head {
|
||||||
padding: 0px 16px;
|
padding: 0px 16px;
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2024 Collate.
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { create } from 'zustand';
|
||||||
|
import { createJSONStorage, persist } from 'zustand/middleware';
|
||||||
|
import { useApplicationStore } from '../useApplicationStore';
|
||||||
|
|
||||||
|
interface UserPreferences {
|
||||||
|
isSidebarCollapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Store {
|
||||||
|
preferences: Record<string, UserPreferences>;
|
||||||
|
setUserPreference: (
|
||||||
|
userName: string,
|
||||||
|
preferences: Partial<UserPreferences>
|
||||||
|
) => void;
|
||||||
|
getUserPreference: (userName: string) => UserPreferences;
|
||||||
|
clearUserPreference: (userName: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultPreferences: UserPreferences = {
|
||||||
|
isSidebarCollapsed: false,
|
||||||
|
// Add default values for other preferences
|
||||||
|
};
|
||||||
|
|
||||||
|
export const usePersistentStorage = create<Store>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
preferences: {},
|
||||||
|
|
||||||
|
setUserPreference: (
|
||||||
|
userName: string,
|
||||||
|
newPreferences: Partial<UserPreferences>
|
||||||
|
) => {
|
||||||
|
set((state) => ({
|
||||||
|
preferences: {
|
||||||
|
...state.preferences,
|
||||||
|
[userName]: {
|
||||||
|
...defaultPreferences,
|
||||||
|
...state.preferences[userName],
|
||||||
|
...newPreferences,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserPreference: (userName: string) => {
|
||||||
|
const state = get();
|
||||||
|
|
||||||
|
return state.preferences[userName] || defaultPreferences;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearUserPreference: (userName: string) => {
|
||||||
|
set((state) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { [userName]: _, ...rest } = state.preferences;
|
||||||
|
|
||||||
|
return { preferences: rest };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: 'user-preferences-store',
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hook to easily access current user's preferences
|
||||||
|
export const useCurrentUserPreferences = () => {
|
||||||
|
const currentUser = useApplicationStore((state) => state.currentUser);
|
||||||
|
const { preferences, setUserPreference } = usePersistentStorage();
|
||||||
|
|
||||||
|
if (!currentUser?.name) {
|
||||||
|
return {
|
||||||
|
preferences: defaultPreferences,
|
||||||
|
setPreference: () => {
|
||||||
|
// update the user name in the local storage
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
preferences: preferences[currentUser.name] || defaultPreferences,
|
||||||
|
setPreference: (newPreferences: Partial<UserPreferences>) =>
|
||||||
|
setUserPreference(currentUser.name, newPreferences),
|
||||||
|
};
|
||||||
|
};
|
@ -69,7 +69,8 @@ export const FrequentlyJoinedTables = () => {
|
|||||||
cardProps={{
|
cardProps={{
|
||||||
title: t('label.frequently-joined-table-plural'),
|
title: t('label.frequently-joined-table-plural'),
|
||||||
}}
|
}}
|
||||||
dataTestId="frequently-joint-data-container">
|
dataTestId="frequently-joint-data-container"
|
||||||
|
isExpandDisabled={isEmpty(joinedTables)}>
|
||||||
{content}
|
{content}
|
||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
);
|
);
|
||||||
|
@ -84,7 +84,8 @@ export const PartitionedKeys = () => {
|
|||||||
<ExpandableCard
|
<ExpandableCard
|
||||||
cardProps={{
|
cardProps={{
|
||||||
title: t('label.table-partition-plural'),
|
title: t('label.table-partition-plural'),
|
||||||
}}>
|
}}
|
||||||
|
isExpandDisabled={isEmpty(partitionColumnDetails)}>
|
||||||
{content}
|
{content}
|
||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
);
|
);
|
||||||
|
@ -178,7 +178,8 @@ const TableConstraints = () => {
|
|||||||
<ExpandableCard
|
<ExpandableCard
|
||||||
cardProps={{
|
cardProps={{
|
||||||
title: header,
|
title: header,
|
||||||
}}>
|
}}
|
||||||
|
isExpandDisabled={isEmpty(data?.tableConstraints)}>
|
||||||
{content}
|
{content}
|
||||||
</ExpandableCard>
|
</ExpandableCard>
|
||||||
);
|
);
|
||||||
|
@ -77,7 +77,6 @@
|
|||||||
.ant-card.new-header-border-card {
|
.ant-card.new-header-border-card {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
|
|
||||||
transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
|
|
||||||
.ant-card-head {
|
.ant-card-head {
|
||||||
background: @grey-50;
|
background: @grey-50;
|
||||||
border-radius: @border-radius-sm;
|
border-radius: @border-radius-sm;
|
||||||
@ -89,6 +88,8 @@
|
|||||||
.ant-card-body {
|
.ant-card-body {
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
max-height: none !important;
|
max-height: none !important;
|
||||||
|
transition: all 200ms ease;
|
||||||
|
transition-property: height, left, top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-collapse-icon {
|
.expand-collapse-icon {
|
||||||
@ -120,5 +121,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-card-extra {
|
||||||
|
margin-left: @size-sm;
|
||||||
|
}
|
||||||
|
|
||||||
border: 1px solid @grey-15;
|
border: 1px solid @grey-15;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user