mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-11-04 04:29:13 +00:00 
			
		
		
		
	MINOR: fix the widget placement when adding new one (#17329)
* fix the widget placement when adding new one * fix the widget not adding in the right side add placeholders * added unit test for the same * fix sonar
This commit is contained in:
		
							parent
							
								
									a098c20c7c
								
							
						
					
					
						commit
						785e450e28
					
				@ -0,0 +1,184 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  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 { expect, Page, test as base } from '@playwright/test';
 | 
			
		||||
import { PersonaClass } from '../../support/persona/PersonaClass';
 | 
			
		||||
import { UserClass } from '../../support/user/UserClass';
 | 
			
		||||
import { performAdminLogin } from '../../utils/admin';
 | 
			
		||||
import { redirectToHomePage, toastNotification } from '../../utils/common';
 | 
			
		||||
import {
 | 
			
		||||
  checkAllDefaultWidgets,
 | 
			
		||||
  navigateToCustomizeLandingPage,
 | 
			
		||||
  removeAndCheckWidget,
 | 
			
		||||
  setUserDefaultPersona,
 | 
			
		||||
} from '../../utils/customizeLandingPage';
 | 
			
		||||
 | 
			
		||||
const adminUser = new UserClass();
 | 
			
		||||
const persona = new PersonaClass();
 | 
			
		||||
const persona2 = new PersonaClass();
 | 
			
		||||
 | 
			
		||||
const test = base.extend<{ adminPage: Page; userPage: Page }>({
 | 
			
		||||
  adminPage: async ({ browser }, use) => {
 | 
			
		||||
    const adminPage = await browser.newPage();
 | 
			
		||||
    await adminUser.login(adminPage);
 | 
			
		||||
    await use(adminPage);
 | 
			
		||||
    await adminPage.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 persona2.create(apiContext);
 | 
			
		||||
  await afterAction();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
base.afterAll('Cleanup', async ({ browser }) => {
 | 
			
		||||
  const { afterAction, apiContext } = await performAdminLogin(browser);
 | 
			
		||||
  await adminUser.delete(apiContext);
 | 
			
		||||
  await persona.delete(apiContext);
 | 
			
		||||
  await persona2.delete(apiContext);
 | 
			
		||||
  await afterAction();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
test.describe('Customize Landing Page Flow', () => {
 | 
			
		||||
  test('Check all default widget present', async ({ adminPage }) => {
 | 
			
		||||
    await redirectToHomePage(adminPage);
 | 
			
		||||
    await adminPage.getByTestId('welcome-screen-close-btn').click();
 | 
			
		||||
    await checkAllDefaultWidgets(adminPage);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Remove and check widget', async ({ adminPage }) => {
 | 
			
		||||
    await redirectToHomePage(adminPage);
 | 
			
		||||
    await setUserDefaultPersona(adminPage, persona.responseData.displayName);
 | 
			
		||||
    await navigateToCustomizeLandingPage(adminPage, {
 | 
			
		||||
      personaName: persona.responseData.name,
 | 
			
		||||
      customPageDataResponse: 404,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await removeAndCheckWidget(adminPage, {
 | 
			
		||||
      widgetTestId: 'activity-feed-widget',
 | 
			
		||||
      widgetKey: 'KnowledgePanel.ActivityFeed',
 | 
			
		||||
    });
 | 
			
		||||
    await removeAndCheckWidget(adminPage, {
 | 
			
		||||
      widgetTestId: 'following-widget',
 | 
			
		||||
      widgetKey: 'KnowledgePanel.Following',
 | 
			
		||||
    });
 | 
			
		||||
    await removeAndCheckWidget(adminPage, {
 | 
			
		||||
      widgetTestId: 'kpi-widget',
 | 
			
		||||
      widgetKey: 'KnowledgePanel.KPI',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const saveResponse = adminPage.waitForResponse('/api/v1/docStore');
 | 
			
		||||
    await adminPage.click('[data-testid="save-button"]');
 | 
			
		||||
    await saveResponse;
 | 
			
		||||
 | 
			
		||||
    await toastNotification(adminPage, 'Page layout created successfully.');
 | 
			
		||||
    await redirectToHomePage(adminPage);
 | 
			
		||||
 | 
			
		||||
    // Check if removed widgets are not present on landing adminPage
 | 
			
		||||
    await expect(
 | 
			
		||||
      adminPage.locator('[data-testid="activity-feed-widget"]')
 | 
			
		||||
    ).not.toBeVisible();
 | 
			
		||||
    await expect(
 | 
			
		||||
      adminPage.locator('[data-testid="following-widget"]')
 | 
			
		||||
    ).not.toBeVisible();
 | 
			
		||||
    await expect(
 | 
			
		||||
      adminPage.locator('[data-testid="kpi-widget"]')
 | 
			
		||||
    ).not.toBeVisible();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Remove and add the widget in the same placeholder', async ({
 | 
			
		||||
    adminPage,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await redirectToHomePage(adminPage);
 | 
			
		||||
 | 
			
		||||
    const feedResponse = adminPage.waitForResponse(
 | 
			
		||||
      '/api/v1/feed?type=Conversation&*'
 | 
			
		||||
    );
 | 
			
		||||
    await navigateToCustomizeLandingPage(adminPage, {
 | 
			
		||||
      personaName: persona2.responseData.name,
 | 
			
		||||
      customPageDataResponse: 404,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await feedResponse;
 | 
			
		||||
 | 
			
		||||
    await adminPage.waitForSelector('[data-testid="activity-feed-widget"]');
 | 
			
		||||
 | 
			
		||||
    const followingElementStyle = await adminPage
 | 
			
		||||
      .locator('[id="KnowledgePanel.Following"]')
 | 
			
		||||
      .evaluate((node) => {
 | 
			
		||||
        const computedStyle = window.getComputedStyle(node);
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
          transform: computedStyle.transform,
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    // Remove and check the placement of Following widget.
 | 
			
		||||
    await adminPage.click(
 | 
			
		||||
      '[data-testid="following-widget"] [data-testid="remove-widget-button"]'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    await adminPage.waitForSelector('[data-testid="following-widget"]', {
 | 
			
		||||
      state: 'detached',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    await adminPage.waitForSelector(
 | 
			
		||||
      '[data-testid*="KnowledgePanel.Following"][data-testid$="EmptyWidgetPlaceholder"]'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Add KPI widget in the same placeholder
 | 
			
		||||
    const getWidgetList = adminPage.waitForResponse(
 | 
			
		||||
      'api/v1/docStore?fqnPrefix=KnowledgePanel&*'
 | 
			
		||||
    );
 | 
			
		||||
    await adminPage.click(
 | 
			
		||||
      '[data-testid="KnowledgePanel.Following.EmptyWidgetPlaceholder"] [data-testid="add-widget-button"]'
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    await getWidgetList;
 | 
			
		||||
 | 
			
		||||
    await adminPage.waitForSelector('[role="dialog"].ant-modal');
 | 
			
		||||
 | 
			
		||||
    expect(adminPage.locator('[role="dialog"].ant-modal')).toBeVisible();
 | 
			
		||||
 | 
			
		||||
    await adminPage.click('[data-testid="KPI-widget-tab-label"]');
 | 
			
		||||
 | 
			
		||||
    await adminPage
 | 
			
		||||
      .locator('.ant-tabs-tabpane-active [data-testid="add-widget-button"]')
 | 
			
		||||
      .click();
 | 
			
		||||
 | 
			
		||||
    await adminPage.waitForSelector('[role="dialog"].ant-modal', {
 | 
			
		||||
      state: 'detached',
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const kpiElement = adminPage.locator('[id^="KnowledgePanel.KPI-"]');
 | 
			
		||||
    const kpiElementStyle = await kpiElement.evaluate((node) => {
 | 
			
		||||
      const computedStyle = window.getComputedStyle(node);
 | 
			
		||||
 | 
			
		||||
      return {
 | 
			
		||||
        transform: computedStyle.transform,
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // Check if the KPI widget is added in the same placeholder,by their transform property or placement.
 | 
			
		||||
    expect(kpiElementStyle.transform).toEqual(followingElementStyle.transform);
 | 
			
		||||
 | 
			
		||||
    const saveResponse = adminPage.waitForResponse('/api/v1/docStore');
 | 
			
		||||
    await adminPage.click('[data-testid="save-button"]');
 | 
			
		||||
    await saveResponse;
 | 
			
		||||
 | 
			
		||||
    await toastNotification(adminPage, 'Page layout created successfully.');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  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 { APIRequestContext } from '@playwright/test';
 | 
			
		||||
import { uuid } from '../../utils/common';
 | 
			
		||||
 | 
			
		||||
type ResponseDataType = {
 | 
			
		||||
  name: string;
 | 
			
		||||
  displayName: string;
 | 
			
		||||
  description: string;
 | 
			
		||||
  id?: string;
 | 
			
		||||
  fullyQualifiedName?: string;
 | 
			
		||||
  users?: string[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export class PersonaClass {
 | 
			
		||||
  id = uuid();
 | 
			
		||||
  data: ResponseDataType;
 | 
			
		||||
  responseData: ResponseDataType;
 | 
			
		||||
 | 
			
		||||
  constructor(data?: ResponseDataType) {
 | 
			
		||||
    this.data = data ?? {
 | 
			
		||||
      name: `PW%Persona-${this.id}`,
 | 
			
		||||
      displayName: `PW Persona ${this.id}`,
 | 
			
		||||
      description: 'playwright for persona description',
 | 
			
		||||
      users: [],
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get() {
 | 
			
		||||
    return this.responseData;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async create(apiContext: APIRequestContext, users?: string[]) {
 | 
			
		||||
    const response = await apiContext.post('/api/v1/personas', {
 | 
			
		||||
      data: { ...this.data, users },
 | 
			
		||||
    });
 | 
			
		||||
    const data = await response.json();
 | 
			
		||||
    this.responseData = data;
 | 
			
		||||
 | 
			
		||||
    return data;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async delete(apiContext: APIRequestContext) {
 | 
			
		||||
    const response = await apiContext.delete(
 | 
			
		||||
      `/api/v1/personas/${this.responseData.id}?hardDelete=true&recursive=false`
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return await response.json();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async patch(apiContext: APIRequestContext, data: Record<string, unknown>[]) {
 | 
			
		||||
    const response = await apiContext.patch(
 | 
			
		||||
      `/api/v1/personas/${this.responseData.id}`,
 | 
			
		||||
      {
 | 
			
		||||
        data,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'application/json-patch+json',
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    this.responseData = await response.json();
 | 
			
		||||
 | 
			
		||||
    return await response.json();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -61,6 +61,19 @@ export class UserClass {
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async setAdminRole(apiContext: APIRequestContext) {
 | 
			
		||||
    return this.patch({
 | 
			
		||||
      apiContext,
 | 
			
		||||
      patchData: [
 | 
			
		||||
        {
 | 
			
		||||
          op: 'replace',
 | 
			
		||||
          path: '/isAdmin',
 | 
			
		||||
          value: true,
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async delete(apiContext: APIRequestContext) {
 | 
			
		||||
    const response = await apiContext.delete(
 | 
			
		||||
      `/api/v1/users/${this.responseData.id}?recursive=false&hardDelete=true`
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,118 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  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 { expect, Page } from '@playwright/test';
 | 
			
		||||
import { GlobalSettingOptions } from '../constant/settings';
 | 
			
		||||
import { visitUserProfilePage } from './common';
 | 
			
		||||
import { settingClick } from './sidebar';
 | 
			
		||||
 | 
			
		||||
export const navigateToCustomizeLandingPage = async (
 | 
			
		||||
  page: Page,
 | 
			
		||||
  { personaName, customPageDataResponse }
 | 
			
		||||
) => {
 | 
			
		||||
  const getPersonas = page.waitForResponse('/api/v1/personas*');
 | 
			
		||||
 | 
			
		||||
  await settingClick(page, GlobalSettingOptions.CUSTOMIZE_LANDING_PAGE);
 | 
			
		||||
 | 
			
		||||
  await getPersonas;
 | 
			
		||||
 | 
			
		||||
  // Navigate to the customize landing page
 | 
			
		||||
  await page.click(
 | 
			
		||||
    `[data-testid="persona-details-card-${personaName}"] [data-testid="customize-page-button"]`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const getCustomPageDataResponse = await page.waitForResponse(
 | 
			
		||||
    `/api/v1/docStore/name/persona.${encodeURIComponent(
 | 
			
		||||
      personaName
 | 
			
		||||
    )}.Page.LandingPage`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  //   await getCustomPageDataResponse;
 | 
			
		||||
  expect(getCustomPageDataResponse.status()).toBe(customPageDataResponse);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const removeAndCheckWidget = async (
 | 
			
		||||
  page: Page,
 | 
			
		||||
  { widgetTestId, widgetKey }
 | 
			
		||||
) => {
 | 
			
		||||
  // Click on remove widget button
 | 
			
		||||
  await page.click(
 | 
			
		||||
    `[data-testid="${widgetTestId}"] [data-testid="remove-widget-button"]`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Check if widget does not exist
 | 
			
		||||
  await page.waitForSelector(`[data-testid="${widgetTestId}"]`, {
 | 
			
		||||
    state: 'detached',
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  // Check if empty widget placeholder is displayed in place of removed widget
 | 
			
		||||
  await page.waitForSelector(
 | 
			
		||||
    `[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Remove empty widget placeholder
 | 
			
		||||
  await page.click(
 | 
			
		||||
    `[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"] [data-testid="remove-widget-button"]`
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Check if empty widget placeholder does not exist
 | 
			
		||||
  await page.waitForSelector(
 | 
			
		||||
    `[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`,
 | 
			
		||||
    { state: 'detached' }
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const checkAllDefaultWidgets = async (
 | 
			
		||||
  page: Page,
 | 
			
		||||
  checkEmptyWidgetPlaceholder = false
 | 
			
		||||
) => {
 | 
			
		||||
  await expect(page.getByTestId('activity-feed-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('following-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('recently-viewed-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('data-assets-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('my-data-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('kpi-widget')).toBeVisible();
 | 
			
		||||
  await expect(page.getByTestId('total-assets-widget')).toBeVisible();
 | 
			
		||||
 | 
			
		||||
  if (checkEmptyWidgetPlaceholder) {
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByTestId('ExtraWidget.EmptyWidgetPlaceholder')
 | 
			
		||||
    ).toBeVisible();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const setUserDefaultPersona = async (
 | 
			
		||||
  page: Page,
 | 
			
		||||
  personaName: string
 | 
			
		||||
) => {
 | 
			
		||||
  await visitUserProfilePage(page);
 | 
			
		||||
 | 
			
		||||
  await page
 | 
			
		||||
    .locator(
 | 
			
		||||
      '[data-testid="user-profile-details"] [data-testid="edit-persona"]'
 | 
			
		||||
    )
 | 
			
		||||
    .click();
 | 
			
		||||
 | 
			
		||||
  await page.waitForSelector(
 | 
			
		||||
    '[role="tooltip"] [data-testid="selectable-list"]'
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const setDefaultPersona = page.waitForResponse('/api/v1/users/*');
 | 
			
		||||
 | 
			
		||||
  await page.getByTitle(personaName).click();
 | 
			
		||||
 | 
			
		||||
  await setDefaultPersona;
 | 
			
		||||
 | 
			
		||||
  await expect(
 | 
			
		||||
    page.locator('[data-testid="user-profile-details"]')
 | 
			
		||||
  ).toContainText(personaName);
 | 
			
		||||
};
 | 
			
		||||
@ -90,7 +90,6 @@ function AddWidgetTabContent({
 | 
			
		||||
                placement="bottom"
 | 
			
		||||
                title={widgetAddable ? '' : t('message.can-not-add-widget')}>
 | 
			
		||||
                <Button
 | 
			
		||||
                  ghost
 | 
			
		||||
                  className="p-x-lg m-t-md"
 | 
			
		||||
                  data-testid="add-widget-button"
 | 
			
		||||
                  disabled={!widgetAddable}
 | 
			
		||||
 | 
			
		||||
@ -208,5 +208,42 @@ export const mockAddWidgetReturnValues = [
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 100,
 | 
			
		||||
  },
 | 
			
		||||
  { h: 3, i: 'KnowledgePanel.Following-1', static: false, w: 1, x: 0, y: 6 },
 | 
			
		||||
  { h: 3, i: 'KnowledgePanel.Following-1', static: false, w: 1, x: 0, y: 4 },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export const mockAddWidgetReturnValues2 = [
 | 
			
		||||
  {
 | 
			
		||||
    h: 6,
 | 
			
		||||
    i: 'KnowledgePanel.ActivityFeed',
 | 
			
		||||
    static: false,
 | 
			
		||||
    w: 3,
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 0,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    h: 3,
 | 
			
		||||
    i: 'KnowledgePanel.RecentlyViewed',
 | 
			
		||||
    static: false,
 | 
			
		||||
    w: 1,
 | 
			
		||||
    x: 3,
 | 
			
		||||
    y: 3,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    h: 2,
 | 
			
		||||
    i: 'ExtraWidget.EmptyWidgetPlaceholder',
 | 
			
		||||
    isDraggable: false,
 | 
			
		||||
    static: false,
 | 
			
		||||
    w: 4,
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 100,
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    h: 3,
 | 
			
		||||
    i: 'KnowledgePanel.dataAsset',
 | 
			
		||||
    w: 1,
 | 
			
		||||
    x: 2,
 | 
			
		||||
    y: 4,
 | 
			
		||||
    static: false,
 | 
			
		||||
  },
 | 
			
		||||
  { h: 3, i: 'KnowledgePanel.Following-2', static: false, w: 1, x: 3, y: 4 },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -13,19 +13,41 @@
 | 
			
		||||
import { mockWidget } from '../mocks/AddWidgetTabContent.mock';
 | 
			
		||||
import {
 | 
			
		||||
  mockAddWidgetReturnValues,
 | 
			
		||||
  mockAddWidgetReturnValues2,
 | 
			
		||||
  mockCurrentAddWidget,
 | 
			
		||||
} from '../mocks/CustomizablePage.mock';
 | 
			
		||||
import { getAddWidgetHandler } from './CustomizableLandingPageUtils';
 | 
			
		||||
 | 
			
		||||
describe('getAddWidgetHandler function', () => {
 | 
			
		||||
  it('should add new widget at EmptyWidgetPlaceholder place to be in the bottom', () => {
 | 
			
		||||
  it('should add new widget at the bottom if not fit in the grid row', () => {
 | 
			
		||||
    const result = getAddWidgetHandler(
 | 
			
		||||
      mockWidget,
 | 
			
		||||
      'ExtraWidget.EmptyWidgetPlaceholder',
 | 
			
		||||
      1,
 | 
			
		||||
      3
 | 
			
		||||
      4
 | 
			
		||||
    )(mockCurrentAddWidget);
 | 
			
		||||
 | 
			
		||||
    expect(result).toEqual(mockAddWidgetReturnValues);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should add new widget at the same line if new widget can fit', () => {
 | 
			
		||||
    const result = getAddWidgetHandler(
 | 
			
		||||
      mockWidget,
 | 
			
		||||
      'ExtraWidget.EmptyWidgetPlaceholder',
 | 
			
		||||
      1,
 | 
			
		||||
      4
 | 
			
		||||
    )([
 | 
			
		||||
      ...mockCurrentAddWidget,
 | 
			
		||||
      {
 | 
			
		||||
        h: 3,
 | 
			
		||||
        i: 'KnowledgePanel.dataAsset',
 | 
			
		||||
        w: 1,
 | 
			
		||||
        x: 2,
 | 
			
		||||
        y: 4,
 | 
			
		||||
        static: false,
 | 
			
		||||
      },
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    expect(result).toEqual(mockAddWidgetReturnValues2);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -34,6 +34,47 @@ import { EntityReference } from '../generated/entity/type';
 | 
			
		||||
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
 | 
			
		||||
import customizePageClassBase from './CustomizePageClassBase';
 | 
			
		||||
 | 
			
		||||
const getNewWidgetPlacement = (
 | 
			
		||||
  currentLayout: WidgetConfig[],
 | 
			
		||||
  widgetWidth: number
 | 
			
		||||
) => {
 | 
			
		||||
  const lowestWidgetLayout = currentLayout.reduce(
 | 
			
		||||
    (acc, widget) => {
 | 
			
		||||
      if (
 | 
			
		||||
        widget.y >= acc.y &&
 | 
			
		||||
        widget.i !== LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
 | 
			
		||||
      ) {
 | 
			
		||||
        if (widget.y === acc.y && widget.x < acc.x) {
 | 
			
		||||
          return acc;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return widget;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return acc;
 | 
			
		||||
    },
 | 
			
		||||
    { y: 0, x: 0, w: 0 }
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Check if there's enough space to place the new widget on the same row
 | 
			
		||||
  if (
 | 
			
		||||
    customizePageClassBase.landingPageMaxGridSize -
 | 
			
		||||
      (lowestWidgetLayout.x + lowestWidgetLayout.w) >=
 | 
			
		||||
    widgetWidth
 | 
			
		||||
  ) {
 | 
			
		||||
    return {
 | 
			
		||||
      x: lowestWidgetLayout.x + lowestWidgetLayout.w,
 | 
			
		||||
      y: lowestWidgetLayout.y,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Otherwise, move to the next row
 | 
			
		||||
  return {
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: lowestWidgetLayout.y + 1,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getAddWidgetHandler =
 | 
			
		||||
  (
 | 
			
		||||
    newWidgetData: Document,
 | 
			
		||||
@ -55,19 +96,14 @@ export const getAddWidgetHandler =
 | 
			
		||||
    if (
 | 
			
		||||
      placeholderWidgetKey === LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
 | 
			
		||||
    ) {
 | 
			
		||||
      const emptyWidgetPlaceholder = currentLayout.find(
 | 
			
		||||
        (item) => item.i === LandingPageWidgetKeys.EMPTY_WIDGET_PLACEHOLDER
 | 
			
		||||
      ) ?? { x: 0, y: 99 };
 | 
			
		||||
 | 
			
		||||
      return [
 | 
			
		||||
        ...moveEmptyWidgetToTheEnd(currentLayout),
 | 
			
		||||
        {
 | 
			
		||||
          w: widgetWidth,
 | 
			
		||||
          h: widgetHeight,
 | 
			
		||||
          x: emptyWidgetPlaceholder.x,
 | 
			
		||||
          y: emptyWidgetPlaceholder.y,
 | 
			
		||||
          i: widgetFQN,
 | 
			
		||||
          static: false,
 | 
			
		||||
          ...getNewWidgetPlacement(currentLayout, widgetWidth),
 | 
			
		||||
        },
 | 
			
		||||
      ];
 | 
			
		||||
    } else {
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ class CustomizePageClassBase {
 | 
			
		||||
  defaultWidgetHeight = 3;
 | 
			
		||||
  landingPageWidgetMargin = 16;
 | 
			
		||||
  landingPageRowHeight = 100;
 | 
			
		||||
  landingPageMaxGridSize = 3;
 | 
			
		||||
  landingPageMaxGridSize = 4;
 | 
			
		||||
 | 
			
		||||
  landingPageWidgetDefaultHeights: Record<string, number> = {
 | 
			
		||||
    activityFeed: 6,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user