mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-30 19:36:41 +00:00
Fix save enable/disable on customize navigation page (#23142)
* Fix save enable/disable on customize navigation page * add e2e tests * fix failing and flaky tests * address comments * Address comment and fix curated assets e2e tests
This commit is contained in:
parent
8177e529bc
commit
57d0d70e99
@ -266,7 +266,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator.locator('.rule--operator .ant-select'),
|
ruleLocator.locator('.rule--operator .ant-select'),
|
||||||
'=='
|
'Is'
|
||||||
);
|
);
|
||||||
|
|
||||||
await ruleLocator
|
await ruleLocator
|
||||||
@ -340,7 +340,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator1.locator('.rule--operator .ant-select'),
|
ruleLocator1.locator('.rule--operator .ant-select'),
|
||||||
'Is not null'
|
'Is Set'
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Add Condition' }).click();
|
await page.getByRole('button', { name: 'Add Condition' }).click();
|
||||||
@ -357,7 +357,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator2.locator('.rule--operator .ant-select'),
|
ruleLocator2.locator('.rule--operator .ant-select'),
|
||||||
'=='
|
'Is'
|
||||||
);
|
);
|
||||||
await ruleLocator2
|
await ruleLocator2
|
||||||
.locator('.rule--value .rule--widget--BOOLEAN .ant-switch')
|
.locator('.rule--value .rule--widget--BOOLEAN .ant-switch')
|
||||||
@ -444,7 +444,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator1.locator('.rule--operator .ant-select'),
|
ruleLocator1.locator('.rule--operator .ant-select'),
|
||||||
'=='
|
'Is'
|
||||||
);
|
);
|
||||||
await ruleLocator1
|
await ruleLocator1
|
||||||
.locator('.rule--value .rule--widget--BOOLEAN .ant-switch')
|
.locator('.rule--value .rule--widget--BOOLEAN .ant-switch')
|
||||||
@ -570,7 +570,8 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator1.locator('.rule--value .ant-select'),
|
ruleLocator1.locator('.rule--value .ant-select'),
|
||||||
'admin'
|
'admin',
|
||||||
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Add Condition' }).click();
|
await page.getByRole('button', { name: 'Add Condition' }).click();
|
||||||
@ -588,7 +589,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator2.locator('.rule--operator .ant-select'),
|
ruleLocator2.locator('.rule--operator .ant-select'),
|
||||||
'=='
|
'Is'
|
||||||
);
|
);
|
||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
@ -610,7 +611,7 @@ test.describe('Curated Assets Widget', () => {
|
|||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
ruleLocator3.locator('.rule--operator .ant-select'),
|
ruleLocator3.locator('.rule--operator .ant-select'),
|
||||||
'!='
|
'Is Not'
|
||||||
);
|
);
|
||||||
await selectOption(
|
await selectOption(
|
||||||
page,
|
page,
|
||||||
|
@ -126,7 +126,7 @@ test.describe('Navigation Blocker Tests', () => {
|
|||||||
await expect(adminPage.locator('.ant-modal')).toBeVisible();
|
await expect(adminPage.locator('.ant-modal')).toBeVisible();
|
||||||
|
|
||||||
// Click "Save changes" button (should save changes and then navigate)
|
// Click "Save changes" button (should save changes and then navigate)
|
||||||
const saveResponse = adminPage.waitForResponse('api/v1/docStore/**');
|
const saveResponse = adminPage.waitForResponse('api/v1/docStore*');
|
||||||
await adminPage.locator('button:has-text("Save changes")').click();
|
await adminPage.locator('button:has-text("Save changes")').click();
|
||||||
|
|
||||||
// Wait for save operation to complete
|
// Wait for save operation to complete
|
||||||
|
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2025 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 { expect, Page, test as base } from '@playwright/test';
|
||||||
|
import { GlobalSettingOptions } from '../../constant/settings';
|
||||||
|
import { PersonaClass } from '../../support/persona/PersonaClass';
|
||||||
|
import { UserClass } from '../../support/user/UserClass';
|
||||||
|
import { performAdminLogin } from '../../utils/admin';
|
||||||
|
import { redirectToHomePage } from '../../utils/common';
|
||||||
|
import { setUserDefaultPersona } from '../../utils/customizeLandingPage';
|
||||||
|
import { settingClick } from '../../utils/sidebar';
|
||||||
|
|
||||||
|
const adminUser = new UserClass();
|
||||||
|
const persona = new PersonaClass();
|
||||||
|
|
||||||
|
const test = base.extend<{ page: Page }>({
|
||||||
|
page: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await adminUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
|
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||||
|
await adminUser.create(apiContext);
|
||||||
|
await adminUser.setAdminRole(apiContext);
|
||||||
|
await persona.create(apiContext, [adminUser.responseData.id]);
|
||||||
|
await afterAction();
|
||||||
|
});
|
||||||
|
|
||||||
|
const navigateToPersonaNavigation = async (page: Page) => {
|
||||||
|
const getPersonas = page.waitForResponse('/api/v1/personas*');
|
||||||
|
await settingClick(page, GlobalSettingOptions.PERSONA);
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
await getPersonas;
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByTestId(`persona-details-card-${persona.responseData.name}`)
|
||||||
|
.click();
|
||||||
|
|
||||||
|
await page.getByTestId('navigation').click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
};
|
||||||
|
|
||||||
|
test.describe('Settings Navigation Page Tests', () => {
|
||||||
|
test('should update navigation sidebar', async ({ page }) => {
|
||||||
|
// Create and set default persona
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||||
|
|
||||||
|
// Go to navigation in persona
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
|
||||||
|
// Verify page loads with expected elements
|
||||||
|
await expect(page.getByRole('tree')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('save-button')).toBeVisible();
|
||||||
|
await expect(page.getByTestId('reset-button')).toBeVisible();
|
||||||
|
|
||||||
|
// Save button should be disabled initially
|
||||||
|
await expect(page.getByTestId('save-button')).toBeEnabled();
|
||||||
|
|
||||||
|
// Make changes to enable save button
|
||||||
|
const exploreSwitch = page
|
||||||
|
.locator('.ant-tree-title:has-text("Explore")')
|
||||||
|
.locator('.ant-switch');
|
||||||
|
|
||||||
|
await exploreSwitch.click();
|
||||||
|
|
||||||
|
// Check save is enabled and click save
|
||||||
|
await expect(page.getByTestId('save-button')).toBeEnabled();
|
||||||
|
|
||||||
|
const saveResponse = page.waitForResponse('api/v1/docStore');
|
||||||
|
await page.getByTestId('save-button').click();
|
||||||
|
await saveResponse;
|
||||||
|
|
||||||
|
// Check the navigation bar if the changes reflect
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
|
||||||
|
// Verify the navigation change is reflected in the sidebar
|
||||||
|
await expect(page.getByTestId('app-bar-item-explore')).not.toBeVisible();
|
||||||
|
|
||||||
|
// Clean up: Restore original state
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
await exploreSwitch.click();
|
||||||
|
|
||||||
|
const restoreResponse = page.waitForResponse('api/v1/docStore/*');
|
||||||
|
await page.getByTestId('save-button').click();
|
||||||
|
await restoreResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show navigation blocker when leaving with unsaved changes', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Create persona and navigate to navigation page
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
|
||||||
|
// Make changes to trigger unsaved state
|
||||||
|
const navigateSwitch = page
|
||||||
|
.locator('.ant-tree-title:has-text("Explore")')
|
||||||
|
.locator('.ant-switch');
|
||||||
|
|
||||||
|
await navigateSwitch.click();
|
||||||
|
|
||||||
|
// Verify save button is enabled
|
||||||
|
await expect(page.getByTestId('save-button')).toBeEnabled();
|
||||||
|
|
||||||
|
// Try to navigate away - should show navigation blocker
|
||||||
|
await page
|
||||||
|
.getByTestId('left-sidebar')
|
||||||
|
.getByTestId('app-bar-item-settings')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Verify navigation blocker modal appears
|
||||||
|
await expect(page.getByTestId('unsaved-changes-modal-title')).toContainText(
|
||||||
|
'Unsaved changes'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('unsaved-changes-modal-description')
|
||||||
|
).toContainText('Do you want to save or discard changes?');
|
||||||
|
|
||||||
|
// Verify modal buttons
|
||||||
|
await expect(page.getByTestId('unsaved-changes-modal-save')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('unsaved-changes-modal-discard')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Test discard changes
|
||||||
|
await page.getByTestId('unsaved-changes-modal-discard').click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// Should navigate away and changes should be discarded
|
||||||
|
await expect(page).toHaveURL(/.*settings.*/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should save changes and navigate when "Save changes" is clicked in blocker', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Create persona and navigate to navigation page
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
|
||||||
|
// Make changes
|
||||||
|
const navigateSwitch = page
|
||||||
|
.locator('.ant-tree-title:has-text("Insights")')
|
||||||
|
.locator('.ant-switch');
|
||||||
|
|
||||||
|
await navigateSwitch.click();
|
||||||
|
|
||||||
|
// Try to navigate away
|
||||||
|
await page
|
||||||
|
.getByTestId('left-sidebar')
|
||||||
|
.getByTestId('app-bar-item-settings')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
// Click "Save changes" to save and navigate
|
||||||
|
const saveResponse = page.waitForResponse('api/v1/docStore');
|
||||||
|
await page.getByTestId('unsaved-changes-modal-save').click();
|
||||||
|
await saveResponse;
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// Should navigate to settings page
|
||||||
|
await expect(page).toHaveURL(/.*settings.*/);
|
||||||
|
|
||||||
|
// Verify changes were saved by checking navigation bar
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
|
||||||
|
// Check if Insights navigation item visibility changed
|
||||||
|
const insightsVisible = await page
|
||||||
|
.getByTestId('left-sidebar')
|
||||||
|
.getByTestId('app-bar-item-insights')
|
||||||
|
.isVisible();
|
||||||
|
|
||||||
|
expect(insightsVisible).toBe(false);
|
||||||
|
|
||||||
|
// Clean up: Restore original state
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
await navigateSwitch.click();
|
||||||
|
|
||||||
|
const restoreResponse = page.waitForResponse('api/v1/docStore/*');
|
||||||
|
await page.getByTestId('save-button').click();
|
||||||
|
await restoreResponse;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle reset functionality and prevent navigation blocker after save', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
// Create persona and navigate to navigation page
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
await setUserDefaultPersona(page, persona.responseData.displayName);
|
||||||
|
await navigateToPersonaNavigation(page);
|
||||||
|
|
||||||
|
// Make changes
|
||||||
|
const domainSwitch = page
|
||||||
|
.locator('.ant-tree-title:has-text("Domains")')
|
||||||
|
.locator('.ant-switch');
|
||||||
|
|
||||||
|
await domainSwitch.click();
|
||||||
|
|
||||||
|
// Verify save button is enabled
|
||||||
|
await expect(page.getByTestId('save-button')).toBeEnabled();
|
||||||
|
|
||||||
|
expect(await domainSwitch.isChecked()).toBeFalsy();
|
||||||
|
|
||||||
|
// Test reset functionality
|
||||||
|
await page.getByTestId('reset-button').click();
|
||||||
|
|
||||||
|
// Verify navigation blocker modal appears
|
||||||
|
await expect(page.getByTestId('unsaved-changes-modal-title')).toContainText(
|
||||||
|
'Reset Default Layout'
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('unsaved-changes-modal-description')
|
||||||
|
).toContainText('Are you sure you want to apply the "Default Layout"?');
|
||||||
|
|
||||||
|
// Verify modal buttons
|
||||||
|
await expect(page.getByTestId('unsaved-changes-modal-save')).toBeVisible();
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('unsaved-changes-modal-discard')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
|
// Test discard changes
|
||||||
|
await page.getByTestId('unsaved-changes-modal-save').click();
|
||||||
|
await page.waitForLoadState('networkidle');
|
||||||
|
|
||||||
|
// Verify reset worked - save button disabled and state reverted
|
||||||
|
expect(await domainSwitch.isChecked()).toBeTruthy();
|
||||||
|
await expect(page.getByTestId('save-button')).toBeEnabled();
|
||||||
|
});
|
||||||
|
});
|
@ -33,6 +33,7 @@ export const UnsavedChangesModal: React.FC<UnsavedChangesModalProps> = ({
|
|||||||
centered
|
centered
|
||||||
closable
|
closable
|
||||||
className="unsaved-changes-modal-container"
|
className="unsaved-changes-modal-container"
|
||||||
|
data-testid="unsaved-changes-modal"
|
||||||
footer={null}
|
footer={null}
|
||||||
open={open}
|
open={open}
|
||||||
width={400}
|
width={400}
|
||||||
@ -44,21 +45,30 @@ export const UnsavedChangesModal: React.FC<UnsavedChangesModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="unsaved-changes-modal-content">
|
<div className="unsaved-changes-modal-content">
|
||||||
<Typography.Title className="unsaved-changes-modal-title" level={5}>
|
<Typography.Title
|
||||||
|
className="unsaved-changes-modal-title"
|
||||||
|
data-testid="unsaved-changes-modal-title"
|
||||||
|
level={5}>
|
||||||
{title}
|
{title}
|
||||||
</Typography.Title>
|
</Typography.Title>
|
||||||
<Typography.Text className="unsaved-changes-modal-description">
|
<Typography.Text
|
||||||
|
className="unsaved-changes-modal-description"
|
||||||
|
data-testid="unsaved-changes-modal-description">
|
||||||
{description}
|
{description}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="unsaved-changes-modal-actions">
|
<div className="unsaved-changes-modal-actions">
|
||||||
<Button className="unsaved-changes-modal-discard" onClick={onDiscard}>
|
<Button
|
||||||
|
className="unsaved-changes-modal-discard"
|
||||||
|
data-testid="unsaved-changes-modal-discard"
|
||||||
|
onClick={onDiscard}>
|
||||||
{discardText}
|
{discardText}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="unsaved-changes-modal-save"
|
className="unsaved-changes-modal-save"
|
||||||
|
data-testid="unsaved-changes-modal-save"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={onSave}>
|
onClick={onSave}>
|
||||||
|
@ -77,7 +77,7 @@ export const mockCustomizedLayout1: Array<WidgetConfig> = [
|
|||||||
{
|
{
|
||||||
h: 3,
|
h: 3,
|
||||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
static: false,
|
static: false,
|
||||||
@ -104,7 +104,7 @@ export const mockCustomizedLayout2: Array<WidgetConfig> = [
|
|||||||
{
|
{
|
||||||
h: 6,
|
h: 6,
|
||||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
static: false,
|
static: false,
|
||||||
@ -158,7 +158,7 @@ export const mockCurrentAddWidget = [
|
|||||||
{
|
{
|
||||||
h: 3,
|
h: 3,
|
||||||
i: 'KnowledgePanel.ActivityFeed',
|
i: 'KnowledgePanel.ActivityFeed',
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
static: false,
|
static: false,
|
||||||
@ -174,7 +174,7 @@ export const mockCurrentAddWidget = [
|
|||||||
{
|
{
|
||||||
h: 3,
|
h: 3,
|
||||||
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
||||||
w: 4,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 6,
|
y: 6,
|
||||||
isDraggable: false,
|
isDraggable: false,
|
||||||
@ -187,7 +187,7 @@ export const mockAddWidgetReturnValues = [
|
|||||||
h: 3,
|
h: 3,
|
||||||
i: 'KnowledgePanel.ActivityFeed',
|
i: 'KnowledgePanel.ActivityFeed',
|
||||||
static: false,
|
static: false,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
@ -204,7 +204,7 @@ export const mockAddWidgetReturnValues = [
|
|||||||
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
||||||
isDraggable: false,
|
isDraggable: false,
|
||||||
static: false,
|
static: false,
|
||||||
w: 4,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 100,
|
y: 100,
|
||||||
},
|
},
|
||||||
@ -216,7 +216,7 @@ export const mockAddWidgetReturnValues2 = [
|
|||||||
h: 3,
|
h: 3,
|
||||||
i: 'KnowledgePanel.ActivityFeed',
|
i: 'KnowledgePanel.ActivityFeed',
|
||||||
static: false,
|
static: false,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
@ -233,7 +233,7 @@ export const mockAddWidgetReturnValues2 = [
|
|||||||
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
i: 'ExtraWidget.EmptyWidgetPlaceholder',
|
||||||
isDraggable: false,
|
isDraggable: false,
|
||||||
static: false,
|
static: false,
|
||||||
w: 4,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 100,
|
y: 100,
|
||||||
},
|
},
|
||||||
|
@ -38,7 +38,7 @@ export const mockDefaultLayout: Array<WidgetConfig> = [
|
|||||||
{
|
{
|
||||||
h: 6,
|
h: 6,
|
||||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
static: false,
|
static: false,
|
||||||
@ -62,7 +62,7 @@ export const mockDefaultLayout: Array<WidgetConfig> = [
|
|||||||
{
|
{
|
||||||
h: 3,
|
h: 3,
|
||||||
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
|
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 9,
|
y: 9,
|
||||||
static: false,
|
static: false,
|
||||||
@ -89,7 +89,7 @@ export const mockCustomizedLayout: Array<WidgetConfig> = [
|
|||||||
{
|
{
|
||||||
h: 6,
|
h: 6,
|
||||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||||
w: 3,
|
w: 2,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
static: false,
|
static: false,
|
||||||
|
@ -18,6 +18,7 @@ import { useCallback, useMemo, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ReactComponent as IconDown } from '../../assets/svg/ic-arrow-down.svg';
|
import { ReactComponent as IconDown } from '../../assets/svg/ic-arrow-down.svg';
|
||||||
import { ReactComponent as IconRight } from '../../assets/svg/ic-arrow-right.svg';
|
import { ReactComponent as IconRight } from '../../assets/svg/ic-arrow-right.svg';
|
||||||
|
import { NavigationBlocker } from '../../components/common/NavigationBlocker/NavigationBlocker';
|
||||||
import { CustomizablePageHeader } from '../../components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader';
|
import { CustomizablePageHeader } from '../../components/MyData/CustomizableComponents/CustomizablePageHeader/CustomizablePageHeader';
|
||||||
import PageLayoutV1 from '../../components/PageLayoutV1/PageLayoutV1';
|
import PageLayoutV1 from '../../components/PageLayoutV1/PageLayoutV1';
|
||||||
import { NavigationItem } from '../../generated/system/ui/uiCustomization';
|
import { NavigationItem } from '../../generated/system/ui/uiCustomization';
|
||||||
@ -46,20 +47,11 @@ export const SettingsNavigationPage = ({ onSave }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const disableSave = useMemo(() => {
|
const disableSave = useMemo(() => {
|
||||||
// Get the initial hidden keys from the current navigation
|
// Get the current navigation items from the modified tree data
|
||||||
const initialHiddenKeys =
|
const currentNavigationItems = getNavigationItems(treeData, hiddenKeys);
|
||||||
getHiddenKeysFromNavigationItems(currentNavigation);
|
|
||||||
|
|
||||||
// Get the current navigation items from the tree data
|
// Compare the entire structure including order, names, hidden status, and all properties
|
||||||
const currentNavigationItems =
|
return isEqual(currentNavigation, currentNavigationItems);
|
||||||
getNavigationItems(treeData, hiddenKeys) || [];
|
|
||||||
|
|
||||||
// Get the current hidden keys from the current navigation items
|
|
||||||
const currentHiddenKeys =
|
|
||||||
getHiddenKeysFromNavigationItems(currentNavigationItems) || [];
|
|
||||||
|
|
||||||
// Check if the initial hidden keys are the same as the current hidden keys
|
|
||||||
return isEqual(initialHiddenKeys, currentHiddenKeys);
|
|
||||||
}, [currentNavigation, treeData, hiddenKeys]);
|
}, [currentNavigation, treeData, hiddenKeys]);
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
@ -149,37 +141,39 @@ export const SettingsNavigationPage = ({ onSave }: Props) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageLayoutV1 className="bg-grey" pageTitle="Settings Navigation Page">
|
<NavigationBlocker enabled={!disableSave} onConfirm={handleSave}>
|
||||||
<Row gutter={[0, 20]}>
|
<PageLayoutV1 className="bg-grey" pageTitle="Settings Navigation Page">
|
||||||
<Col span={24}>
|
<Row gutter={[0, 20]}>
|
||||||
<CustomizablePageHeader
|
<Col span={24}>
|
||||||
disableSave={disableSave}
|
<CustomizablePageHeader
|
||||||
personaName={t('label.customize-your-navigation')}
|
disableSave={disableSave}
|
||||||
onReset={handleReset}
|
personaName={t('label.customize-your-navigation')}
|
||||||
onSave={handleSave}
|
onReset={handleReset}
|
||||||
/>
|
onSave={handleSave}
|
||||||
</Col>
|
|
||||||
|
|
||||||
<Col span={24}>
|
|
||||||
<Card
|
|
||||||
bordered={false}
|
|
||||||
className="custom-navigation-tree-container"
|
|
||||||
title="Navigation Menus">
|
|
||||||
<Tree
|
|
||||||
autoExpandParent
|
|
||||||
blockNode
|
|
||||||
defaultExpandAll
|
|
||||||
showIcon
|
|
||||||
draggable={{ icon: <HolderOutlined /> }}
|
|
||||||
itemHeight={48}
|
|
||||||
switcherIcon={switcherIcon}
|
|
||||||
titleRender={titleRenderer}
|
|
||||||
treeData={treeData}
|
|
||||||
onDrop={onDrop}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Col>
|
||||||
</Col>
|
|
||||||
</Row>
|
<Col span={24}>
|
||||||
</PageLayoutV1>
|
<Card
|
||||||
|
bordered={false}
|
||||||
|
className="custom-navigation-tree-container"
|
||||||
|
title="Navigation Menus">
|
||||||
|
<Tree
|
||||||
|
autoExpandParent
|
||||||
|
blockNode
|
||||||
|
defaultExpandAll
|
||||||
|
showIcon
|
||||||
|
draggable={{ icon: <HolderOutlined /> }}
|
||||||
|
itemHeight={48}
|
||||||
|
switcherIcon={switcherIcon}
|
||||||
|
titleRender={titleRenderer}
|
||||||
|
treeData={treeData}
|
||||||
|
onDrop={onDrop}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</PageLayoutV1>
|
||||||
|
</NavigationBlocker>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -575,7 +575,41 @@ class AdvancedSearchClassBase {
|
|||||||
...this.baseConfig,
|
...this.baseConfig,
|
||||||
types: this.configTypes,
|
types: this.configTypes,
|
||||||
widgets: this.configWidgets,
|
widgets: this.configWidgets,
|
||||||
operators: this.configOperators,
|
operators: {
|
||||||
|
...this.configOperators,
|
||||||
|
like: {
|
||||||
|
...this.baseConfig.operators.like,
|
||||||
|
elasticSearchQueryType: 'wildcard',
|
||||||
|
},
|
||||||
|
...(isExplorePage
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
equal: {
|
||||||
|
...this.baseConfig.operators.equal,
|
||||||
|
label: t('label.is'),
|
||||||
|
},
|
||||||
|
not_equal: {
|
||||||
|
...this.baseConfig.operators.not_equal,
|
||||||
|
label: t('label.is-not'),
|
||||||
|
},
|
||||||
|
select_equals: {
|
||||||
|
...this.baseConfig.operators.select_equals,
|
||||||
|
label: t('label.is'),
|
||||||
|
},
|
||||||
|
select_not_equals: {
|
||||||
|
...this.baseConfig.operators.select_not_equals,
|
||||||
|
label: t('label.is-not'),
|
||||||
|
},
|
||||||
|
is_null: {
|
||||||
|
...this.baseConfig.operators.is_null,
|
||||||
|
label: t('label.is-not-set'),
|
||||||
|
},
|
||||||
|
is_not_null: {
|
||||||
|
...this.baseConfig.operators.is_not_null,
|
||||||
|
label: t('label.is-set'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
settings: {
|
settings: {
|
||||||
...this.baseConfig.settings,
|
...this.baseConfig.settings,
|
||||||
showLabels: isExplorePage,
|
showLabels: isExplorePage,
|
||||||
|
@ -23,6 +23,15 @@ import { Document } from '../generated/entity/docStore/document';
|
|||||||
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
|
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
|
||||||
import customizeMyDataPageClassBase from './CustomizeMyDataPageClassBase';
|
import customizeMyDataPageClassBase from './CustomizeMyDataPageClassBase';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures widget width doesn't exceed the maximum allowed width of 2
|
||||||
|
*/
|
||||||
|
export const getConstrainedWidgetWidth = (width: number): number => {
|
||||||
|
const maxWidth = 2;
|
||||||
|
|
||||||
|
return Math.min(width, maxWidth);
|
||||||
|
};
|
||||||
|
|
||||||
export const getNewWidgetPlacement = (
|
export const getNewWidgetPlacement = (
|
||||||
currentLayout: WidgetConfig[],
|
currentLayout: WidgetConfig[],
|
||||||
widgetWidth: number
|
widgetWidth: number
|
||||||
@ -284,16 +293,18 @@ export const getAddWidgetHandler =
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!currentLayout || currentLayout.length === 0) {
|
if (!currentLayout || currentLayout.length === 0) {
|
||||||
|
const constrainedWidth = getConstrainedWidgetWidth(widgetWidth);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
w: widgetWidth,
|
w: constrainedWidth,
|
||||||
h: widgetHeight,
|
h: widgetHeight,
|
||||||
i: widgetFQN,
|
i: widgetFQN,
|
||||||
static: false,
|
static: false,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
},
|
},
|
||||||
createPlaceholderWidget(widgetWidth, 0),
|
createPlaceholderWidget(constrainedWidth, 0),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -306,8 +317,9 @@ export const getAddWidgetHandler =
|
|||||||
// Calculate position at the end of all existing widgets
|
// Calculate position at the end of all existing widgets
|
||||||
const placement = getNewWidgetPlacement(regularWidgets, widgetWidth);
|
const placement = getNewWidgetPlacement(regularWidgets, widgetWidth);
|
||||||
|
|
||||||
|
const constrainedWidth = getConstrainedWidgetWidth(widgetWidth);
|
||||||
const newWidget = {
|
const newWidget = {
|
||||||
w: widgetWidth,
|
w: constrainedWidth,
|
||||||
h: widgetHeight,
|
h: widgetHeight,
|
||||||
i: widgetFQN,
|
i: widgetFQN,
|
||||||
static: false,
|
static: false,
|
||||||
@ -319,14 +331,15 @@ export const getAddWidgetHandler =
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Replace specific placeholder
|
// Replace specific placeholder
|
||||||
|
const constrainedWidth = getConstrainedWidgetWidth(widgetWidth);
|
||||||
const updatedWidgets = currentLayout.map((widget: WidgetConfig) => {
|
const updatedWidgets = currentLayout.map((widget: WidgetConfig) => {
|
||||||
if (widget.i === placeholderWidgetKey) {
|
if (widget.i === placeholderWidgetKey) {
|
||||||
return {
|
return {
|
||||||
...widget,
|
...widget,
|
||||||
i: widgetFQN,
|
i: widgetFQN,
|
||||||
h: widgetHeight,
|
h: widgetHeight,
|
||||||
w: widgetWidth,
|
w: constrainedWidth,
|
||||||
x: Math.min(widget.x, maxGridSize - widgetWidth),
|
x: Math.min(widget.x, maxGridSize - constrainedWidth),
|
||||||
static: false,
|
static: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user