From e85c64c6ee3ce9583664aa366495e28943e113c8 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 08:36:54 +0200 Subject: [PATCH 01/16] add new props to FuxNotifiction; add content handler util for FuxNotification --- .../dtos/user-settings.interface.ts | 1 + .../credit-balance-button.component.tsx | 4 +- .../fux-notification.component.tsx | 71 +++++++++++- .../credit-balance/fux-notification/utils.ts | 103 ++++++++++++++++++ 4 files changed, 173 insertions(+), 6 deletions(-) create mode 100644 web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts diff --git a/web_ui/src/core/user-settings/dtos/user-settings.interface.ts b/web_ui/src/core/user-settings/dtos/user-settings.interface.ts index 44a09c07d7..4ae0a55a07 100644 --- a/web_ui/src/core/user-settings/dtos/user-settings.interface.ts +++ b/web_ui/src/core/user-settings/dtos/user-settings.interface.ts @@ -48,6 +48,7 @@ export enum FUX_NOTIFICATION_KEYS { ANNOTATOR_CONTINUE_ANNOTATING = 'annotatorContinueAnnotating', AUTO_TRAINING_MODAL = 'autoTrainingCreditModal', AUTO_TRAINING_NOTIFICATION = 'autoTrainingCreditNotification', + CREDIT_BALANCE_BUTTON = 'creditBalanceButton', } export enum FUX_SETTINGS_KEYS { diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx index 5a2a81f099..8ae83dd8f1 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx @@ -117,8 +117,10 @@ const CreditBalanceButtonFuxNotification = ({ isDarkMode }: { isDarkMode: boolea {isFirstAutoTrainedProject ? ( , 'triggerRef' | 'children'> { - docUrl?: string; - children: ReactNode; + settingsKey: FUX_NOTIFICATION_KEYS; + customDocUrl?: string; + children?: ReactNode; triggerRef: MutableRefObject; onClose?: () => void; } -export const FuxNotification = ({ docUrl, placement, triggerRef, state, onClose, children }: CustomPopoverProps) => { +export const FuxNotification = ({ + settingsKey, + customDocUrl, + placement, + triggerRef, + state, + onClose, + children, +}: CustomPopoverProps) => { + const { header, description, nextStepId, previousStepId, showDismissAll, docUrl } = + getFuxNotificationData(settingsKey); + const { close, isOpen, dismissAll, changeTutorial } = useTutorialEnablement(settingsKey); + const message = children ? children : description; + const url = useDocsUrl(); + const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined); + + if (!showDismissAll) { + + + {children} + + {newDocUrl && ( + + )} + + + + { + state.close(); + isFunction(onClose) && onClose(); + }} + aria-label={'close first user experience notification'} + UNSAFE_className={classes.close} + > + + + + ; + } + return ( {children} - {docUrl && ( + {newDocUrl && ( diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts b/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts new file mode 100644 index 0000000000..b39d6bfae6 --- /dev/null +++ b/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts @@ -0,0 +1,103 @@ +import { FUX_NOTIFICATION_KEYS } from '../../../../../core/user-settings/dtos/user-settings.interface'; +import { DocsUrl } from '../../../tutorials/utils'; + +interface FuxNotificationData { + header: string | undefined; + description: string; + docUrl: DocsUrl | undefined; + nextStepId: FUX_NOTIFICATION_KEYS | undefined; + previousStepId: FUX_NOTIFICATION_KEYS | undefined; + showDismissAll: boolean; +} + +export const getFuxNotificationData = (fuxNotificationId: string): FuxNotificationData => { + switch (fuxNotificationId) { + case FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY: + return { + header: '', + description: 'Click here to start annotating your dataset.', + docUrl: DocsUrl.ACTIVE_LEARNING, + nextStepId: undefined, + previousStepId: undefined, + showDismissAll: false, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_AUTO_TRAINING_STARTED: + return { + header: '', + description: + `This auto-training job is scheduled and ready to start when resources are available.` + + ` Click the 'bell' icon to see the training progress.`, + docUrl: undefined, + nextStepId: undefined, + previousStepId: undefined, + showDismissAll: false, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_TOOLS: + return { + header: 'How to annotate my data?', + description: + `Effective data annotation lays the groundwork for a model’s ability to accurately interpret and` + + ` learn from information. By annotating your data, you teach the model what patterns to ` + + `recognize. Intel® Geti™ provides you with various smart annotation assistants to accelerate` + + ` this annotation process.`, + docUrl: DocsUrl.ANNOTATION_TOOLS, + nextStepId: FUX_NOTIFICATION_KEYS.ANNOTATOR_ACTIVE_SET, + previousStepId: undefined, + showDismissAll: true, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_ACTIVE_SET: + return { + header: `What’s Active set?`, + description: + `In the media gallery you can switch between Active set and Dataset. Active set is set by default` + + ` in Intel® Geti™ and it displays the media items in an order that is optimal for creating a ` + + `well-balanced model, based on their informative features compared to the rest of your dataset.` + + ` However, you can switch to Dataset to display the media items in the order that was arranged ` + + `in your dataset folder.`, + docUrl: DocsUrl.MEDIA_GALLERY, + nextStepId: undefined, + previousStepId: FUX_NOTIFICATION_KEYS.ANNOTATOR_TOOLS, + showDismissAll: true, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_SUCCESSFULLY_TRAINED: + return { + header: 'Your model has been successfully trained', + description: '', + docUrl: DocsUrl.MODELS, + nextStepId: FUX_NOTIFICATION_KEYS.ANNOTATOR_CHECK_PREDICTIONS, + previousStepId: undefined, + showDismissAll: true, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_CHECK_PREDICTIONS: + return { + header: 'Check predictions', + description: + `Click here to review the accuracy of your model by comparing the model’s predictions against` + + ` your original annotations. Use our tools to analyze and adjust the predictions as needed, so` + + ` they can be used during the next training rounds. This will help to further improve your ` + + `model's performance.`, + docUrl: DocsUrl.TESTS, + nextStepId: undefined, + previousStepId: FUX_NOTIFICATION_KEYS.ANNOTATOR_SUCCESSFULLY_TRAINED, + showDismissAll: true, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_CONTINUE_ANNOTATING: + return { + header: 'Continue annotating', + description: '', + docUrl: DocsUrl.ANNOTATION_EDITOR, + nextStepId: undefined, + previousStepId: undefined, + showDismissAll: true, + }; + default: + return { + header: '', + description: '', + docUrl: undefined, + previousStepId: undefined, + nextStepId: undefined, + showDismissAll: true, + }; + } +}; From 6f30e818c1c6ba689d3afa9f0eb35c8cb92c4c96 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 12:51:52 +0200 Subject: [PATCH 02/16] fix FuxNotification implementation for the case without the Dismiss All button; fix credit button notification to work under new implementation --- .../credits-to-consume.module.scss | 2 +- .../fux-notification.component.tsx | 84 +++++++++---------- .../fux-notification.module.scss | 78 ++++++++++++++++- .../credit-balance/fux-notification/utils.ts | 9 ++ 4 files changed, 126 insertions(+), 47 deletions(-) diff --git a/web_ui/src/shared/components/header/credit-balance/credits-to-consume.module.scss b/web_ui/src/shared/components/header/credit-balance/credits-to-consume.module.scss index 2e70e1f9ee..7ead4acb2d 100644 --- a/web_ui/src/shared/components/header/credit-balance/credits-to-consume.module.scss +++ b/web_ui/src/shared/components/header/credit-balance/credits-to-consume.module.scss @@ -2,7 +2,7 @@ // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE .threeDotsFlashing { - display: inline-block; + display: inline-flex !important; margin-left: var(--spectrum-global-dimension-size-150); margin-right: var(--spectrum-global-dimension-size-150); } diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx b/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx index 21c59767fc..7ce3687c3d 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx +++ b/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx @@ -3,14 +3,14 @@ import { ComponentProps, MutableRefObject, ReactNode } from 'react'; -import { ActionButton, Button, CustomPopover, Divider, Flex, Popover, Text } from '@geti/ui'; +import { ActionButton, Button, CustomPopover, Divider, Flex, Popover, Text, View } from '@geti/ui'; import { Close } from '@geti/ui/icons'; import { isFunction } from 'lodash-es'; import { FUX_NOTIFICATION_KEYS } from '../../../../../core/user-settings/dtos/user-settings.interface'; import { useDocsUrl } from '../../../../../hooks/use-docs-url/use-docs-url.hook'; -import { useTutorialEnablement } from '../../../../hooks/use-tutorial-enablement.hook'; import { openNewTab } from '../../../../utils'; +import { onPressLearnMore } from '../../../tutorials/utils'; import { getFuxNotificationData } from './utils'; import classes from './fux-notification.module.scss'; @@ -34,52 +34,52 @@ export const FuxNotification = ({ }: CustomPopoverProps) => { const { header, description, nextStepId, previousStepId, showDismissAll, docUrl } = getFuxNotificationData(settingsKey); - const { close, isOpen, dismissAll, changeTutorial } = useTutorialEnablement(settingsKey); const message = children ? children : description; const url = useDocsUrl(); - const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined); - + const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined; if (!showDismissAll) { - - - {children} + return ( + + + {message} + {newDocUrl && ( + + )} - {newDocUrl && ( - - )} - - - - { - state.close(); - isFunction(onClose) && onClose(); - }} - aria-label={'close first user experience notification'} - UNSAFE_className={classes.close} - > - - - - ; + + + + + ); } - + // todo: not implemented anywhere yet, to do in next PR return ( Date: Thu, 10 Jul 2025 12:57:42 +0200 Subject: [PATCH 03/16] fix unit tests --- web_ui/src/core/user-settings/utils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web_ui/src/core/user-settings/utils.ts b/web_ui/src/core/user-settings/utils.ts index 9b174f257e..f0366ea831 100644 --- a/web_ui/src/core/user-settings/utils.ts +++ b/web_ui/src/core/user-settings/utils.ts @@ -184,6 +184,9 @@ export const initialFuxNotificationsConfig: FuxNotificationsConfig = { [FUX_NOTIFICATION_KEYS.AUTO_TRAINING_NOTIFICATION]: { isEnabled: false, }, + [FUX_NOTIFICATION_KEYS.CREDIT_BALANCE_BUTTON]: { + isEnabled: false, + }, }; export const initialFuxSettingsConfig: FuxSettingsConfig = { From d2d67e58765069073903f1f92abe36bbbb6a22ce Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 13:16:34 +0200 Subject: [PATCH 04/16] add missing header; remove unsused declarations --- .../fux-notification/fux-notification.component.tsx | 3 +-- .../components/header/credit-balance/fux-notification/utils.ts | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx b/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx index 7ce3687c3d..1c2bb0eec9 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx +++ b/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx @@ -32,8 +32,7 @@ export const FuxNotification = ({ onClose, children, }: CustomPopoverProps) => { - const { header, description, nextStepId, previousStepId, showDismissAll, docUrl } = - getFuxNotificationData(settingsKey); + const { description, showDismissAll, docUrl } = getFuxNotificationData(settingsKey); const message = children ? children : description; const url = useDocsUrl(); const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined; diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts b/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts index cfcea5d4ab..21c3c8a627 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts +++ b/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts @@ -1,3 +1,6 @@ +// Copyright (C) 2022-2025 Intel Corporation +// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE + import { FUX_NOTIFICATION_KEYS } from '../../../../../core/user-settings/dtos/user-settings.interface'; import { DocsUrl } from '../../../tutorials/utils'; From 13f18ab1119c9f1c264b71b8c7895b01e027f565 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 13:18:27 +0200 Subject: [PATCH 05/16] fix unit test --- .../header/credit-balance/credit-balance-button.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx index 8ab66bea89..2e0751ecd0 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx @@ -97,7 +97,7 @@ describe('CreditBalanceButton', () => { expect(screen.getByRole('button', { name: /credit balance stat/i })).toHaveClass('fuxOpen'); expect(await screen.findByRole('button', { name: 'Learn more' })).toBeInTheDocument(); - fireEvent.click(screen.getByRole('button', { name: /close first user experience notification/i })); + fireEvent.click(screen.getByRole('button', { name: /dismiss help dialog/i })); await waitFor(() => { const userSettings = 0; From 965d8603301396f914cfa302cbf85328ae10a4fe Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 14:18:30 +0200 Subject: [PATCH 06/16] fix component tests --- web_ui/tests/features/fux/annotator-training-fux.spec.ts | 6 ++---- web_ui/tests/features/fux/dataset-page-fux.spec.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/web_ui/tests/features/fux/annotator-training-fux.spec.ts b/web_ui/tests/features/fux/annotator-training-fux.spec.ts index 7473e5b4b6..c24b5b4e37 100644 --- a/web_ui/tests/features/fux/annotator-training-fux.spec.ts +++ b/web_ui/tests/features/fux/annotator-training-fux.spec.ts @@ -482,12 +482,10 @@ test.describe('Check FUX notifications in Annotator related to training', () => await expect(page.getByText(autoTrainingCreditSystemNotificationRegex)).toBeVisible(); - await page.getByRole('button', { name: /close first user experience notification/i }).click(); + await page.getByRole('button', { name: /Dismiss help dialog/i }).click(); await page.reload(); - await expect( - page.getByRole('button', { name: /close first user experience notification/i }) - ).toBeHidden(); + await expect(page.getByRole('button', { name: /Dismiss help dialog/i })).toBeHidden(); }); // eslint-disable-next-line max-len diff --git a/web_ui/tests/features/fux/dataset-page-fux.spec.ts b/web_ui/tests/features/fux/dataset-page-fux.spec.ts index 4aee4e0740..6b4064690d 100644 --- a/web_ui/tests/features/fux/dataset-page-fux.spec.ts +++ b/web_ui/tests/features/fux/dataset-page-fux.spec.ts @@ -54,7 +54,7 @@ test.describe('Check FUX notifications on dataset page', () => { await expect(page.getByRole('heading', { name: 'Building a good dataset' })).toBeVisible(); // The first click closes the "The auto-training job has ..." notification that is popover - await page.getByLabel('close first user experience notification').click(); + await page.getByLabel('Dismiss help dialog').click(); await expect(page.getByText('The auto-training job has been started')).toBeHidden(); From 8691426d19ba058c198ffe2b11d8de96c2a231f1 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 16:28:42 +0200 Subject: [PATCH 07/16] use useCheckPermissions hook instead of deprecated property --- .../credit-balance-button.component.tsx | 11 ++++----- .../credit-balance-button.test.tsx | 24 ++++++------------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx index 8ae83dd8f1..165d2567a2 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx @@ -4,7 +4,6 @@ import { forwardRef, useEffect, useRef } from 'react'; import { paths } from '@geti/core'; -import { useUsers } from '@geti/core/src/users/hook/use-users.hook'; import { ActionButton, Tooltip, TooltipTrigger, type FocusableRef } from '@geti/ui'; import { CreditCard } from '@geti/ui/icons'; import { isNil } from 'lodash-es'; @@ -18,6 +17,8 @@ import { useOrganizationIdentifier } from '../../../../hooks/use-organization-id import { usePrevious } from '../../../../hooks/use-previous/use-previous.hook'; import { useProject } from '../../../../pages/project-details/providers/project-provider/project-provider.component'; import { ONE_MINUTE } from '../../../utils'; +import { useCheckPermission } from '../../has-permission/has-permission.component'; +import { OPERATION } from '../../has-permission/has-permission.interface'; import { CreditsToConsume } from './credits-to-consume.component'; import { FuxNotification } from './fux-notification/fux-notification.component'; import { isBalanceLow } from './util'; @@ -83,11 +84,10 @@ const CreditBalanceButtonFuxNotification = ({ isDarkMode }: { isDarkMode: boolea const firstAutoTrainedProject = settings.config[FUX_SETTINGS_KEYS.FIRST_AUTOTRAINED_PROJECT_ID].value; const prevFuxEnabled = usePrevious(isFuxNotificationEnabled); - const { useActiveUser } = useUsers(); const { organizationId } = useOrganizationIdentifier(); - const { data: activeUser } = useActiveUser(organizationId); const { project } = useProject(); const isFirstAutoTrainedProject = firstAutoTrainedProject === project.id; + const canCheckUsageTab = useCheckPermission([OPERATION.USAGE_TAB]); useEffect(() => { if (isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { @@ -119,14 +119,13 @@ const CreditBalanceButtonFuxNotification = ({ isDarkMode }: { isDarkMode: boolea The auto-training job has been started, credits deducted. - {activeUser?.isAdmin ? ' Check your credit balance here.' : null} + {canCheckUsageTab ? ' Check your credit balance here.' : null} ) : ( <> diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx index 2e0751ecd0..8d10cc3d4c 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx @@ -2,7 +2,6 @@ // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE import { createInMemoryUsersService } from '@geti/core/src/users/services/in-memory-users-service'; -import { User } from '@geti/core/src/users/users.interface'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { useParams } from 'react-router-dom'; @@ -16,6 +15,7 @@ import { createInMemoryUserSettingsService } from '../../../../core/user-setting import { getMockedUserGlobalSettings } from '../../../../test-utils/mocked-items-factory/mocked-settings'; import { getMockedUser } from '../../../../test-utils/mocked-items-factory/mocked-users'; import { projectRender as render } from '../../../../test-utils/project-provider-render'; +import { useCheckPermission } from '../../has-permission/has-permission.component'; import { CreditBalanceButton } from './credit-balance-button.component'; import { CREDIT_LOW_LIMIT } from './util'; @@ -29,20 +29,22 @@ jest.mock('react-router-dom', () => ({ useParams: jest.fn(), })); +jest.mock('../../has-permission/has-permission.component', () => ({ + useCheckPermission: jest.fn(() => true), +})); + const renderApp = async ({ projectId, settings = {}, mockedSaveSettings = jest.fn(), creditsIncoming = 0, creditsAvailable = 0, - user, }: { settings?: Partial; mockedSaveSettings?: jest.Mock; projectId?: string; creditsIncoming?: number; creditsAvailable?: number; - user?: User; }) => { jest.mocked(useParams).mockReturnValue({ projectId, organizationId: 'organizationId' }); const userSettingsService = createInMemoryUserSettingsService(); @@ -53,13 +55,8 @@ const renderApp = async ({ creditsService.getOrganizationBalance = () => Promise.resolve({ incoming: creditsIncoming, available: creditsAvailable, blocked: 0 }); - const usersService = createInMemoryUsersService(); - if (user) { - usersService.getActiveUser = jest.fn(async () => user); - } - return render(, { - services: { userSettingsService, creditsService, usersService }, + services: { userSettingsService, creditsService }, }); }; @@ -81,7 +78,6 @@ describe('CreditBalanceButton', () => { describe('has projectId param', () => { it('open/close notification', async () => { const mockedSaveSettings = jest.fn(); - const admin = getMockedUser({ email: 'test@mail.com', isAdmin: true }); await renderApp({ projectId: '321', @@ -90,7 +86,6 @@ describe('CreditBalanceButton', () => { firstAutotrainedProjectId: { value: 'project-id' }, }, mockedSaveSettings, - user: admin, }); expect(screen.getByText(/the auto-training job has been started/i)).toBeVisible(); @@ -112,7 +107,6 @@ describe('CreditBalanceButton', () => { it('will not show the notification if the users has navigated to other project that the autotrained one', async () => { const mockedSaveSettings = jest.fn(); - const admin = getMockedUser({ email: 'test@mail.com', isAdmin: true }); await renderApp({ projectId: '321', @@ -121,16 +115,13 @@ describe('CreditBalanceButton', () => { firstAutotrainedProjectId: { value: 'other-project-id' }, }, mockedSaveSettings, - user: admin, }); expect(screen.queryByText(/the auto-training job has been started/i)).not.toBeInTheDocument(); }); it('does not show a link to credits usage page for contributors', async () => { - const usersService = createInMemoryUsersService(); - const contributor = getMockedUser({ email: 'test@mail.com', isAdmin: false }); - usersService.getActiveUser = jest.fn(async () => contributor); + jest.mocked(useCheckPermission).mockReturnValue(false); await renderApp({ projectId: '321', @@ -138,7 +129,6 @@ describe('CreditBalanceButton', () => { autoTrainingCreditNotification: { isEnabled: true }, firstAutotrainedProjectId: { value: 'project-id' }, }, - user: contributor, }); expect(screen.getByText(/the auto-training job has been started/i)).toBeVisible(); From 93e365adcf82c0fbef27164e501ed2004230dfc6 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 16:31:59 +0200 Subject: [PATCH 08/16] move FuxNotification to shared components --- .../fux-notification/fux-notification.component.tsx | 8 ++++---- .../fux-notification/fux-notification.module.scss | 2 +- .../{header/credit-balance => }/fux-notification/utils.ts | 4 ++-- .../credit-balance/credit-balance-button.component.tsx | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) rename web_ui/src/shared/components/{header/credit-balance => }/fux-notification/fux-notification.component.tsx (93%) rename web_ui/src/shared/components/{header/credit-balance => }/fux-notification/fux-notification.module.scss (96%) rename web_ui/src/shared/components/{header/credit-balance => }/fux-notification/utils.ts (97%) diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx similarity index 93% rename from web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx rename to web_ui/src/shared/components/fux-notification/fux-notification.component.tsx index 1c2bb0eec9..ad21105ca9 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.component.tsx +++ b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx @@ -7,10 +7,10 @@ import { ActionButton, Button, CustomPopover, Divider, Flex, Popover, Text, View import { Close } from '@geti/ui/icons'; import { isFunction } from 'lodash-es'; -import { FUX_NOTIFICATION_KEYS } from '../../../../../core/user-settings/dtos/user-settings.interface'; -import { useDocsUrl } from '../../../../../hooks/use-docs-url/use-docs-url.hook'; -import { openNewTab } from '../../../../utils'; -import { onPressLearnMore } from '../../../tutorials/utils'; +import { FUX_NOTIFICATION_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; +import { useDocsUrl } from '../../../hooks/use-docs-url/use-docs-url.hook'; +import { openNewTab } from '../../utils'; +import { onPressLearnMore } from '../tutorials/utils'; import { getFuxNotificationData } from './utils'; import classes from './fux-notification.module.scss'; diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.module.scss b/web_ui/src/shared/components/fux-notification/fux-notification.module.scss similarity index 96% rename from web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.module.scss rename to web_ui/src/shared/components/fux-notification/fux-notification.module.scss index c83bc81976..34106ac963 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/fux-notification.module.scss +++ b/web_ui/src/shared/components/fux-notification/fux-notification.module.scss @@ -8,7 +8,7 @@ fill: #89ad4e !important; } - margin-top: 5px !important; + margin-top: var(--spectrum-global-dimension-size-65) !important; min-width: var(--spectrum-global-dimension-size-4600) !important; } diff --git a/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts b/web_ui/src/shared/components/fux-notification/utils.ts similarity index 97% rename from web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts rename to web_ui/src/shared/components/fux-notification/utils.ts index 21c3c8a627..d83b87fc5a 100644 --- a/web_ui/src/shared/components/header/credit-balance/fux-notification/utils.ts +++ b/web_ui/src/shared/components/fux-notification/utils.ts @@ -1,8 +1,8 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { FUX_NOTIFICATION_KEYS } from '../../../../../core/user-settings/dtos/user-settings.interface'; -import { DocsUrl } from '../../../tutorials/utils'; +import { FUX_NOTIFICATION_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; +import { DocsUrl } from '../tutorials/utils'; interface FuxNotificationData { header: string | undefined; diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx index 165d2567a2..e2de13648d 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.component.tsx @@ -17,10 +17,10 @@ import { useOrganizationIdentifier } from '../../../../hooks/use-organization-id import { usePrevious } from '../../../../hooks/use-previous/use-previous.hook'; import { useProject } from '../../../../pages/project-details/providers/project-provider/project-provider.component'; import { ONE_MINUTE } from '../../../utils'; +import { FuxNotification } from '../../fux-notification/fux-notification.component'; import { useCheckPermission } from '../../has-permission/has-permission.component'; import { OPERATION } from '../../has-permission/has-permission.interface'; import { CreditsToConsume } from './credits-to-consume.component'; -import { FuxNotification } from './fux-notification/fux-notification.component'; import { isBalanceLow } from './util'; import classes from './credit-balance.module.scss'; From 799addf8f0c4e608f62f8388b7806590ee66d76c Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 16:38:32 +0200 Subject: [PATCH 09/16] fix path --- .../components/fux-notification/fux-notification.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/shared/components/fux-notification/fux-notification.module.scss b/web_ui/src/shared/components/fux-notification/fux-notification.module.scss index 34106ac963..5c939da4e9 100644 --- a/web_ui/src/shared/components/fux-notification/fux-notification.module.scss +++ b/web_ui/src/shared/components/fux-notification/fux-notification.module.scss @@ -1,4 +1,4 @@ -@use '../../../../shared.module'; +@use '../../shared.module'; .container { @extend .blueGreenGradient; From ed1c489bbdc6e68bd27776c0f495de5e3b782363 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Thu, 10 Jul 2025 16:43:53 +0200 Subject: [PATCH 10/16] fix eslit issue --- .../header/credit-balance/credit-balance-button.test.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx index 8d10cc3d4c..16999f03b5 100644 --- a/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx +++ b/web_ui/src/shared/components/header/credit-balance/credit-balance-button.test.tsx @@ -1,7 +1,6 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { createInMemoryUsersService } from '@geti/core/src/users/services/in-memory-users-service'; import { fireEvent, screen, waitFor } from '@testing-library/react'; import { useParams } from 'react-router-dom'; @@ -13,7 +12,6 @@ import { } from '../../../../core/user-settings/dtos/user-settings.interface'; import { createInMemoryUserSettingsService } from '../../../../core/user-settings/services/in-memory-user-settings-service'; import { getMockedUserGlobalSettings } from '../../../../test-utils/mocked-items-factory/mocked-settings'; -import { getMockedUser } from '../../../../test-utils/mocked-items-factory/mocked-users'; import { projectRender as render } from '../../../../test-utils/project-provider-render'; import { useCheckPermission } from '../../has-permission/has-permission.component'; import { CreditBalanceButton } from './credit-balance-button.component'; From 59389c185af79ed81a4aedc6bdf5fda715abfbb2 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 09:05:43 +0200 Subject: [PATCH 11/16] move util from coachmark to fux; add handler functions to fux notification component --- ...ly-auto-trained-notification.component.tsx | 2 +- .../src/shared/components/coach-mark/utils.ts | 38 +----------------- .../fux-notification.component.tsx | 19 +++++++-- .../notifications}/utils.test.ts | 10 ++--- .../fux-notification/notifications/utils.ts | 40 +++++++++++++++++++ .../components/fux-notification/utils.ts | 30 ++++++++++++++ 6 files changed, 93 insertions(+), 46 deletions(-) rename web_ui/src/shared/components/{coach-mark => fux-notification/notifications}/utils.test.ts (91%) create mode 100644 web_ui/src/shared/components/fux-notification/notifications/utils.ts diff --git a/web_ui/src/shared/components/coach-mark/fux-notifications/successfully-auto-trained-notification.component.tsx b/web_ui/src/shared/components/coach-mark/fux-notifications/successfully-auto-trained-notification.component.tsx index d374217b92..87b6711b21 100644 --- a/web_ui/src/shared/components/coach-mark/fux-notifications/successfully-auto-trained-notification.component.tsx +++ b/web_ui/src/shared/components/coach-mark/fux-notifications/successfully-auto-trained-notification.component.tsx @@ -18,8 +18,8 @@ import { useUserGlobalSettings } from '../../../../core/user-settings/hooks/use- import { useFuxNotifications } from '../../../../hooks/use-fux-notifications/use-fux-notifications.hook'; import { useProjectIdentifier } from '../../../../hooks/use-project-identifier/use-project-identifier'; import { useProject } from '../../../../pages/project-details/providers/project-provider/project-provider.component'; +import { onFirstSuccessfulAutoTrainingJob } from '../../fux-notification/notifications/utils'; import { CoachMark } from '../coach-mark.component'; -import { onFirstSuccessfulAutoTrainingJob } from '../utils'; const useSuccessfullyAutotrainedNotificationJobs = ({ enabled, diff --git a/web_ui/src/shared/components/coach-mark/utils.ts b/web_ui/src/shared/components/coach-mark/utils.ts index a85879fbd2..bae70f9b74 100644 --- a/web_ui/src/shared/components/coach-mark/utils.ts +++ b/web_ui/src/shared/components/coach-mark/utils.ts @@ -1,13 +1,7 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { InfiniteData } from '@tanstack/react-query'; - -import { GETI_SYSTEM_AUTHOR_ID, JobState } from '../../../core/jobs/jobs.const'; -import { JobTask } from '../../../core/jobs/jobs.interface'; -import { JobsResponse } from '../../../core/jobs/services/jobs-service.interface'; -import { FUX_NOTIFICATION_KEYS, FUX_SETTINGS_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; -import { UserGlobalSettings, UseSettings } from '../../../core/user-settings/services/user-settings.interface'; +import { FUX_NOTIFICATION_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; import { DocsUrl } from '../../../shared/components/tutorials/utils'; export enum TipPosition { @@ -155,33 +149,3 @@ export const getStepInfo = (fuxNotificationId: FUX_NOTIFICATION_KEYS) => { }; } }; - -export const onFirstSuccessfulAutoTrainingJob = - (settings: UseSettings, callback: (modelId: string) => void) => - ({ pages }: InfiniteData) => { - if (!pages[0]) { - return; - } - - const { jobsCount, jobs } = pages[0]; - const totalFinishedJobs = Number(jobsCount.numberOfFinishedJobs); - const hasFinishedJobs = totalFinishedJobs > 0; - const neverSuccessfullyAutoTrained = settings.config[FUX_SETTINGS_KEYS.NEVER_SUCCESSFULLY_AUTOTRAINED].value; - const firstScheduledAutoTrainingJobId = settings.config[FUX_SETTINGS_KEYS.FIRST_AUTOTRAINING_JOB_ID].value; - const desiredJob = jobs.find((job): job is JobTask => { - return ( - job.state === JobState.FINISHED && - job.authorId === GETI_SYSTEM_AUTHOR_ID && - job.id === firstScheduledAutoTrainingJobId - ); - }); - const trainedModelId = desiredJob?.metadata?.trainedModel?.modelId; - - if (trainedModelId === undefined) { - return; - } - - if (trainedModelId && hasFinishedJobs && neverSuccessfullyAutoTrained && desiredJob) { - callback(trainedModelId); - } - }; diff --git a/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx index ad21105ca9..60e4238327 100644 --- a/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx +++ b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx @@ -9,9 +9,10 @@ import { isFunction } from 'lodash-es'; import { FUX_NOTIFICATION_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; import { useDocsUrl } from '../../../hooks/use-docs-url/use-docs-url.hook'; +import { useTutorialEnablement } from '../../hooks/use-tutorial-enablement.hook'; import { openNewTab } from '../../utils'; import { onPressLearnMore } from '../tutorials/utils'; -import { getFuxNotificationData } from './utils'; +import { getFuxNotificationData, getStepInfo } from './utils'; import classes from './fux-notification.module.scss'; @@ -32,7 +33,9 @@ export const FuxNotification = ({ onClose, children, }: CustomPopoverProps) => { - const { description, showDismissAll, docUrl } = getFuxNotificationData(settingsKey); + const { header, description, docUrl, nextStepId, previousStepId, showDismissAll } = + getFuxNotificationData(settingsKey); + const { dismissAll, changeTutorial } = useTutorialEnablement(settingsKey); const message = children ? children : description; const url = useDocsUrl(); const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined; @@ -78,7 +81,17 @@ export const FuxNotification = ({ ); } - // todo: not implemented anywhere yet, to do in next PR + + const onPressNext = () => { + nextStepId && changeTutorial(settingsKey, nextStepId); + }; + + const onPressPrevious = () => { + previousStepId && changeTutorial(settingsKey, previousStepId); + }; + + const stepInfo = getStepInfo(settingsKey); + return ( = {}, mockedJobs: Job[] = []) => ({ diff --git a/web_ui/src/shared/components/fux-notification/notifications/utils.ts b/web_ui/src/shared/components/fux-notification/notifications/utils.ts new file mode 100644 index 0000000000..0cb54f1f4c --- /dev/null +++ b/web_ui/src/shared/components/fux-notification/notifications/utils.ts @@ -0,0 +1,40 @@ +// Copyright (C) 2022-2025 Intel Corporation +// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE + +import { InfiniteData } from '@tanstack/react-query'; + +import { GETI_SYSTEM_AUTHOR_ID, JobState } from '../../../../core/jobs/jobs.const'; +import { JobTask } from '../../../../core/jobs/jobs.interface'; +import { JobsResponse } from '../../../../core/jobs/services/jobs-service.interface'; +import { FUX_SETTINGS_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; +import { UserGlobalSettings, UseSettings } from '../../../../core/user-settings/services/user-settings.interface'; + +export const onFirstSuccessfulAutoTrainingJob = + (settings: UseSettings, callback: (modelId: string) => void) => + ({ pages }: InfiniteData) => { + if (!pages[0]) { + return; + } + + const { jobsCount, jobs } = pages[0]; + const totalFinishedJobs = Number(jobsCount.numberOfFinishedJobs); + const hasFinishedJobs = totalFinishedJobs > 0; + const neverSuccessfullyAutoTrained = settings.config[FUX_SETTINGS_KEYS.NEVER_SUCCESSFULLY_AUTOTRAINED].value; + const firstScheduledAutoTrainingJobId = settings.config[FUX_SETTINGS_KEYS.FIRST_AUTOTRAINING_JOB_ID].value; + const desiredJob = jobs.find((job): job is JobTask => { + return ( + job.state === JobState.FINISHED && + job.authorId === GETI_SYSTEM_AUTHOR_ID && + job.id === firstScheduledAutoTrainingJobId + ); + }); + const trainedModelId = desiredJob?.metadata?.trainedModel?.modelId; + + if (trainedModelId === undefined) { + return; + } + + if (trainedModelId && hasFinishedJobs && neverSuccessfullyAutoTrained && desiredJob) { + callback(trainedModelId); + } + }; diff --git a/web_ui/src/shared/components/fux-notification/utils.ts b/web_ui/src/shared/components/fux-notification/utils.ts index d83b87fc5a..dd01b0710c 100644 --- a/web_ui/src/shared/components/fux-notification/utils.ts +++ b/web_ui/src/shared/components/fux-notification/utils.ts @@ -113,3 +113,33 @@ export const getFuxNotificationData = (fuxNotificationId: string): FuxNotificati }; } }; + +export const getStepInfo = (fuxNotificationId: FUX_NOTIFICATION_KEYS) => { + switch (fuxNotificationId) { + case FUX_NOTIFICATION_KEYS.ANNOTATOR_TOOLS: + return { + stepNumber: 1, + totalCount: 2, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_ACTIVE_SET: + return { + stepNumber: 2, + totalCount: 2, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_SUCCESSFULLY_TRAINED: + return { + stepNumber: 1, + totalCount: 2, + }; + case FUX_NOTIFICATION_KEYS.ANNOTATOR_CHECK_PREDICTIONS: + return { + stepNumber: 2, + totalCount: 2, + }; + default: + return { + stepNumber: undefined, + totalCount: undefined, + }; + } +}; From 3521d803a6dbeb380f369dd7270711c38175aa85 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 09:10:15 +0200 Subject: [PATCH 12/16] improve FuxNotification to have CoachMark features --- .../fux-notification.component.tsx | 127 +++++++++++++----- 1 file changed, 94 insertions(+), 33 deletions(-) diff --git a/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx index 60e4238327..9a0ce60b28 100644 --- a/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx +++ b/web_ui/src/shared/components/fux-notification/fux-notification.component.tsx @@ -3,14 +3,27 @@ import { ComponentProps, MutableRefObject, ReactNode } from 'react'; -import { ActionButton, Button, CustomPopover, Divider, Flex, Popover, Text, View } from '@geti/ui'; -import { Close } from '@geti/ui/icons'; -import { isFunction } from 'lodash-es'; +import { + ActionButton, + Button, + ButtonGroup, + CustomPopover, + Divider, + Flex, + Item, + Menu, + MenuTrigger, + Popover, + Text, + View, +} from '@geti/ui'; +import { ChevronLeft, Close, MoreMenu } from '@geti/ui/icons'; +import { isEmpty, isFunction } from 'lodash-es'; import { FUX_NOTIFICATION_KEYS } from '../../../core/user-settings/dtos/user-settings.interface'; +import { useUserGlobalSettings } from '../../../core/user-settings/hooks/use-global-settings.hook'; import { useDocsUrl } from '../../../hooks/use-docs-url/use-docs-url.hook'; import { useTutorialEnablement } from '../../hooks/use-tutorial-enablement.hook'; -import { openNewTab } from '../../utils'; import { onPressLearnMore } from '../tutorials/utils'; import { getFuxNotificationData, getStepInfo } from './utils'; @@ -33,12 +46,18 @@ export const FuxNotification = ({ onClose, children, }: CustomPopoverProps) => { + const settings = useUserGlobalSettings(); const { header, description, docUrl, nextStepId, previousStepId, showDismissAll } = getFuxNotificationData(settingsKey); const { dismissAll, changeTutorial } = useTutorialEnablement(settingsKey); const message = children ? children : description; const url = useDocsUrl(); const newDocUrl = customDocUrl ?? (docUrl && `${url}${docUrl}`) ?? undefined; + + if (isEmpty(message)) { + return <>; + } + if (!showDismissAll) { return ( - - {children} - - {newDocUrl && ( - - )} - - - - { - state.close(); - isFunction(onClose) && onClose(); - }} - aria-label={'close first user experience notification'} - UNSAFE_className={classes.close} - > - - - + + + {header && {header}} + {stepInfo.stepNumber && stepInfo.totalCount && ( + + {stepInfo.stepNumber} of {stepInfo.totalCount} + + )} + + + {message} + + + + {previousStepId && ( + + )} + {docUrl && ( + + )} + {nextStepId ? ( + + ) : ( + + )} + + + + + + + + + Dismiss all + + + + + ); }; From 2b099094803e2d62051f104259ee0974f0a69e27 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 09:29:39 +0200 Subject: [PATCH 13/16] Implement ANNOTATE_INTERACTIVELY notification using FuxNotification Component --- .../dataset-tab-panel.component.tsx | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx index 5555ac26b3..011e271f9d 100644 --- a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx +++ b/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { Key } from 'react'; +import { Key, useEffect, useRef } from 'react'; import { useNavigateToAnnotatorRoute } from '@geti/core/src/services/use-navigate-to-annotator-route.hook'; import { Button, Flex, Item, TabList, TabPanels, Tabs, View } from '@geti/ui'; @@ -12,8 +12,10 @@ import { useOverlayTriggerState } from 'react-stately'; import { Dataset } from '../../../../core/projects/dataset.interface'; import { isAnomalyDomain } from '../../../../core/projects/domains'; import { FUX_NOTIFICATION_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; -import { CoachMark } from '../../../../shared/components/coach-mark/coach-mark.component'; +import { useUserGlobalSettings } from '../../../../core/user-settings/hooks/use-global-settings.hook'; +import { usePrevious } from '../../../../hooks/use-previous/use-previous.hook'; import { TooltipWithDisableButton } from '../../../../shared/components/custom-tooltip/tooltip-with-disable-button'; +import { FuxNotification } from '../../../../shared/components/fux-notification/fux-notification.component'; import { TabItem } from '../../../../shared/components/tabs/tabs.interface'; import { TruncatedText } from '../../../../shared/components/truncated-text/truncated-text.component'; import { useActiveTab } from '../../../../shared/hooks/use-active-tab.hook'; @@ -31,6 +33,7 @@ import { DATASET_TABS_TO_PATH, DatasetChapters, NO_MEDIA_MESSAGE } from './utils import classes from './project-dataset.module.scss'; export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { + const settings = useUserGlobalSettings(); const navigate = useNavigate(); const { media } = useMedia(); const selectedDataset = dataset; @@ -62,6 +65,27 @@ export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { }); }; + const triggerRef = useRef(null); + const fuxState = useOverlayTriggerState({}); + const isFuxNotificationEnabled = settings.config[FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]?.isEnabled; + const prevFuxEnabled = usePrevious(isFuxNotificationEnabled); + + useEffect(() => { + if (isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { + fuxState.open(); + } else if (!isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { + fuxState.close(); + } + }, [fuxState, isFuxNotificationEnabled, prevFuxEnabled]); + + const handleCloseNotification = () => { + isFuxNotificationEnabled && + settings.saveConfig({ + ...settings.config, + [FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]: { isEnabled: false }, + }); + }; + useOpenNotificationToast(); return ( @@ -99,21 +123,9 @@ export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { {isAnomalyProject && } - {annotateButtonText === 'Annotate interactively' && !isAnnotatorDisabled && ( - - )} - + {annotateButtonText === 'Annotate interactively' && !isAnnotatorDisabled && ( + + )} From 0f4f6623a68cf933868251a6e765a08fd25a6ef4 Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 09:40:39 +0200 Subject: [PATCH 14/16] change type from string to FUX_NOTIFICATION_KEYS --- web_ui/src/shared/components/fux-notification/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/shared/components/fux-notification/utils.ts b/web_ui/src/shared/components/fux-notification/utils.ts index dd01b0710c..d21b88f6e7 100644 --- a/web_ui/src/shared/components/fux-notification/utils.ts +++ b/web_ui/src/shared/components/fux-notification/utils.ts @@ -13,7 +13,7 @@ interface FuxNotificationData { showDismissAll: boolean; } -export const getFuxNotificationData = (fuxNotificationId: string): FuxNotificationData => { +export const getFuxNotificationData = (fuxNotificationId: FUX_NOTIFICATION_KEYS): FuxNotificationData => { switch (fuxNotificationId) { case FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY: return { From 28c43fed3314cc65bd684b1576d52e0ad7b575fa Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 10:07:06 +0200 Subject: [PATCH 15/16] move AnnotateInteractivelyNotification to separate component --- .../dataset-tab-panel.component.tsx | 34 ++----------- ...e-interactively-notification.component.tsx | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 31 deletions(-) create mode 100644 web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx diff --git a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx b/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx index 011e271f9d..a7c955a0f5 100644 --- a/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx +++ b/web_ui/src/pages/project-details/components/project-dataset/dataset-tab-panel.component.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { Key, useEffect, useRef } from 'react'; +import { Key, useRef } from 'react'; import { useNavigateToAnnotatorRoute } from '@geti/core/src/services/use-navigate-to-annotator-route.hook'; import { Button, Flex, Item, TabList, TabPanels, Tabs, View } from '@geti/ui'; @@ -11,11 +11,8 @@ import { useOverlayTriggerState } from 'react-stately'; import { Dataset } from '../../../../core/projects/dataset.interface'; import { isAnomalyDomain } from '../../../../core/projects/domains'; -import { FUX_NOTIFICATION_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; -import { useUserGlobalSettings } from '../../../../core/user-settings/hooks/use-global-settings.hook'; -import { usePrevious } from '../../../../hooks/use-previous/use-previous.hook'; import { TooltipWithDisableButton } from '../../../../shared/components/custom-tooltip/tooltip-with-disable-button'; -import { FuxNotification } from '../../../../shared/components/fux-notification/fux-notification.component'; +import { AnnotateInteractivelyNotification } from '../../../../shared/components/fux-notification/notifications/annotate-interactively-notification.component'; import { TabItem } from '../../../../shared/components/tabs/tabs.interface'; import { TruncatedText } from '../../../../shared/components/truncated-text/truncated-text.component'; import { useActiveTab } from '../../../../shared/hooks/use-active-tab.hook'; @@ -33,7 +30,6 @@ import { DATASET_TABS_TO_PATH, DatasetChapters, NO_MEDIA_MESSAGE } from './utils import classes from './project-dataset.module.scss'; export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { - const settings = useUserGlobalSettings(); const navigate = useNavigate(); const { media } = useMedia(); const selectedDataset = dataset; @@ -67,24 +63,6 @@ export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { const triggerRef = useRef(null); const fuxState = useOverlayTriggerState({}); - const isFuxNotificationEnabled = settings.config[FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]?.isEnabled; - const prevFuxEnabled = usePrevious(isFuxNotificationEnabled); - - useEffect(() => { - if (isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { - fuxState.open(); - } else if (!isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { - fuxState.close(); - } - }, [fuxState, isFuxNotificationEnabled, prevFuxEnabled]); - - const handleCloseNotification = () => { - isFuxNotificationEnabled && - settings.saveConfig({ - ...settings.config, - [FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]: { isEnabled: false }, - }); - }; useOpenNotificationToast(); @@ -136,13 +114,7 @@ export const DatasetTabPanel = ({ dataset }: { dataset: Dataset }) => { {annotateButtonText === 'Annotate interactively' && !isAnnotatorDisabled && ( - + )} diff --git a/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx b/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx new file mode 100644 index 0000000000..b7567fca30 --- /dev/null +++ b/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx @@ -0,0 +1,48 @@ +// Copyright (C) 2022-2025 Intel Corporation +// LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE + +import { ComponentProps, MutableRefObject, ReactNode, useEffect } from 'react'; + +import { OverlayTriggerState } from 'react-stately'; + +import { FUX_NOTIFICATION_KEYS } from '../../../../core/user-settings/dtos/user-settings.interface'; +import { useUserGlobalSettings } from '../../../../core/user-settings/hooks/use-global-settings.hook'; +import { usePrevious } from '../../../../hooks/use-previous/use-previous.hook'; +import { FuxNotification } from '../fux-notification.component'; + +interface AnnotateInteractivelyNotificationProps { + triggerRef: MutableRefObject; + state: OverlayTriggerState; +} + +export const AnnotateInteractivelyNotification = ({ triggerRef, state }: AnnotateInteractivelyNotificationProps) => { + const settings = useUserGlobalSettings(); + const isFuxNotificationEnabled = settings.config[FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]?.isEnabled; + const prevFuxEnabled = usePrevious(isFuxNotificationEnabled); + + useEffect(() => { + if (isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { + state.open(); + } else if (!isFuxNotificationEnabled && prevFuxEnabled !== isFuxNotificationEnabled) { + state.close(); + } + }, [state, isFuxNotificationEnabled, prevFuxEnabled]); + + const handleCloseNotification = () => { + isFuxNotificationEnabled && + settings.saveConfig({ + ...settings.config, + [FUX_NOTIFICATION_KEYS.ANNOTATE_INTERACTIVELY]: { isEnabled: false }, + }); + }; + + return ( + + ); +}; From 668a1d883799ed792052360c7cbf608fcfa60a0a Mon Sep 17 00:00:00 2001 From: "Romanowska, Katarzyna" Date: Fri, 11 Jul 2025 10:12:04 +0200 Subject: [PATCH 16/16] remove unused imports --- .../annotate-interactively-notification.component.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx b/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx index b7567fca30..cbbeeb2366 100644 --- a/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx +++ b/web_ui/src/shared/components/fux-notification/notifications/annotate-interactively-notification.component.tsx @@ -1,7 +1,7 @@ // Copyright (C) 2022-2025 Intel Corporation // LIMITED EDGE SOFTWARE DISTRIBUTION LICENSE -import { ComponentProps, MutableRefObject, ReactNode, useEffect } from 'react'; +import { MutableRefObject, useEffect } from 'react'; import { OverlayTriggerState } from 'react-stately';