mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-31 12:39:01 +00:00
* Removed the announcement knowledge panel Announcements widget will only show on landing page if announcements are present * renamed the MyDataPageV1 to MyDataPage * added unit tests for the MyDataPage component * Added unit tests for customizablePage and MyDataPage * removed the API call to fetch announcements in customizeMyData component * added unit tests for CustomizeMyData component * added unit tests for EmptyWidgetPlaceholder component * Added unit tests for AddWidgetModal components * updated the data test ids for widgets * updated the data test id for KPI widget * added data-testids related to customize page flow * Added tests for persona CRUD operations * localization change for other languages * updated data test ids related to customize landing page * updated the persona flow spec * added cypress tests for customizable landing page flow * fixed unit tests * updated the my data page logic to add announcement widget * fixed empty widget placeholder not showing after resetting the layout * Minor: Fix the DocStoreResourceTest * remove the border for announcement widget --------- Co-authored-by: Sriharsha Chintalapani <harsha@getcollate.io>
This commit is contained in:
parent
ac39e3e262
commit
5a0880ccaa
@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "Announcements",
|
||||
"displayName": "Announcements",
|
||||
"description": "Announcements KnowledgePanel shows the Announcements from teams,users published on Data Assets.",
|
||||
"entityType": "KnowledgePanel",
|
||||
"fullyQualifiedName": "KnowledgePanel.Announcements",
|
||||
"data": {
|
||||
"gridSizes": ["small"]
|
||||
}
|
||||
}
|
@ -148,7 +148,7 @@ public class DocStoreResourceTest extends EntityResourceTest<Document, CreateDoc
|
||||
queryParams.put(
|
||||
"fqnPrefix", FullyQualifiedName.build(knowledgePanel.getEntityType().toString()));
|
||||
ResultList<Document> panelList = listEntities(queryParams, ADMIN_AUTH_HEADERS);
|
||||
assertEquals(panelDocs.size() + 7, panelList.getPaging().getTotal());
|
||||
assertEquals(panelDocs.size() + 6, panelList.getPaging().getTotal());
|
||||
|
||||
// docs
|
||||
List<Document> pageDocs = new ArrayList<>();
|
||||
|
@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import {
|
||||
interceptURL,
|
||||
toastNotification,
|
||||
verifyResponseStatusCode,
|
||||
} from './common';
|
||||
|
||||
export const removeAndCheckWidget = ({ widgetTestId, widgetKey }) => {
|
||||
// Click on remove widget button
|
||||
cy.get(
|
||||
`[data-testid="${widgetTestId}"] [data-testid="remove-widget-button"]`
|
||||
).click({ waitForAnimations: true });
|
||||
|
||||
cy.get(`[data-testid="${widgetTestId}"]`).should('not.exist');
|
||||
|
||||
// Check if empty widget placeholder is displayed in place of removed widget
|
||||
cy.get(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`
|
||||
).should('exist');
|
||||
|
||||
// Remove empty widget placeholder
|
||||
cy.get(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"] [data-testid="remove-widget-button"]`
|
||||
).click({ waitForAnimations: true });
|
||||
cy.get(
|
||||
`[data-testid*="${widgetKey}"][data-testid$="EmptyWidgetPlaceholder"]`
|
||||
).should('not.exist');
|
||||
};
|
||||
|
||||
export const navigateToCustomizeLandingPage = ({
|
||||
personaName,
|
||||
customPageDataResponse,
|
||||
}) => {
|
||||
interceptURL('GET', '/api/v1/teams/name/*', 'settingsPage');
|
||||
|
||||
cy.get('[data-testid="app-bar-item-settings"]').click();
|
||||
verifyResponseStatusCode('@settingsPage', 200);
|
||||
cy.get('[data-testid="settings-left-panel"]').should('be.visible');
|
||||
|
||||
interceptURL('GET', '/api/v1/personas*', 'getPersonas');
|
||||
cy.get(`[data-menu-id*="openMetadata.customizeLandingPage"]`)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonas', 200);
|
||||
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/docStore/name/persona.${personaName}.Page.LandingPage`,
|
||||
'getCustomPageData'
|
||||
);
|
||||
interceptURL('GET', `/api/v1/users/*?fields=follows,owns`, 'getMyData');
|
||||
|
||||
cy.get(
|
||||
`[data-testid="persona-details-card-${personaName}"] [data-testid="customize-page-button"]`
|
||||
).click();
|
||||
|
||||
verifyResponseStatusCode('@getCustomPageData', customPageDataResponse);
|
||||
verifyResponseStatusCode('@getMyData', 200);
|
||||
};
|
||||
|
||||
export const saveLayout = () => {
|
||||
// Save layout
|
||||
interceptURL('PATCH', `/api/v1/docStore/*`, 'getMyData');
|
||||
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
|
||||
verifyResponseStatusCode('@getMyData', 200);
|
||||
|
||||
toastNotification('Page layout updated successfully.');
|
||||
};
|
||||
|
||||
export const navigateToLandingPage = () => {
|
||||
interceptURL('GET', `/api/v1/feed*`, 'getFeedsData');
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/analytics/dataInsights/charts/aggregate*`,
|
||||
'getDataInsightReport'
|
||||
);
|
||||
|
||||
cy.get('#openmetadata_logo').click();
|
||||
|
||||
verifyResponseStatusCode('@getFeedsData', 200);
|
||||
verifyResponseStatusCode('@getDataInsightReport', 200);
|
||||
};
|
||||
|
||||
export const openAddWidgetModal = () => {
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/docStore?fqnPrefix=KnowledgePanel`,
|
||||
'getWidgetsList'
|
||||
);
|
||||
|
||||
cy.get(
|
||||
'[data-testid="ExtraWidget.EmptyWidgetPlaceholder"] [data-testid="add-widget-button"]'
|
||||
).click();
|
||||
|
||||
verifyResponseStatusCode('@getWidgetsList', 200);
|
||||
};
|
||||
|
||||
export const checkAllWidgets = (checkEmptyWidgetPlaceholder = false) => {
|
||||
cy.get('[data-testid="activity-feed-widget"]').should('exist');
|
||||
cy.get('[data-testid="following-widget"]').should('exist');
|
||||
cy.get('[data-testid="recently-viewed-widget"]').should('exist');
|
||||
cy.get('[data-testid="my-data-widget"]').should('exist');
|
||||
cy.get('[data-testid="kpi-widget"]').should('exist');
|
||||
cy.get('[data-testid="total-assets-widget"]').should('exist');
|
||||
if (checkEmptyWidgetPlaceholder) {
|
||||
cy.get('[data-testid="ExtraWidget.EmptyWidgetPlaceholder"]').should(
|
||||
'exist'
|
||||
);
|
||||
}
|
||||
};
|
@ -436,3 +436,18 @@ export const VISIT_ENTITIES_DATA = {
|
||||
serviceName: STORAGE_SERVICE.service.name,
|
||||
},
|
||||
};
|
||||
|
||||
export const USER_NAME = `user${uuid()}`;
|
||||
|
||||
export const USER_DETAILS = {
|
||||
firstName: `first-name-${uuid()}`,
|
||||
lastName: `last-name-${uuid()}`,
|
||||
email: `${USER_NAME}@example.com`,
|
||||
password: 'User@OMD123',
|
||||
};
|
||||
|
||||
export const PERSONA_DETAILS = {
|
||||
name: `persona-${uuid()}`,
|
||||
displayName: `persona ${uuid()}`,
|
||||
description: `Persona description.`,
|
||||
};
|
||||
|
@ -137,16 +137,17 @@ describe('Following data assets', () => {
|
||||
});
|
||||
|
||||
it('following section should be present', () => {
|
||||
cy.get('[data-testid="following-data-container"]')
|
||||
cy.get('[data-testid="following-widget"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
|
||||
cy.get('[data-testid="following-data-container"]').contains(
|
||||
cy.get('[data-testid="following-widget"]').contains(
|
||||
'You have not followed anything yet.'
|
||||
);
|
||||
cy.get(
|
||||
`[data-testid="following-data-container"] .right-panel-list-item`
|
||||
).should('have.length', 0);
|
||||
cy.get(`[data-testid="following-widget"] .right-panel-list-item`).should(
|
||||
'have.length',
|
||||
0
|
||||
);
|
||||
});
|
||||
|
||||
// Follow entity
|
||||
|
@ -96,12 +96,12 @@ describe('Recently viwed data assets', () => {
|
||||
});
|
||||
|
||||
it('recently view section should be present', () => {
|
||||
cy.get('[data-testid="recently-viewed-container"]')
|
||||
cy.get('[data-testid="recently-viewed-widget"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
|
||||
cy.get(
|
||||
`[data-testid="recently-viewed-container"] .right-panel-list-item`
|
||||
`[data-testid="recently-viewed-widget"] .right-panel-list-item`
|
||||
).should('have.length', 0);
|
||||
});
|
||||
|
||||
@ -124,18 +124,18 @@ describe('Recently viwed data assets', () => {
|
||||
|
||||
// need to add manual wait as we are dependant on local storage for recently view data
|
||||
cy.wait(500);
|
||||
cy.get('[data-testid="recently-viewed-container"]')
|
||||
cy.get('[data-testid="recently-viewed-widget"]')
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
cy.get(
|
||||
`[data-testid="recently-viewed-container"] [title="${entity.displayName}"]`
|
||||
`[data-testid="recently-viewed-widget"] [title="${entity.displayName}"]`
|
||||
)
|
||||
.scrollIntoView()
|
||||
.should('be.visible');
|
||||
|
||||
// Checking count since we will only show max 5 not more than that
|
||||
cy.get(
|
||||
`[data-testid="recently-viewed-container"] .right-panel-list-item`
|
||||
`[data-testid="recently-viewed-widget"] .right-panel-list-item`
|
||||
).should('have.length', index + 1);
|
||||
});
|
||||
});
|
||||
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { interceptURL, toastNotification } from '../../common/common';
|
||||
import {
|
||||
checkAllWidgets,
|
||||
navigateToCustomizeLandingPage,
|
||||
navigateToLandingPage,
|
||||
openAddWidgetModal,
|
||||
removeAndCheckWidget,
|
||||
saveLayout,
|
||||
} from '../../common/CustomizeLandingPageUtils';
|
||||
import { PERSONA_DETAILS } from '../../constants/EntityConstant';
|
||||
|
||||
describe('Customize Landing Page Flow', () => {
|
||||
let testData = {};
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.getAllLocalStorage().then((data) => {
|
||||
const token = Object.values(data)[0].oidcIdToken;
|
||||
|
||||
// Fetch logged in user details to get user id
|
||||
cy.request({
|
||||
method: 'GET',
|
||||
url: `/api/v1/users/loggedInUser`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).then((userResponse) => {
|
||||
// Create a persona
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/v1/personas`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
body: { ...PERSONA_DETAILS, users: [userResponse.body.id] },
|
||||
}).then((personaResponse) => {
|
||||
testData.user = userResponse.body;
|
||||
testData.persona = personaResponse.body;
|
||||
const {
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
displayName,
|
||||
fullyQualifiedName,
|
||||
href,
|
||||
} = personaResponse.body;
|
||||
|
||||
// Set newly created persona as default persona for the logged in user
|
||||
const patchData = compare(userResponse.body, {
|
||||
...userResponse.body,
|
||||
defaultPersona: {
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
displayName,
|
||||
fullyQualifiedName,
|
||||
href,
|
||||
type: 'persona',
|
||||
},
|
||||
});
|
||||
|
||||
cy.request({
|
||||
method: 'PATCH',
|
||||
url: `/api/v1/users/${testData.user.id}`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json-patch+json',
|
||||
},
|
||||
body: patchData,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.login();
|
||||
const token = localStorage.getItem('oidcIdToken');
|
||||
|
||||
// Delete created user
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/personas/${testData.persona.id}`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
||||
// Delete created landing page config doc
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/docStore/${testData.docStoreData.id}`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
});
|
||||
|
||||
it('Creation of custom landing page config and widget removal should work properly', () => {
|
||||
navigateToCustomizeLandingPage({
|
||||
personaName: PERSONA_DETAILS.name,
|
||||
customPageDataResponse: 404,
|
||||
});
|
||||
|
||||
checkAllWidgets(true);
|
||||
|
||||
// Editing the layout
|
||||
removeAndCheckWidget({
|
||||
widgetTestId: 'activity-feed-widget',
|
||||
widgetKey: 'KnowledgePanel.ActivityFeed',
|
||||
});
|
||||
removeAndCheckWidget({
|
||||
widgetTestId: 'following-widget',
|
||||
widgetKey: 'KnowledgePanel.Following',
|
||||
});
|
||||
removeAndCheckWidget({
|
||||
widgetTestId: 'kpi-widget',
|
||||
widgetKey: 'KnowledgePanel.KPI',
|
||||
});
|
||||
|
||||
// Save layout
|
||||
interceptURL('POST', `/api/v1/docStore`, 'getMyData');
|
||||
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
|
||||
cy.wait('@getMyData').then((interception) => {
|
||||
testData.docStoreData = interception.response.body;
|
||||
|
||||
expect(interception.response.statusCode).equal(201);
|
||||
});
|
||||
|
||||
toastNotification('Page layout created successfully.');
|
||||
|
||||
navigateToLandingPage();
|
||||
|
||||
// Check if removed widgets are not present on landing page
|
||||
cy.get(`[data-testid="activity-feed-widget"]`).should('not.exist');
|
||||
cy.get(`[data-testid="following-widget"]`).should('not.exist');
|
||||
cy.get(`[data-testid="kpi-widget"]`).should('not.exist');
|
||||
});
|
||||
|
||||
it('Adding new widget should work properly', () => {
|
||||
navigateToCustomizeLandingPage({
|
||||
personaName: PERSONA_DETAILS.name,
|
||||
customPageDataResponse: 200,
|
||||
});
|
||||
|
||||
// Check if removed widgets are not present on customize page
|
||||
cy.get('[data-testid="activity-feed-widget"]').should('not.exist');
|
||||
cy.get('[data-testid="following-widget"]').should('not.exist');
|
||||
cy.get('[data-testid="kpi-widget"]').should('not.exist');
|
||||
|
||||
// Check if other widgets are present
|
||||
cy.get('[data-testid="recently-viewed-widget"]').should('exist');
|
||||
cy.get('[data-testid="my-data-widget"]').should('exist');
|
||||
cy.get('[data-testid="total-assets-widget"]').should('exist');
|
||||
cy.get('[data-testid="ExtraWidget.EmptyWidgetPlaceholder"]').should(
|
||||
'exist'
|
||||
);
|
||||
|
||||
openAddWidgetModal();
|
||||
|
||||
// Check if 'check' icon is present for existing widgets
|
||||
cy.get('[data-testid="MyData-check-icon"]').should('exist');
|
||||
cy.get('[data-testid="RecentlyViewed-check-icon"]').should('exist');
|
||||
cy.get('[data-testid="TotalAssets-check-icon"]').should('exist');
|
||||
|
||||
// Check if 'check' icon is not present for removed widgets
|
||||
cy.get('[data-testid="ActivityFeed-check-icon"]').should('not.exist');
|
||||
cy.get('[data-testid="Following-check-icon"]').should('not.exist');
|
||||
cy.get('[data-testid="KPI-check-icon"]').should('not.exist');
|
||||
|
||||
// Add Following widget
|
||||
cy.get('[data-testid="Following-widget-tab-label"]').click();
|
||||
cy.get(
|
||||
'[aria-labelledby$="KnowledgePanel.Following"] [data-testid="add-widget-button"]'
|
||||
).click();
|
||||
cy.get('[data-testid="following-widget"]').should('exist');
|
||||
|
||||
// Check if check icons are present in tab labels for newly added widgets
|
||||
openAddWidgetModal();
|
||||
cy.get('[data-testid="Following-check-icon"]').should('exist');
|
||||
cy.get('[data-testid="add-widget-modal"] [aria-label="Close"]').click();
|
||||
|
||||
saveLayout();
|
||||
|
||||
navigateToLandingPage();
|
||||
|
||||
cy.get(`[data-testid="activity-feed-widget"]`).should('not.exist');
|
||||
cy.get(`[data-testid="kpi-widget"]`).should('not.exist');
|
||||
|
||||
// Check if newly added widgets are present on landing page
|
||||
cy.get(`[data-testid="following-widget"]`).should('exist');
|
||||
});
|
||||
|
||||
it('Resetting the layout flow should work properly', () => {
|
||||
// Check if removed widgets are not present on landing page
|
||||
cy.get(`[data-testid="activity-feed-widget"]`).should('not.exist');
|
||||
cy.get(`[data-testid="kpi-widget"]`).should('not.exist');
|
||||
|
||||
navigateToCustomizeLandingPage({
|
||||
personaName: PERSONA_DETAILS.name,
|
||||
customPageDataResponse: 200,
|
||||
});
|
||||
|
||||
// Check if removed widgets are not present on customize page
|
||||
cy.get(`[data-testid="activity-feed-widget"]`).should('not.exist');
|
||||
cy.get(`[data-testid="kpi-widget"]`).should('not.exist');
|
||||
|
||||
cy.get(`[data-testid="reset-button"]`).click();
|
||||
|
||||
cy.get(`[data-testid="reset-layout-modal"] .ant-modal-footer`)
|
||||
.contains('Yes')
|
||||
.click();
|
||||
|
||||
toastNotification('Page layout updated successfully.');
|
||||
|
||||
// Check if all widgets are present after resetting the layout
|
||||
checkAllWidgets(true);
|
||||
|
||||
// Check if all widgets are present on landing page
|
||||
navigateToLandingPage();
|
||||
|
||||
checkAllWidgets();
|
||||
});
|
||||
});
|
@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import {
|
||||
descriptionBox,
|
||||
interceptURL,
|
||||
toastNotification,
|
||||
verifyResponseStatusCode,
|
||||
} from '../../common/common';
|
||||
import { DELETE_TERM } from '../../constants/constants';
|
||||
import { PERSONA_DETAILS, USER_DETAILS } from '../../constants/EntityConstant';
|
||||
|
||||
const updatePersonaDisplayName = (displayName) => {
|
||||
interceptURL('PATCH', `/api/v1/personas/*`, 'updatePersona');
|
||||
|
||||
cy.get('[data-testid="manage-button"]').click();
|
||||
|
||||
cy.get(
|
||||
'[data-testid="manage-dropdown-list-container"] [data-testid="rename-button"]'
|
||||
).click();
|
||||
|
||||
cy.get('#name').should('be.disabled');
|
||||
cy.get('#displayName').should('not.be.disabled').clear();
|
||||
|
||||
cy.get('#displayName').type(displayName);
|
||||
|
||||
cy.get('[data-testid="save-button"]').click();
|
||||
verifyResponseStatusCode('@updatePersona', 200);
|
||||
};
|
||||
|
||||
describe('Persona operations', () => {
|
||||
let user = {};
|
||||
const userSearchText = `${USER_DETAILS.firstName}${USER_DETAILS.lastName}`;
|
||||
before(() => {
|
||||
cy.login();
|
||||
cy.getAllLocalStorage().then((data) => {
|
||||
const token = Object.values(data)[0].oidcIdToken;
|
||||
|
||||
// Create a new user
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `/api/v1/users/signup`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
body: USER_DETAILS,
|
||||
}).then((response) => {
|
||||
user.details = response.body;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.login();
|
||||
cy.getAllLocalStorage().then((data) => {
|
||||
const token = Object.values(data)[0].oidcIdToken;
|
||||
|
||||
// Delete created user
|
||||
cy.request({
|
||||
method: 'DELETE',
|
||||
url: `/api/v1/users/${user.details.id}?hardDelete=true&recursive=false`,
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.login();
|
||||
interceptURL('GET', '/api/v1/teams/name/*', 'settingsPage');
|
||||
|
||||
cy.get('[data-testid="app-bar-item-settings"]').click();
|
||||
verifyResponseStatusCode('@settingsPage', 200);
|
||||
cy.get('[data-testid="settings-left-panel"]').should('be.visible');
|
||||
|
||||
interceptURL('GET', '/api/v1/personas*', 'getPersonas');
|
||||
cy.get(`[data-menu-id*="members.persona"]`).scrollIntoView().click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonas', 200);
|
||||
});
|
||||
|
||||
it('Persona creation should work properly', () => {
|
||||
cy.get('[data-testid="add-persona-button"]').scrollIntoView().click();
|
||||
cy.get('[data-testid="name"]').clear().type(PERSONA_DETAILS.name);
|
||||
cy.get('[data-testid="displayName"]')
|
||||
.clear()
|
||||
.type(PERSONA_DETAILS.displayName);
|
||||
cy.get(descriptionBox).type(PERSONA_DETAILS.description);
|
||||
cy.get('[data-testid="add-users"]').scrollIntoView().click();
|
||||
|
||||
cy.get('[data-testid="searchbar"]').type(userSearchText);
|
||||
|
||||
cy.get(`[title="${userSearchText}"] .ant-checkbox-input`).check();
|
||||
cy.get('[data-testid="selectable-list-update-btn"]')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
interceptURL('POST', '/api/v1/personas', 'createPersona');
|
||||
|
||||
cy.get('.ant-modal-footer > .ant-btn-primary')
|
||||
.contains('Create')
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@createPersona', 201);
|
||||
|
||||
// Verify created persona details
|
||||
|
||||
cy.get('[data-testid="persona-details-card"] .ant-card-meta-title').should(
|
||||
'contain',
|
||||
PERSONA_DETAILS.displayName
|
||||
);
|
||||
cy.get(
|
||||
'[data-testid="persona-details-card"] .ant-card-meta-description'
|
||||
).should('contain', PERSONA_DETAILS.description);
|
||||
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/personas/name/${PERSONA_DETAILS.name}*`,
|
||||
'getPersonaDetails'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="persona-details-card"]')
|
||||
.contains(PERSONA_DETAILS.displayName)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonaDetails', 200);
|
||||
|
||||
cy.get(
|
||||
'[data-testid="page-header-container"] [data-testid="heading"]'
|
||||
).should('contain', PERSONA_DETAILS.displayName);
|
||||
cy.get(
|
||||
'[data-testid="page-header-container"] [data-testid="sub-heading"]'
|
||||
).should('contain', PERSONA_DETAILS.name);
|
||||
cy.get(
|
||||
'[data-testid="viewer-container"] [data-testid="markdown-parser"]'
|
||||
).should('contain', PERSONA_DETAILS.description);
|
||||
|
||||
cy.get(
|
||||
`[data-row-key="${user.details.name}"] [data-testid="${user.details.name}"]`
|
||||
).should('contain', user.details.name);
|
||||
});
|
||||
|
||||
it('Persona update description flow should work properly', () => {
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/personas/name/${PERSONA_DETAILS.name}*`,
|
||||
'getPersonaDetails'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="persona-details-card"]')
|
||||
.contains(PERSONA_DETAILS.displayName)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonaDetails', 200);
|
||||
|
||||
cy.get('[data-testid="edit-description"]').click();
|
||||
|
||||
cy.get(`[data-testid="markdown-editor"] ${descriptionBox}`)
|
||||
.clear()
|
||||
.type('Updated description.');
|
||||
|
||||
interceptURL('PATCH', `/api/v1/personas/*`, 'updatePersona');
|
||||
|
||||
cy.get(`[data-testid="markdown-editor"] [data-testid="save"]`).click();
|
||||
|
||||
verifyResponseStatusCode('@updatePersona', 200);
|
||||
|
||||
cy.get(
|
||||
`[data-testid="viewer-container"] [data-testid="markdown-parser"]`
|
||||
).should('contain', 'Updated description.');
|
||||
});
|
||||
|
||||
it('Persona rename flow should work properly', () => {
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/personas/name/${PERSONA_DETAILS.name}*`,
|
||||
'getPersonaDetails'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="persona-details-card"]')
|
||||
.contains(PERSONA_DETAILS.displayName)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonaDetails', 200);
|
||||
|
||||
updatePersonaDisplayName('Test Persona');
|
||||
|
||||
cy.get('[data-testid="heading"]').should('contain', 'Test Persona');
|
||||
|
||||
updatePersonaDisplayName(PERSONA_DETAILS.displayName);
|
||||
|
||||
cy.get('[data-testid="heading"]').should(
|
||||
'contain',
|
||||
PERSONA_DETAILS.displayName
|
||||
);
|
||||
});
|
||||
|
||||
it('Remove users in persona should work properly', () => {
|
||||
// Remove user from the users tab
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/personas/name/${PERSONA_DETAILS.name}*`,
|
||||
'getPersonaDetails'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="persona-details-card"]')
|
||||
.contains(PERSONA_DETAILS.displayName)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonaDetails', 200);
|
||||
|
||||
cy.get(
|
||||
`[data-row-key="${user.details.name}"] [data-testid="remove-user-btn"]`
|
||||
).click();
|
||||
|
||||
cy.get('[data-testid="remove-confirmation-modal"]').should(
|
||||
'contain',
|
||||
`Are you sure you want to remove ${user.details.name}?`
|
||||
);
|
||||
|
||||
interceptURL('PATCH', `/api/v1/personas/*`, 'updatePersona');
|
||||
|
||||
cy.get('[data-testid="remove-confirmation-modal"]')
|
||||
.contains('Confirm')
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@updatePersona', 200);
|
||||
});
|
||||
|
||||
it('Delete persona should work properly', () => {
|
||||
interceptURL(
|
||||
'GET',
|
||||
`/api/v1/personas/name/${PERSONA_DETAILS.name}*`,
|
||||
'getPersonaDetails'
|
||||
);
|
||||
|
||||
cy.get('[data-testid="persona-details-card"]')
|
||||
.contains(PERSONA_DETAILS.displayName)
|
||||
.scrollIntoView()
|
||||
.click();
|
||||
|
||||
verifyResponseStatusCode('@getPersonaDetails', 200);
|
||||
|
||||
cy.get('[data-testid="manage-button"]').click();
|
||||
|
||||
cy.get('[data-testid="delete-button-title"]').click();
|
||||
|
||||
cy.get('.ant-modal-header').should(
|
||||
'contain',
|
||||
`Delete ${PERSONA_DETAILS.name}`
|
||||
);
|
||||
|
||||
cy.get(`[data-testid="hard-delete-option"]`).click();
|
||||
|
||||
cy.get('[data-testid="confirm-button"]').should('be.disabled');
|
||||
cy.get('[data-testid="confirmation-text-input"]').type(DELETE_TERM);
|
||||
|
||||
interceptURL(
|
||||
'DELETE',
|
||||
`/api/v1/personas/*?hardDelete=true&recursive=false`,
|
||||
`deletePersona`
|
||||
);
|
||||
cy.get('[data-testid="confirm-button"]').should('not.be.disabled');
|
||||
cy.get('[data-testid="confirm-button"]').click();
|
||||
verifyResponseStatusCode(`@deletePersona`, 200);
|
||||
|
||||
toastNotification(`Persona deleted successfully!`);
|
||||
});
|
||||
});
|
@ -32,8 +32,8 @@ const GlobalSettingPage = withSuspenseFallback(
|
||||
React.lazy(() => import('../../pages/GlobalSettingPage/GlobalSettingPage'))
|
||||
);
|
||||
|
||||
const MyDataPageV1 = withSuspenseFallback(
|
||||
React.lazy(() => import('../../pages/MyDataPage/MyDataPageV1.component'))
|
||||
const MyDataPage = withSuspenseFallback(
|
||||
React.lazy(() => import('../../pages/MyDataPage/MyDataPage.component'))
|
||||
);
|
||||
|
||||
const TestSuiteIngestionPage = withSuspenseFallback(
|
||||
@ -390,7 +390,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact component={MyDataPageV1} path={ROUTES.MY_DATA} />
|
||||
<Route exact component={MyDataPage} path={ROUTES.MY_DATA} />
|
||||
<Route exact component={TourPageComponent} path={ROUTES.TOUR} />
|
||||
<Route exact component={ExplorePageV1} path={ROUTES.EXPLORE} />
|
||||
<Route component={ExplorePageV1} path={ROUTES.EXPLORE_WITH_TAB} />
|
||||
|
@ -44,7 +44,7 @@ import {
|
||||
REDIRECT_PATHNAME,
|
||||
ROUTES,
|
||||
} from '../../../constants/constants';
|
||||
import { ClientErrors } from '../../../enums/axios.enum';
|
||||
import { ClientErrors } from '../../../enums/Axios.enum';
|
||||
import { AuthenticationConfiguration } from '../../../generated/configuration/authenticationConfiguration';
|
||||
import { AuthorizerConfiguration } from '../../../generated/configuration/authorizerConfiguration';
|
||||
import { User } from '../../../generated/entity/teams/user';
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { Document } from '../../../generated/entity/docStore/document';
|
||||
|
||||
export interface AddWidgetModalProps {
|
||||
@ -34,6 +35,6 @@ export interface AddWidgetTabContentProps {
|
||||
}
|
||||
|
||||
export interface WidgetSizeInfo {
|
||||
label: string;
|
||||
label: ReactNode;
|
||||
value: number;
|
||||
}
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { mockWidgetsData } from '../../../mocks/AddWidgetModal.mock';
|
||||
import { getAllKnowledgePanels } from '../../../rest/DocStoreAPI';
|
||||
import AddWidgetModal from './AddWidgetModal';
|
||||
import { AddWidgetModalProps } from './AddWidgetModal.interface';
|
||||
|
||||
const mockProps: AddWidgetModalProps = {
|
||||
open: true,
|
||||
addedWidgetsList: [],
|
||||
handleCloseAddWidgetModal: jest.fn(),
|
||||
handleAddWidget: jest.fn(),
|
||||
maxGridSizeSupport: 4,
|
||||
placeholderWidgetKey: 'placeholderKey',
|
||||
};
|
||||
|
||||
jest.mock('../../Loader/Loader', () =>
|
||||
jest.fn().mockImplementation(() => <div>Loader</div>)
|
||||
);
|
||||
|
||||
jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () =>
|
||||
jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>)
|
||||
);
|
||||
|
||||
jest.mock('./AddWidgetTabContent', () =>
|
||||
jest.fn().mockImplementation(({ getAddWidgetHandler }) => (
|
||||
<div>
|
||||
AddWidgetTabContent
|
||||
<div onClick={getAddWidgetHandler(mockWidgetsData.data[0], 3)}>
|
||||
getAddWidgetHandler
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('../../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../rest/DocStoreAPI', () => ({
|
||||
getAllKnowledgePanels: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockWidgetsData)),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/CustomizableLandingPageUtils', () => ({
|
||||
getWidgetWidthLabelFromKey: jest.fn().mockImplementation((label) => label),
|
||||
}));
|
||||
|
||||
describe('AddWidgetModal component', () => {
|
||||
it('AddWidgetModal should not display the modal when open is false', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetModal {...mockProps} open={false} />);
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('add-widget-modal')).toBeNull();
|
||||
});
|
||||
|
||||
it('AddWidgetModal should display all widgets tab from the widgets list', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetModal {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByTestId('ActivityFeed-widget-tab-label')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('Following-widget-tab-label')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('KPI-widget-tab-label')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('MyData-widget-tab-label')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('RecentlyViewed-widget-tab-label')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('TotalAssets-widget-tab-label')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('AddWidgetTabContent')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('AddWidgetModal should display check icons in the tab labels only for the tabs included in addedWidgetsList', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<AddWidgetModal
|
||||
{...mockProps}
|
||||
addedWidgetsList={[
|
||||
'KnowledgePanel.ActivityFeed',
|
||||
'KnowledgePanel.Following',
|
||||
]}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('ActivityFeed-check-icon')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('Following-check-icon')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('KPI-check-icon')).toBeNull();
|
||||
expect(screen.queryByTestId('MyData-check-icon')).toBeNull();
|
||||
expect(screen.queryByTestId('RecentlyViewed-check-icon')).toBeNull();
|
||||
expect(screen.queryByTestId('TotalAssets-check-icon')).toBeNull();
|
||||
});
|
||||
|
||||
it('AddWidgetModal should call handleAddWidget when clicked on add widget button', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetModal {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(mockProps.handleAddWidget).toHaveBeenCalledTimes(0);
|
||||
|
||||
const addWidgetButton = screen.getByText('getAddWidgetHandler');
|
||||
|
||||
expect(addWidgetButton).toBeInTheDocument();
|
||||
|
||||
await act(async () => userEvent.click(addWidgetButton));
|
||||
|
||||
expect(mockProps.handleAddWidget).toHaveBeenCalledTimes(1);
|
||||
expect(mockProps.handleAddWidget).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'ActivityFeed',
|
||||
}),
|
||||
'placeholderKey',
|
||||
3
|
||||
);
|
||||
});
|
||||
|
||||
it('AddWidgetModal should display ErrorPlaceHolder when API to fetch widgets list is failed', async () => {
|
||||
(getAllKnowledgePanels as jest.Mock).mockImplementation(() =>
|
||||
Promise.reject(new Error('API Failed'))
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<AddWidgetModal {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('ErrorPlaceHolder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('AddWidgetModal should', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetModal {...mockProps} />);
|
||||
});
|
||||
});
|
||||
});
|
@ -24,6 +24,7 @@ import { getAllKnowledgePanels } from '../../../rest/DocStoreAPI';
|
||||
import { getWidgetWidthLabelFromKey } from '../../../utils/CustomizableLandingPageUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import './add-widget-modal.less';
|
||||
import {
|
||||
AddWidgetModalProps,
|
||||
@ -41,9 +42,11 @@ function AddWidgetModal({
|
||||
}: Readonly<AddWidgetModalProps>) {
|
||||
const { t } = useTranslation();
|
||||
const [widgetsList, setWidgetsList] = useState<Array<Document>>();
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
const fetchKnowledgePanels = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getAllKnowledgePanels({
|
||||
fqnPrefix: 'KnowledgePanel',
|
||||
});
|
||||
@ -51,6 +54,8 @@ function AddWidgetModal({
|
||||
setWidgetsList(response.data);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -65,19 +70,24 @@ function AddWidgetModal({
|
||||
widgetsList?.map((widget) => {
|
||||
const widgetSizeOptions: Array<WidgetSizeInfo> =
|
||||
widget.data.gridSizes.map((size: WidgetWidths) => ({
|
||||
label: getWidgetWidthLabelFromKey(toString(size)),
|
||||
label: (
|
||||
<span data-testid={`${size}-size-selector`}>
|
||||
{getWidgetWidthLabelFromKey(toString(size))}
|
||||
</span>
|
||||
),
|
||||
value: WidgetWidths[size],
|
||||
}));
|
||||
|
||||
return {
|
||||
label: (
|
||||
<Space>
|
||||
<Space data-testid={`${widget.name}-widget-tab-label`}>
|
||||
<span>{widget.name}</span>
|
||||
{addedWidgetsList.some((w) =>
|
||||
w.startsWith(widget.fullyQualifiedName)
|
||||
) && (
|
||||
<CheckOutlined
|
||||
className="m-l-xs"
|
||||
data-testid={`${widget.name}-check-icon`}
|
||||
style={{ color: '#4CAF50' }}
|
||||
/>
|
||||
)}
|
||||
@ -101,24 +111,42 @@ function AddWidgetModal({
|
||||
fetchKnowledgePanels();
|
||||
}, []);
|
||||
|
||||
const widgetsInfo = useMemo(() => {
|
||||
if (loading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
if (isEmpty(widgetsList)) {
|
||||
return (
|
||||
<ErrorPlaceHolder
|
||||
className="h-min-480"
|
||||
data-testid="no-widgets-placeholder"
|
||||
type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
|
||||
{t('message.no-widgets-to-add')}
|
||||
</ErrorPlaceHolder>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
data-testid="widget-info-tabs"
|
||||
items={tabItems}
|
||||
tabPosition="left"
|
||||
/>
|
||||
);
|
||||
}, [loading, widgetsList, tabItems]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
centered
|
||||
className="add-widget-modal"
|
||||
data-testid="add-widget-modal"
|
||||
footer={null}
|
||||
open={open}
|
||||
title={t('label.add-new-entity', { entity: t('label.widget') })}
|
||||
width={750}
|
||||
onCancel={handleCloseAddWidgetModal}>
|
||||
{isEmpty(widgetsList) ? (
|
||||
<ErrorPlaceHolder
|
||||
className="h-min-480"
|
||||
type={ERROR_PLACEHOLDER_TYPE.CUSTOM}>
|
||||
{t('message.no-widgets-to-add')}
|
||||
</ErrorPlaceHolder>
|
||||
) : (
|
||||
<Tabs items={tabItems} tabPosition="left" />
|
||||
)}
|
||||
{widgetsInfo}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import {
|
||||
mockWidget,
|
||||
mockWidgetSizes,
|
||||
} from '../../../mocks/AddWidgetTabContent.mock';
|
||||
import { AddWidgetTabContentProps } from './AddWidgetModal.interface';
|
||||
import AddWidgetTabContent from './AddWidgetTabContent';
|
||||
|
||||
const mockProps: AddWidgetTabContentProps = {
|
||||
getAddWidgetHandler: jest.fn(),
|
||||
maxGridSizeSupport: 4,
|
||||
widget: mockWidget,
|
||||
widgetSizeOptions: mockWidgetSizes,
|
||||
};
|
||||
|
||||
jest.mock('../../../utils/CustomizePageClassBase', () => ({
|
||||
getWidgetImageFromKey: jest.fn().mockImplementation(() => ''),
|
||||
}));
|
||||
|
||||
describe('AddWidgetTabContent component', () => {
|
||||
it('AddWidgetTabContent should render properly', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetTabContent {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('size-selector-button')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('widget-image')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('widget-description')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('add-widget-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('AddWidgetTabContent should display correct size selector buttons', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetTabContent {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('Small')).toBeInTheDocument();
|
||||
expect(screen.getByText('Medium')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Large')).toBeNull();
|
||||
});
|
||||
|
||||
it('AddWidgetTabContent should send selected widget size to getAddWidgetHandler', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetTabContent {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(mockProps.getAddWidgetHandler).toHaveBeenCalledTimes(1);
|
||||
expect(mockProps.getAddWidgetHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining(mockProps.widget),
|
||||
1
|
||||
);
|
||||
|
||||
const mediumButton = screen.getByText('Medium');
|
||||
|
||||
await act(async () => userEvent.click(mediumButton));
|
||||
|
||||
expect(mockProps.getAddWidgetHandler).toHaveBeenCalledTimes(2);
|
||||
expect(mockProps.getAddWidgetHandler).toHaveBeenCalledWith(
|
||||
expect.objectContaining(mockProps.widget),
|
||||
2
|
||||
);
|
||||
});
|
||||
|
||||
it('AddWidgetTabContent should disable the add widget button if widget size exceeds the maxGridSizeSupport', async () => {
|
||||
await act(async () => {
|
||||
render(<AddWidgetTabContent {...mockProps} maxGridSizeSupport={0} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('add-widget-button')).toBeDisabled();
|
||||
});
|
||||
});
|
@ -63,6 +63,7 @@ function AddWidgetTabContent({
|
||||
<Space>
|
||||
<Typography.Text>{`${t('label.size')}:`}</Typography.Text>
|
||||
<Radio.Group
|
||||
data-testid="size-selector-button"
|
||||
defaultValue={selectedWidgetSize}
|
||||
optionType="button"
|
||||
options={widgetSizeOptions}
|
||||
@ -74,8 +75,15 @@ function AddWidgetTabContent({
|
||||
<Row className="h-min-480" justify="center">
|
||||
<Col>
|
||||
<Space align="center" direction="vertical">
|
||||
<Image className="p-y-md" preview={false} src={widgetImage} />
|
||||
<Typography.Paragraph className="d-block text-center">
|
||||
<Image
|
||||
className="p-y-md"
|
||||
data-testid="widget-image"
|
||||
preview={false}
|
||||
src={widgetImage}
|
||||
/>
|
||||
<Typography.Paragraph
|
||||
className="d-block text-center"
|
||||
data-testid="widget-description">
|
||||
{widget.description}
|
||||
</Typography.Paragraph>
|
||||
<Tooltip
|
||||
@ -84,7 +92,7 @@ function AddWidgetTabContent({
|
||||
<Button
|
||||
ghost
|
||||
className="p-x-lg m-t-md"
|
||||
data-testid="add-widget-placeholder-button"
|
||||
data-testid="add-widget-button"
|
||||
disabled={!widgetAddable}
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
|
@ -0,0 +1,281 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { PageType } from '../../../generated/system/ui/page';
|
||||
import {
|
||||
mockActiveAnnouncementData,
|
||||
mockCustomizePageClassBase,
|
||||
mockDefaultLayout,
|
||||
mockDocumentData,
|
||||
mockPersonaName,
|
||||
mockUserData,
|
||||
} from '../../../mocks/MyDataPage.mock';
|
||||
import { WidgetConfig } from '../../../pages/CustomizablePage/CustomizablePage.interface';
|
||||
import CustomizeMyData from './CustomizeMyData';
|
||||
import { CustomizeMyDataProps } from './CustomizeMyData.interface';
|
||||
|
||||
const mockPush = jest.fn();
|
||||
|
||||
const mockProps: CustomizeMyDataProps = {
|
||||
initialPageData: mockDocumentData,
|
||||
onSaveLayout: jest.fn(),
|
||||
handlePageDataChange: jest.fn(),
|
||||
handleSaveCurrentPageLayout: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock(
|
||||
'../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
|
||||
() => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="activity-feed-provider">{children}</div>
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('../AddWidgetModal/AddWidgetModal', () => {
|
||||
return jest.fn().mockImplementation(({ handleCloseAddWidgetModal }) => (
|
||||
<div>
|
||||
AddWidgetModal
|
||||
<div onClick={handleCloseAddWidgetModal}>handleCloseAddWidgetModal</div>
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../EmptyWidgetPlaceholder/EmptyWidgetPlaceholder', () => {
|
||||
return jest.fn().mockImplementation(({ handleOpenAddWidgetModal }) => (
|
||||
<div>
|
||||
EmptyWidgetPlaceholder
|
||||
<div onClick={handleOpenAddWidgetModal}>handleOpenAddWidgetModal</div>
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../../utils/CustomizePageClassBase', () => {
|
||||
return mockCustomizePageClassBase;
|
||||
});
|
||||
|
||||
jest.mock('../../PageLayoutV1/PageLayoutV1', () => {
|
||||
return jest.fn().mockImplementation(({ children, header }) => (
|
||||
<div data-testid="page-layout-v1">
|
||||
<div data-testid="page-header">{header}</div>
|
||||
{children}
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../Auth/AuthProviders/AuthProvider', () => ({
|
||||
useAuthContext: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ currentUser: mockUserData })),
|
||||
}));
|
||||
|
||||
jest.mock('../../../rest/feedsAPI', () => ({
|
||||
getActiveAnnouncement: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockActiveAnnouncementData),
|
||||
}));
|
||||
|
||||
jest.mock('../../../rest/userAPI', () => ({
|
||||
getUserById: jest.fn().mockImplementation(() => mockUserData),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: jest.fn().mockImplementation(() => ({ pathname: '' })),
|
||||
useHistory: jest.fn().mockImplementation(() => ({
|
||||
push: mockPush,
|
||||
})),
|
||||
useParams: jest.fn().mockImplementation(() => ({
|
||||
fqn: mockPersonaName,
|
||||
pageFqn: PageType.LandingPage,
|
||||
})),
|
||||
Link: jest.fn().mockImplementation(() => <div>Link</div>),
|
||||
}));
|
||||
|
||||
jest.mock('react-grid-layout', () => ({
|
||||
WidthProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="react-grid-layout">{children}</div>
|
||||
))
|
||||
),
|
||||
__esModule: true,
|
||||
default: '',
|
||||
}));
|
||||
|
||||
jest.mock('../../../hooks/authHooks', () => ({
|
||||
useAuth: jest.fn().mockImplementation(() => ({ isAuthDisabled: false })),
|
||||
}));
|
||||
|
||||
describe('CustomizeMyData component', () => {
|
||||
it('CustomizeMyData should render the widgets in the page config', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('KnowledgePanel.ActivityFeed')).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.Following')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.RecentlyViewed')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('KnowledgePanel.Announcements')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.KPI')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.TotalAssets')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.MyData')).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should reroute to the customizable page settings page on click of cancel button', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
const cancelButton = screen.getByTestId('cancel-button');
|
||||
|
||||
await act(async () => userEvent.click(cancelButton));
|
||||
|
||||
expect(mockPush).toHaveBeenCalledWith(
|
||||
'/settings/openMetadata/customizeLandingPage'
|
||||
);
|
||||
});
|
||||
|
||||
it('CustomizeMyData should display reset layout confirmation modal on click of reset button', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
const resetButton = screen.getByTestId('reset-button');
|
||||
|
||||
await act(async () => userEvent.click(resetButton));
|
||||
|
||||
expect(screen.getByTestId('reset-layout-modal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should call handlePageDataChange with default layout and close the reset confirmation modal', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
// handlePageDataChange is called 1 time on mount
|
||||
expect(mockProps.handlePageDataChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
const resetButton = screen.getByTestId('reset-button');
|
||||
|
||||
await act(async () => userEvent.click(resetButton));
|
||||
|
||||
expect(screen.getByTestId('reset-layout-modal')).toBeInTheDocument();
|
||||
|
||||
const yesButton = screen.getByText('label.yes');
|
||||
|
||||
await act(async () => userEvent.click(yesButton));
|
||||
|
||||
expect(mockProps.handlePageDataChange).toHaveBeenCalledTimes(3);
|
||||
// Check if the handlePageDataChange is passed an object with the default layout
|
||||
expect(mockProps.handlePageDataChange).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
...mockDocumentData,
|
||||
data: {
|
||||
page: {
|
||||
layout: expect.arrayContaining<WidgetConfig>(mockDefaultLayout),
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('reset-layout-modal')).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should close the reset confirmation modal without calling handlePageDataChange', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
// handlePageDataChange is called 1 time on mount
|
||||
expect(mockProps.handlePageDataChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
const resetButton = screen.getByTestId('reset-button');
|
||||
|
||||
await act(async () => userEvent.click(resetButton));
|
||||
|
||||
expect(screen.getByTestId('reset-layout-modal')).toBeInTheDocument();
|
||||
|
||||
const noButton = screen.getByText('label.no');
|
||||
|
||||
await act(async () => userEvent.click(noButton));
|
||||
|
||||
// handlePageDataChange is not called again
|
||||
expect(mockProps.handlePageDataChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.queryByTestId('reset-layout-modal')).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should call onSaveLayout after clicking on save layout button', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(mockProps.onSaveLayout).toHaveBeenCalledTimes(0);
|
||||
|
||||
const saveButton = screen.getByTestId('save-button');
|
||||
|
||||
await act(async () => userEvent.click(saveButton));
|
||||
|
||||
expect(mockProps.onSaveLayout).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(screen.queryByTestId('reset-layout-modal')).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should display EmptyWidgetPlaceholder', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('EmptyWidgetPlaceholder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should display AddWidgetModal after handleOpenAddWidgetModal is called', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
const addWidgetButton = screen.getByText('handleOpenAddWidgetModal');
|
||||
|
||||
await act(async () => userEvent.click(addWidgetButton));
|
||||
|
||||
expect(screen.getByText('AddWidgetModal')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CustomizeMyData should not display AddWidgetModal after handleCloseAddWidgetModal is called', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizeMyData {...mockProps} />);
|
||||
});
|
||||
|
||||
const addWidgetButton = screen.getByText('handleOpenAddWidgetModal');
|
||||
|
||||
await act(async () => userEvent.click(addWidgetButton));
|
||||
|
||||
expect(screen.getByText('AddWidgetModal')).toBeInTheDocument();
|
||||
|
||||
const closeWidgetButton = screen.getByText('handleCloseAddWidgetModal');
|
||||
|
||||
await act(async () => userEvent.click(closeWidgetButton));
|
||||
|
||||
expect(screen.queryByText('AddWidgetModal')).toBeNull();
|
||||
});
|
||||
});
|
@ -13,7 +13,7 @@
|
||||
|
||||
import { Button, Col, Modal, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isEmpty, isNil, uniqBy } from 'lodash';
|
||||
import { isEmpty, isNil } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import RGL, { Layout, WidthProvider } from 'react-grid-layout';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -26,12 +26,10 @@ import {
|
||||
import { LandingPageWidgetKeys } from '../../../enums/CustomizablePage.enum';
|
||||
import { AssetsType } from '../../../enums/entity.enum';
|
||||
import { Document } from '../../../generated/entity/docStore/document';
|
||||
import { Thread } from '../../../generated/entity/feed/thread';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { PageType } from '../../../generated/system/ui/page';
|
||||
import { WidgetConfig } from '../../../pages/CustomizablePage/CustomizablePage.interface';
|
||||
import '../../../pages/MyDataPage/my-data.less';
|
||||
import { getActiveAnnouncement } from '../../../rest/feedsAPI';
|
||||
import { getUserById } from '../../../rest/userAPI';
|
||||
import { Transi18next } from '../../../utils/CommonUtils';
|
||||
import {
|
||||
@ -39,6 +37,7 @@ import {
|
||||
getLayoutUpdateHandler,
|
||||
getLayoutWithEmptyWidgetPlaceholder,
|
||||
getRemoveWidgetHandler,
|
||||
getUniqueFilteredLayout,
|
||||
getWidgetFromKey,
|
||||
} from '../../../utils/CustomizableLandingPageUtils';
|
||||
import customizePageClassBase from '../../../utils/CustomizePageClassBase';
|
||||
@ -86,9 +85,6 @@ function CustomizeMyData({
|
||||
const [followedData, setFollowedData] = useState<Array<EntityReference>>();
|
||||
const [followedDataCount, setFollowedDataCount] = useState(0);
|
||||
const [isLoadingOwnedData, setIsLoadingOwnedData] = useState<boolean>(false);
|
||||
const [isAnnouncementLoading, setIsAnnouncementLoading] =
|
||||
useState<boolean>(true);
|
||||
const [announcements, setAnnouncements] = useState<Thread[]>([]);
|
||||
|
||||
const decodedPersonaFQN = useMemo(
|
||||
() => getDecodedFqn(personaFQN),
|
||||
@ -185,7 +181,6 @@ function CustomizeMyData({
|
||||
layout.map((widget) => (
|
||||
<div data-grid={widget} id={widget.i} key={widget.i}>
|
||||
{getWidgetFromKey({
|
||||
announcements: announcements,
|
||||
followedData: followedData ?? [],
|
||||
followedDataCount: followedDataCount,
|
||||
isLoadingOwnedData: isLoadingOwnedData,
|
||||
@ -194,53 +189,26 @@ function CustomizeMyData({
|
||||
handlePlaceholderWidgetKey: handlePlaceholderWidgetKey,
|
||||
handleRemoveWidget: handleRemoveWidget,
|
||||
isEditView: true,
|
||||
isAnnouncementLoading: isAnnouncementLoading,
|
||||
})}
|
||||
</div>
|
||||
)),
|
||||
[
|
||||
layout,
|
||||
announcements,
|
||||
followedData,
|
||||
followedDataCount,
|
||||
isLoadingOwnedData,
|
||||
handleOpenAddWidgetModal,
|
||||
handlePlaceholderWidgetKey,
|
||||
handleRemoveWidget,
|
||||
isAnnouncementLoading,
|
||||
]
|
||||
);
|
||||
|
||||
const fetchAnnouncements = useCallback(async () => {
|
||||
try {
|
||||
setIsAnnouncementLoading(true);
|
||||
const response = await getActiveAnnouncement();
|
||||
|
||||
setAnnouncements(response.data);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsAnnouncementLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAnnouncements();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
handlePageDataChange({
|
||||
...initialPageData,
|
||||
data: {
|
||||
page: {
|
||||
layout: uniqBy(
|
||||
layout.filter(
|
||||
(widget) =>
|
||||
widget.i.startsWith('KnowledgePanel') &&
|
||||
!widget.i.endsWith('.EmptyWidgetPlaceholder')
|
||||
),
|
||||
'i'
|
||||
),
|
||||
layout: getUniqueFilteredLayout(layout),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -256,13 +224,18 @@ function CustomizeMyData({
|
||||
}, []);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
const newMainPanelLayout = customizePageClassBase.defaultLayout;
|
||||
// Get default layout with the empty widget added at the end
|
||||
const newMainPanelLayout = getLayoutWithEmptyWidgetPlaceholder(
|
||||
customizePageClassBase.defaultLayout,
|
||||
2,
|
||||
4
|
||||
);
|
||||
setLayout(newMainPanelLayout);
|
||||
handlePageDataChange({
|
||||
...initialPageData,
|
||||
data: {
|
||||
page: {
|
||||
layout: uniqBy(newMainPanelLayout, 'i'),
|
||||
layout: getUniqueFilteredLayout(newMainPanelLayout),
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -280,9 +253,13 @@ function CustomizeMyData({
|
||||
header={
|
||||
<Col
|
||||
className="bg-white d-flex justify-between border-bottom p-sm"
|
||||
data-testid="customize-landing-page-header"
|
||||
span={24}>
|
||||
<div className="d-flex gap-2 items-center">
|
||||
<Typography.Title className="m-0" level={5}>
|
||||
<Typography.Title
|
||||
className="m-0"
|
||||
data-testid="customize-page-title"
|
||||
level={5}>
|
||||
<Transi18next
|
||||
i18nKey="message.customize-landing-page-header"
|
||||
renderElement={
|
||||
@ -300,13 +277,23 @@ function CustomizeMyData({
|
||||
</Typography.Title>
|
||||
</div>
|
||||
<Space>
|
||||
<Button size="small" onClick={handleCancel}>
|
||||
<Button
|
||||
data-testid="cancel-button"
|
||||
size="small"
|
||||
onClick={handleCancel}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
<Button size="small" onClick={handleOpenResetModal}>
|
||||
<Button
|
||||
data-testid="reset-button"
|
||||
size="small"
|
||||
onClick={handleOpenResetModal}>
|
||||
{t('label.reset')}
|
||||
</Button>
|
||||
<Button size="small" type="primary" onClick={onSaveLayout}>
|
||||
<Button
|
||||
data-testid="save-button"
|
||||
size="small"
|
||||
type="primary"
|
||||
onClick={onSaveLayout}>
|
||||
{t('label.save')}
|
||||
</Button>
|
||||
</Space>
|
||||
@ -348,6 +335,7 @@ function CustomizeMyData({
|
||||
<Modal
|
||||
centered
|
||||
cancelText={t('label.no')}
|
||||
data-testid="reset-layout-modal"
|
||||
okText={t('label.yes')}
|
||||
open={isResetModalOpen}
|
||||
title={t('label.reset-default-layout')}
|
||||
|
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { SIZE } from '../../../enums/common.enum';
|
||||
import { LandingPageWidgetKeys } from '../../../enums/CustomizablePage.enum';
|
||||
import EmptyWidgetPlaceholder from './EmptyWidgetPlaceholder';
|
||||
import { EmptyWidgetPlaceholderProps } from './EmptyWidgetPlaceholder.interface';
|
||||
|
||||
const mockProps: EmptyWidgetPlaceholderProps = {
|
||||
iconHeight: SIZE.MEDIUM,
|
||||
iconWidth: SIZE.MEDIUM,
|
||||
widgetKey: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
handleOpenAddWidgetModal: jest.fn(),
|
||||
handlePlaceholderWidgetKey: jest.fn(),
|
||||
handleRemoveWidget: jest.fn(),
|
||||
isEditable: true,
|
||||
};
|
||||
|
||||
describe('EmptyWidgetPlaceholder component', () => {
|
||||
it('EmptyWidgetPlaceholder should render properly', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<EmptyWidgetPlaceholder
|
||||
{...mockProps}
|
||||
iconHeight={undefined}
|
||||
iconWidth={undefined}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByTestId(LandingPageWidgetKeys.ACTIVITY_FEED)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('drag-widget-button')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('remove-widget-button')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('no-data-image')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('message.adding-new-entity-is-easy-just-give-it-a-spin')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByTestId('add-widget-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('EmptyWidgetPlaceholder should display drag and remove buttons when isEditable is not passed', async () => {
|
||||
await act(async () => {
|
||||
render(<EmptyWidgetPlaceholder {...mockProps} isEditable={undefined} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('drag-widget-button')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('remove-widget-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('EmptyWidgetPlaceholder should not display drag and remove buttons when isEditable is false', async () => {
|
||||
await act(async () => {
|
||||
render(<EmptyWidgetPlaceholder {...mockProps} isEditable={false} />);
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('drag-widget-button')).toBeNull();
|
||||
expect(screen.queryByTestId('remove-widget-button')).toBeNull();
|
||||
});
|
||||
|
||||
it('EmptyWidgetPlaceholder should call handleAddClick after clicking on add widget button', async () => {
|
||||
await act(async () => {
|
||||
render(<EmptyWidgetPlaceholder {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(mockProps.handleOpenAddWidgetModal).toHaveBeenCalledTimes(0);
|
||||
expect(mockProps.handlePlaceholderWidgetKey).toHaveBeenCalledTimes(0);
|
||||
|
||||
const addButton = screen.getByTestId('add-widget-button');
|
||||
|
||||
await act(async () => userEvent.click(addButton));
|
||||
|
||||
expect(mockProps.handleOpenAddWidgetModal).toHaveBeenCalledTimes(1);
|
||||
expect(mockProps.handlePlaceholderWidgetKey).toHaveBeenCalledTimes(1);
|
||||
expect(mockProps.handlePlaceholderWidgetKey).toHaveBeenCalledWith(
|
||||
mockProps.widgetKey
|
||||
);
|
||||
});
|
||||
|
||||
it('EmptyWidgetPlaceholder should call handleRemoveWidget when clicked on remove widget button', async () => {
|
||||
await act(async () => {
|
||||
render(<EmptyWidgetPlaceholder {...mockProps} />);
|
||||
});
|
||||
|
||||
expect(mockProps.handleRemoveWidget).toHaveBeenCalledTimes(0);
|
||||
|
||||
const removeButton = screen.getByTestId('remove-widget-button');
|
||||
|
||||
await act(async () => userEvent.click(removeButton));
|
||||
|
||||
expect(mockProps.handleRemoveWidget).toHaveBeenCalledTimes(1);
|
||||
expect(mockProps.handleRemoveWidget).toHaveBeenCalledWith(
|
||||
mockProps.widgetKey
|
||||
);
|
||||
});
|
||||
});
|
@ -42,7 +42,10 @@ function EmptyWidgetPlaceholder({
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Card bodyStyle={{ height: '100%' }} className="empty-widget-placeholder">
|
||||
<Card
|
||||
bodyStyle={{ height: '100%' }}
|
||||
className="empty-widget-placeholder"
|
||||
data-testid={widgetKey}>
|
||||
<Row className="h-full">
|
||||
{isEditable && (
|
||||
<Col span={24}>
|
||||
@ -50,11 +53,16 @@ function EmptyWidgetPlaceholder({
|
||||
<Col>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
@ -80,7 +88,7 @@ function EmptyWidgetPlaceholder({
|
||||
<Button
|
||||
ghost
|
||||
className="add-button"
|
||||
data-testid="add-widget-placeholder-button"
|
||||
data-testid="add-widget-button"
|
||||
icon={<PlusOutlined />}
|
||||
type="primary"
|
||||
onClick={handleAddClick}>
|
||||
|
@ -16,7 +16,7 @@ import React, { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as ExclamationIcon } from '../../../assets/svg/ic-exclamation-circle.svg';
|
||||
import { ClientErrors } from '../../../enums/axios.enum';
|
||||
import { ClientErrors } from '../../../enums/Axios.enum';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
|
@ -216,7 +216,7 @@ const KPIWidget = ({
|
||||
return (
|
||||
<Card
|
||||
className="kpi-widget-card h-full"
|
||||
data-testid="kpi-card"
|
||||
data-testid="kpi-widget"
|
||||
id="kpi-charts"
|
||||
loading={isKPIListLoading || isLoading}>
|
||||
{isEditView && (
|
||||
@ -225,9 +225,14 @@ const KPIWidget = ({
|
||||
<Space align="center">
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -91,7 +91,10 @@ const MyDataWidgetInternal = ({
|
||||
}, [currentUser]);
|
||||
|
||||
return (
|
||||
<Card className="my-data-widget-container card-widget" loading={isLoading}>
|
||||
<Card
|
||||
className="my-data-widget-container card-widget"
|
||||
data-testid="my-data-widget"
|
||||
loading={isLoading}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<div className="d-flex justify-between m-b-xs">
|
||||
@ -115,9 +118,14 @@ const MyDataWidgetInternal = ({
|
||||
<>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
|
@ -82,8 +82,8 @@ jest.mock(
|
||||
);
|
||||
|
||||
describe('MyDataWidget component', () => {
|
||||
it('should fetch data', () => {
|
||||
act(() => {
|
||||
it('should fetch data', async () => {
|
||||
await act(async () => {
|
||||
render(<MyDataWidget widgetKey="widgetKey" />, { wrapper: MemoryRouter });
|
||||
});
|
||||
|
||||
@ -98,8 +98,8 @@ describe('MyDataWidget component', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.skip('should render header', () => {
|
||||
act(() => {
|
||||
it.skip('should render header', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MyDataWidget widgetKey="widgetKey" />
|
||||
@ -110,8 +110,8 @@ describe('MyDataWidget component', () => {
|
||||
expect(screen.getByText('label.my-data')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render view all for 0 length data', () => {
|
||||
act(() => {
|
||||
it('should not render view all for 0 length data', async () => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MyDataWidget widgetKey="widgetKey" />
|
||||
@ -126,7 +126,7 @@ describe('MyDataWidget component', () => {
|
||||
(searchData as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve(mockSearchAPIResponse)
|
||||
);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MyDataWidget widgetKey="widgetKey" />
|
||||
@ -139,7 +139,7 @@ describe('MyDataWidget component', () => {
|
||||
|
||||
it('should render table names', async () => {
|
||||
(searchData as jest.Mock).mockResolvedValueOnce(mockSearchAPIResponse);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MyDataWidget widgetKey="widgetKey" />
|
||||
|
@ -10,10 +10,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { CloseOutlined, DragOutlined } from '@ant-design/icons';
|
||||
import { Alert, Card, Col, Row, Space, Typography } from 'antd';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Alert, Col, Row, Typography } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as AnnouncementIcon } from '../../../assets/svg/announcements-v1.svg';
|
||||
import { ReactComponent as AnnouncementsEmptyIcon } from '../../../assets/svg/announcment-no-data-placeholder.svg';
|
||||
@ -28,22 +27,15 @@ import './announcements-widget.less';
|
||||
|
||||
export interface AnnouncementsWidgetProps extends WidgetCommonProps {
|
||||
isAnnouncementLoading?: boolean;
|
||||
announcements: Thread[];
|
||||
announcements?: Thread[];
|
||||
}
|
||||
|
||||
function AnnouncementsWidget({
|
||||
announcements,
|
||||
isEditView,
|
||||
handleRemoveWidget,
|
||||
widgetKey,
|
||||
announcements = [],
|
||||
isAnnouncementLoading = false,
|
||||
}: Readonly<AnnouncementsWidgetProps>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleCloseClick = useCallback(() => {
|
||||
!isUndefined(handleRemoveWidget) && handleRemoveWidget(widgetKey);
|
||||
}, [widgetKey]);
|
||||
|
||||
const announcement = useMemo(() => {
|
||||
if (isAnnouncementLoading) {
|
||||
return <Loader size="small" />;
|
||||
@ -115,27 +107,16 @@ function AnnouncementsWidget({
|
||||
}, [isAnnouncementLoading, announcements]);
|
||||
|
||||
return (
|
||||
<Card className="announcement-container card-widget h-full">
|
||||
<div className="announcement-container card-widget h-full">
|
||||
<Row justify="space-between">
|
||||
<Col>
|
||||
<Typography.Paragraph className="font-medium m-b-sm">
|
||||
{t('label.recent-announcement-plural')}
|
||||
</Typography.Paragraph>
|
||||
</Col>
|
||||
{isEditView && (
|
||||
<Col>
|
||||
<Space>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
</Space>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
{announcement}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ function FollowingWidget({
|
||||
return (
|
||||
<Card
|
||||
className="following-widget-container card-widget h-full"
|
||||
data-testid="following-data-container">
|
||||
data-testid="following-widget">
|
||||
<EntityListWithV1
|
||||
entityList={followedData}
|
||||
headerText={
|
||||
@ -72,9 +72,14 @@ function FollowingWidget({
|
||||
<>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
|
@ -17,7 +17,7 @@
|
||||
flex-direction: column;
|
||||
}
|
||||
.announcement-container-list {
|
||||
overflow-y: auto;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
max-height: 90%;
|
||||
.feed-card-body {
|
||||
|
@ -18,7 +18,7 @@ import { HeaderProps } from './PageHeader.interface';
|
||||
|
||||
const PageHeader = ({ data: { header, subHeader } }: HeaderProps) => {
|
||||
return (
|
||||
<div className="page-header-container">
|
||||
<div className="page-header-container" data-testid="page-header-container">
|
||||
<Typography.Title className="heading" data-testid="heading" level={5}>
|
||||
{header}
|
||||
</Typography.Title>
|
||||
|
@ -138,7 +138,7 @@ export const AddEditPersonaForm = ({
|
||||
hasPermission: true,
|
||||
children: (
|
||||
<Button
|
||||
data-testid="add-reviewers"
|
||||
data-testid="add-users"
|
||||
icon={<PlusOutlined style={{ color: 'white', fontSize: '12px' }} />}
|
||||
size="small"
|
||||
type="primary"
|
||||
@ -160,6 +160,7 @@ export const AddEditPersonaForm = ({
|
||||
closable={false}
|
||||
closeIcon={null}
|
||||
confirmLoading={isSaving}
|
||||
data-testid="add-edit-persona-modal"
|
||||
okText={isEditMode ? t('label.update') : t('label.create')}
|
||||
title={isEmpty(persona) ? 'Add Persona' : 'Edit Persona'}
|
||||
width={750}
|
||||
@ -178,7 +179,7 @@ export const AddEditPersonaForm = ({
|
||||
<Space
|
||||
wrap
|
||||
className="m--t-md"
|
||||
data-testid="reviewers-container"
|
||||
data-testid="users-container"
|
||||
size={[8, 8]}>
|
||||
{usersList.map((d) => (
|
||||
<UserTag
|
||||
|
@ -37,6 +37,7 @@ export const PersonaDetailsCard = ({ persona }: PersonaDetailsCardProps) => {
|
||||
<Card
|
||||
bodyStyle={{ height: '100%' }}
|
||||
className="h-full cursor-pointer"
|
||||
data-testid="persona-details-card"
|
||||
onClick={handleCardClick}>
|
||||
<Space className="justify-between h-full" direction="vertical">
|
||||
<Card.Meta
|
||||
|
@ -112,7 +112,7 @@ const TotalDataAssetsWidget = ({
|
||||
return (
|
||||
<Card
|
||||
className="total-data-insight-card"
|
||||
data-testid="entity-summary-card"
|
||||
data-testid="total-assets-widget"
|
||||
id={DataInsightChartType.TotalEntitiesByType}
|
||||
loading={isLoading}>
|
||||
{isEditView && (
|
||||
@ -120,11 +120,16 @@ const TotalDataAssetsWidget = ({
|
||||
<Col>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
|
@ -10,10 +10,11 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Icon from '@ant-design/icons';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Modal } from 'antd';
|
||||
import { isNil } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as CloseIcon } from '../../../assets/svg/ic-delete.svg';
|
||||
import { ReactComponent as IconRemove } from '../../../assets/svg/ic-remove.svg';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
|
||||
import { User } from '../../../generated/entity/teams/user';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
@ -32,9 +33,29 @@ export const UsersTab = ({ users, onRemoveUser }: UsersTabProps) => {
|
||||
const [additionalUsersDetails, setAdditionalUsersDetails] = useState<User[]>(
|
||||
[]
|
||||
);
|
||||
const [removeUserDetails, setRemoveUserDetails] =
|
||||
useState<{ state: boolean; user: User }>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleRemoveButtonClick = useCallback((user: User) => {
|
||||
setRemoveUserDetails({
|
||||
state: true,
|
||||
user,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleRemoveCancel = useCallback(() => {
|
||||
setRemoveUserDetails(undefined);
|
||||
}, []);
|
||||
|
||||
const handleRemoveConfirm = useCallback(() => {
|
||||
if (!isNil(removeUserDetails) && !isNil(onRemoveUser)) {
|
||||
onRemoveUser(removeUserDetails.user.id);
|
||||
}
|
||||
handleRemoveCancel();
|
||||
}, [removeUserDetails, handleRemoveCancel]);
|
||||
|
||||
const fetchUsersAdditionalDetails = async () => {
|
||||
try {
|
||||
setIsDetailsLoading(true);
|
||||
@ -63,19 +84,23 @@ export const UsersTab = ({ users, onRemoveUser }: UsersTabProps) => {
|
||||
title: t('label.action-plural'),
|
||||
dataIndex: 'id',
|
||||
key: 'id',
|
||||
render: (id: string) => {
|
||||
width: 90,
|
||||
render: (_: string, record: User) => {
|
||||
return (
|
||||
onRemoveUser && (
|
||||
<Icon
|
||||
component={CloseIcon}
|
||||
title={t('label.remove')}
|
||||
onClick={() => onRemoveUser(id)}
|
||||
<Button
|
||||
data-testid="remove-user-btn"
|
||||
icon={
|
||||
<IconRemove height={16} name={t('label.remove')} width={16} />
|
||||
}
|
||||
type="text"
|
||||
onClick={() => handleRemoveButtonClick(record)}
|
||||
/>
|
||||
)
|
||||
);
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
}, [onRemoveUser]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [...commonUserDetailColumns(isDetailsLoading), actionColumn],
|
||||
@ -83,26 +108,44 @@ export const UsersTab = ({ users, onRemoveUser }: UsersTabProps) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Table
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={
|
||||
isDetailsLoading ? (users as unknown as User[]) : additionalUsersDetails
|
||||
}
|
||||
loading={isDetailsLoading}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<ErrorPlaceHolder
|
||||
permission
|
||||
className="p-y-md"
|
||||
heading={t('label.user')}
|
||||
type={ERROR_PLACEHOLDER_TYPE.ASSIGN}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="fullyQualifiedName"
|
||||
size="small"
|
||||
/>
|
||||
<>
|
||||
<Table
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={
|
||||
isDetailsLoading
|
||||
? (users as unknown as User[])
|
||||
: additionalUsersDetails
|
||||
}
|
||||
loading={isDetailsLoading}
|
||||
locale={{
|
||||
emptyText: (
|
||||
<ErrorPlaceHolder
|
||||
permission
|
||||
className="p-y-md"
|
||||
heading={t('label.user')}
|
||||
type={ERROR_PLACEHOLDER_TYPE.ASSIGN}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="fullyQualifiedName"
|
||||
size="small"
|
||||
/>
|
||||
<Modal
|
||||
cancelText={t('label.cancel')}
|
||||
data-testid="remove-confirmation-modal"
|
||||
okText={t('label.confirm')}
|
||||
open={Boolean(removeUserDetails?.state)}
|
||||
title={t('label.removing-user')}
|
||||
onCancel={handleRemoveCancel}
|
||||
onOk={handleRemoveConfirm}>
|
||||
{t('message.are-you-sure-want-to-text', {
|
||||
text: t('label.remove-entity-lowercase', {
|
||||
entity: removeUserDetails?.user.name,
|
||||
}),
|
||||
})}
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -215,9 +215,14 @@ const FeedsWidget = ({
|
||||
<>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Space>
|
||||
|
@ -72,7 +72,7 @@ const RecentlyViewed = ({
|
||||
return (
|
||||
<Card
|
||||
className="recently-viewed-widget-container card-widget"
|
||||
data-testid="recently-viewed-container">
|
||||
data-testid="recently-viewed-widget">
|
||||
<EntityListSkeleton
|
||||
dataLength={data.length !== 0 ? data.length : 5}
|
||||
loading={Boolean(isLoading)}>
|
||||
@ -88,9 +88,14 @@ const RecentlyViewed = ({
|
||||
<Space>
|
||||
<DragOutlined
|
||||
className="drag-widget-icon cursor-pointer"
|
||||
data-testid="drag-widget-button"
|
||||
size={14}
|
||||
/>
|
||||
<CloseOutlined size={14} onClick={handleCloseClick} />
|
||||
<CloseOutlined
|
||||
data-testid="remove-widget-button"
|
||||
size={14}
|
||||
onClick={handleCloseClick}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
)}
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Relevanz",
|
||||
"remove": "Entfernen",
|
||||
"remove-entity": "{{entity}} entfernen",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "Entfernt",
|
||||
"removing-user": "Benutzer entfernen",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Relevance",
|
||||
"remove": "Remove",
|
||||
"remove-entity": "Remove {{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "Removed",
|
||||
"removing-user": "Removing User",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Relevancia",
|
||||
"remove": "Eliminar",
|
||||
"remove-entity": "Eliminar {{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "Eliminado",
|
||||
"removing-user": "Eliminando usuario",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Pertinence",
|
||||
"remove": "Retirer",
|
||||
"remove-entity": "Retirer un·e {{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "Retiré",
|
||||
"removing-user": "Retirer un Utilisateur",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Relevance",
|
||||
"remove": "除外",
|
||||
"remove-entity": "{{entity}}を除外",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "除外",
|
||||
"removing-user": "ユーザを除外する",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Relevância",
|
||||
"remove": "Remover",
|
||||
"remove-entity": "Remover {{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remover",
|
||||
"removed": "Removido",
|
||||
"removing-user": "Removendo Usuário",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "Актуальность",
|
||||
"remove": "Удалить",
|
||||
"remove-entity": "Удалить {{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "Удаленный",
|
||||
"removing-user": "Удаление пользователя",
|
||||
|
@ -827,6 +827,7 @@
|
||||
"relevance": "相关性",
|
||||
"remove": "删除",
|
||||
"remove-entity": "删除{{entity}}",
|
||||
"remove-entity-lowercase": "remove {{entity}}",
|
||||
"remove-lowercase": "remove",
|
||||
"removed": "已删除",
|
||||
"removing-user": "正在删除用户",
|
||||
|
@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Document } from '../generated/entity/docStore/document';
|
||||
import { Paging } from '../generated/type/paging';
|
||||
|
||||
export const mockWidgetsData: { data: Document[]; paging: Paging } = {
|
||||
data: [
|
||||
{
|
||||
id: '04fac073-d4a0-42b9-8be1-bf92b45c060b',
|
||||
name: 'ActivityFeed',
|
||||
displayName: 'Activity Feed',
|
||||
fullyQualifiedName: 'KnowledgePanel.ActivityFeed',
|
||||
description:
|
||||
'Activity Feed KnowledgePanel shows Activity Feed,Mentions and Tasks that are assigned to User.',
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['large'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
{
|
||||
id: '5bf549a2-adc7-48be-8a61-17c08aeeaa1b',
|
||||
name: 'Following',
|
||||
displayName: 'Following',
|
||||
fullyQualifiedName: 'KnowledgePanel.Following',
|
||||
description:
|
||||
'Following KnowledgePanel shows all the Assets that the User is Following.',
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['small'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
{
|
||||
id: 'f087d02c-c9ac-4986-800a-36da5074cc98',
|
||||
name: 'KPI',
|
||||
displayName: 'KPI',
|
||||
fullyQualifiedName: 'KnowledgePanel.KPI',
|
||||
description:
|
||||
"KPI KnowledgePanel shows the Organization's KPIs on description, owner coverage.",
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['small', 'medium'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
{
|
||||
id: '3539996d-9980-4d3b-914b-0d3a2f18e09d',
|
||||
name: 'MyData',
|
||||
displayName: 'MyData',
|
||||
fullyQualifiedName: 'KnowledgePanel.MyData',
|
||||
description:
|
||||
"MyData KnowledgePanel shows the list of Assets that is owned by User or User's Team.",
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['small'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
{
|
||||
id: 'd0f5f235-11bf-44d8-b215-0aa8def97d07',
|
||||
name: 'RecentlyViewed',
|
||||
displayName: 'Recently Viewed',
|
||||
fullyQualifiedName: 'KnowledgePanel.RecentlyViewed',
|
||||
description:
|
||||
'Recently Viewed KnowledgePanel shows list of Data Assets that User visited.',
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['small'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
{
|
||||
id: 'bc0d4328-1ef3-4c33-9c79-1c618b18ea92',
|
||||
name: 'TotalAssets',
|
||||
displayName: 'Total Assets',
|
||||
fullyQualifiedName: 'KnowledgePanel.TotalAssets',
|
||||
description:
|
||||
'Total Assets KnowledgePanel shows Data Asset growth across the organization.',
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['medium', 'large'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
},
|
||||
],
|
||||
paging: {
|
||||
total: 6,
|
||||
},
|
||||
};
|
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Document } from '../generated/entity/docStore/document';
|
||||
|
||||
export const mockWidget: Document = {
|
||||
id: '5bf549a2-adc7-48be-8a61-17c08aeeaa1b',
|
||||
name: 'Following',
|
||||
displayName: 'Following',
|
||||
fullyQualifiedName: 'KnowledgePanel.Following',
|
||||
description:
|
||||
'Following KnowledgePanel shows all the Assets that the User is Following.',
|
||||
entityType: 'KnowledgePanel',
|
||||
data: {
|
||||
gridSizes: ['small'],
|
||||
},
|
||||
updatedBy: 'admin',
|
||||
};
|
||||
|
||||
export const mockWidgetSizes = [
|
||||
{ label: 'Small', value: 1 },
|
||||
{ label: 'Medium', value: 2 },
|
||||
];
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2023 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 { LandingPageWidgetKeys } from '../enums/CustomizablePage.enum';
|
||||
import { Document } from '../generated/entity/docStore/document';
|
||||
import { Persona } from '../generated/entity/teams/persona';
|
||||
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
|
||||
|
||||
export const mockPersonaName = 'testPersona';
|
||||
export const mockPersonaDetails: Persona = {
|
||||
id: 'persona-123',
|
||||
name: mockPersonaName,
|
||||
};
|
||||
|
||||
const mockDefaultLayout: Array<WidgetConfig> = [
|
||||
{
|
||||
h: 6,
|
||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.MY_DATA,
|
||||
w: 1,
|
||||
x: 0,
|
||||
y: 6,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.KPI,
|
||||
w: 2,
|
||||
x: 1,
|
||||
y: 6,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 9,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 1.5,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCustomizedLayout1: Array<WidgetConfig> = [
|
||||
{
|
||||
h: 6,
|
||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 1.5,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCustomizedLayout2: Array<WidgetConfig> = [
|
||||
{
|
||||
h: 6,
|
||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 1.5,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.KPI,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockDocumentData: Document = {
|
||||
name: `${mockPersonaName}-LandingPage`,
|
||||
id: 'landing-page-123',
|
||||
fullyQualifiedName: `persona.${mockPersonaName}.Page.LandingPage`,
|
||||
entityType: 'Page',
|
||||
data: {
|
||||
page: {
|
||||
layout: mockCustomizedLayout1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockCustomizePageClassBase = {
|
||||
defaultLayout: mockDefaultLayout,
|
||||
};
|
||||
|
||||
export const mockShowErrorToast = jest.fn();
|
||||
export const mockShowSuccessToast = jest.fn();
|
@ -0,0 +1,159 @@
|
||||
/*
|
||||
* Copyright 2023 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 React from 'react';
|
||||
import { LandingPageWidgetKeys } from '../enums/CustomizablePage.enum';
|
||||
import { Document } from '../generated/entity/docStore/document';
|
||||
import { Thread, ThreadType } from '../generated/entity/feed/thread';
|
||||
import { User } from '../generated/entity/teams/user';
|
||||
import { Paging } from '../generated/type/paging';
|
||||
import { WidgetConfig } from '../pages/CustomizablePage/CustomizablePage.interface';
|
||||
|
||||
export const mockPersonaName = 'testPersona';
|
||||
|
||||
export const mockUserData: User = {
|
||||
name: 'Test User',
|
||||
email: 'testUser1@email.com',
|
||||
id: '123',
|
||||
isAdmin: true,
|
||||
follows: [
|
||||
{
|
||||
id: '12',
|
||||
type: 'table',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const mockDefaultLayout: Array<WidgetConfig> = [
|
||||
{
|
||||
h: 6,
|
||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.MY_DATA,
|
||||
w: 1,
|
||||
x: 0,
|
||||
y: 6,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.KPI,
|
||||
w: 2,
|
||||
x: 1,
|
||||
y: 6,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.TOTAL_DATA_ASSETS,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 9,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 1.5,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCustomizedLayout: Array<WidgetConfig> = [
|
||||
{
|
||||
h: 6,
|
||||
i: LandingPageWidgetKeys.ACTIVITY_FEED,
|
||||
w: 3,
|
||||
x: 0,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 1.5,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.RECENTLY_VIEWED,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 3,
|
||||
static: false,
|
||||
},
|
||||
];
|
||||
|
||||
export const mockCustomizePageClassBase = {
|
||||
defaultLayout: mockDefaultLayout,
|
||||
announcementWidget: {
|
||||
h: 3,
|
||||
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 0,
|
||||
static: true,
|
||||
} as WidgetConfig,
|
||||
landingPageMaxGridSize: 4,
|
||||
landingPageWidgetMargin: 16,
|
||||
landingPageRowHeight: 200,
|
||||
getWidgetsFromKey: (i: string) => () => <div>{i}</div>,
|
||||
};
|
||||
|
||||
export const mockDocumentData: Document = {
|
||||
name: `${mockPersonaName}-LandingPage`,
|
||||
fullyQualifiedName: `persona.${mockPersonaName}.Page.LandingPage`,
|
||||
entityType: 'Page',
|
||||
data: {
|
||||
page: {
|
||||
layout: mockCustomizedLayout,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const mockAnnouncementsData: Array<Thread> = [
|
||||
{
|
||||
id: '444c10b8-e0bb-4e0b-9286-1eeaa2efbf95',
|
||||
type: ThreadType.Announcement,
|
||||
about: '<#E::table::sample_data.ecommerce_db.shopify.dim_address>',
|
||||
message: 'Test Announcement',
|
||||
announcement: {
|
||||
description: '',
|
||||
startTime: 1701620135,
|
||||
endTime: 1701706538,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const mockActiveAnnouncementData: { data: Thread[]; paging: Paging } = {
|
||||
data: mockAnnouncementsData,
|
||||
paging: { total: 1 },
|
||||
};
|
@ -133,7 +133,7 @@ export const CustomPageSettings = () => {
|
||||
return (
|
||||
<Row
|
||||
className="customize-landing-page user-listing p-b-md"
|
||||
data-testid="user-list-v1-component"
|
||||
data-testid="custom-page-setting-component"
|
||||
gutter={[16, 16]}>
|
||||
<Col span={18}>
|
||||
<PageHeader data={PAGE_HEADERS.CUSTOM_PAGE} />
|
||||
@ -152,9 +152,11 @@ export const CustomPageSettings = () => {
|
||||
<Card
|
||||
bodyStyle={{ height: '100%' }}
|
||||
className="h-full"
|
||||
data-testid={`persona-details-card-${persona.name}`}
|
||||
extra={
|
||||
<Button
|
||||
className="text-link-color"
|
||||
data-testid="customize-page-button"
|
||||
size="small"
|
||||
type="text"
|
||||
onClick={() => handleCustomisePersona(persona)}>
|
||||
|
@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { LandingPageWidgetKeys } from '../../enums/CustomizablePage.enum';
|
||||
import { PageType } from '../../generated/system/ui/page';
|
||||
import {
|
||||
mockCustomizePageClassBase,
|
||||
mockDocumentData,
|
||||
mockPersonaDetails,
|
||||
mockPersonaName,
|
||||
mockShowErrorToast,
|
||||
mockShowSuccessToast,
|
||||
} from '../../mocks/CustomizablePage.mock';
|
||||
import { getDocumentByFQN } from '../../rest/DocStoreAPI';
|
||||
import { getPersonaByName } from '../../rest/PersonaAPI';
|
||||
import { CustomizablePage } from './CustomizablePage';
|
||||
import { WidgetConfig } from './CustomizablePage.interface';
|
||||
|
||||
jest.mock(
|
||||
'../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder',
|
||||
() => {
|
||||
return jest.fn().mockImplementation(() => <div>ErrorPlaceHolder</div>);
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
'../../components/CustomizableComponents/CustomizeMyData/CustomizeMyData',
|
||||
() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(
|
||||
({ initialPageData, handleSaveCurrentPageLayout }) => (
|
||||
<div data-testid="customize-my-data">
|
||||
{initialPageData.data.page.layout.map((widget: WidgetConfig) => (
|
||||
<div key={widget.i}>{widget.i}</div>
|
||||
))}
|
||||
<div onClick={handleSaveCurrentPageLayout}>
|
||||
handleSaveCurrentPageLayout
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
jest.mock('../../components/Loader/Loader', () => {
|
||||
return jest.fn().mockImplementation(() => <div>Loader</div>);
|
||||
});
|
||||
|
||||
jest.mock('../../utils/CustomizePageClassBase', () => {
|
||||
return mockCustomizePageClassBase;
|
||||
});
|
||||
|
||||
jest.mock('../../rest/DocStoreAPI', () => ({
|
||||
createDocument: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockDocumentData)),
|
||||
getDocumentByFQN: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockDocumentData)),
|
||||
updateDocument: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockDocumentData)),
|
||||
}));
|
||||
|
||||
jest.mock('../../rest/PersonaAPI', () => ({
|
||||
getPersonaByName: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockPersonaDetails)),
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/ToastUtils', () => ({
|
||||
showErrorToast: mockShowErrorToast,
|
||||
showSuccessToast: mockShowSuccessToast,
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useParams: jest.fn().mockImplementation(() => ({
|
||||
fqn: mockPersonaName,
|
||||
pageFqn: PageType.LandingPage,
|
||||
})),
|
||||
Link: jest.fn().mockImplementation(() => <div>Link</div>),
|
||||
}));
|
||||
|
||||
describe('CustomizablePage component', () => {
|
||||
it('CustomizablePage should show ErrorPlaceholder if the API to fetch the persona details fails', async () => {
|
||||
(getPersonaByName as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject(new Error('API failure'))
|
||||
);
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('ErrorPlaceHolder')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('customize-my-data')).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizablePage should show Loader while the layout is being fetched', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
|
||||
expect(screen.getByText('Loader')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ErrorPlaceHolder')).toBeNull();
|
||||
expect(screen.queryByTestId('customize-my-data')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('CustomizablePage should pass the correct page layout data for the persona', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('customize-my-data')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ErrorPlaceHolder')).toBeNull();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.ACTIVITY_FEED)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.FOLLOWING)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.RECENTLY_VIEWED)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText(LandingPageWidgetKeys.MY_DATA)).toBeNull();
|
||||
expect(screen.queryByText(LandingPageWidgetKeys.KPI)).toBeNull();
|
||||
expect(
|
||||
screen.queryByText(LandingPageWidgetKeys.TOTAL_DATA_ASSETS)
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('CustomizablePage should pass the default layout data when no layout is present for the persona', async () => {
|
||||
(getDocumentByFQN as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject({
|
||||
response: {
|
||||
status: 404,
|
||||
},
|
||||
})
|
||||
);
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('customize-my-data')).toBeInTheDocument();
|
||||
expect(screen.queryByText('ErrorPlaceHolder')).toBeNull();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.ACTIVITY_FEED)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.FOLLOWING)
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.RECENTLY_VIEWED)
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText(LandingPageWidgetKeys.MY_DATA)).toBeInTheDocument();
|
||||
expect(screen.getByText(LandingPageWidgetKeys.KPI)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(LandingPageWidgetKeys.TOTAL_DATA_ASSETS)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('CustomizablePage should update the layout when layout data is present for persona', async () => {
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
const saveCurrentPageLayoutBtn = screen.getByText(
|
||||
'handleSaveCurrentPageLayout'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(saveCurrentPageLayoutBtn);
|
||||
});
|
||||
|
||||
expect(mockShowSuccessToast).toHaveBeenCalledWith(
|
||||
'server.page-layout-operation-success'
|
||||
);
|
||||
});
|
||||
|
||||
it('CustomizablePage should save the layout when no layout data present for persona', async () => {
|
||||
(getDocumentByFQN as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject({
|
||||
response: {
|
||||
status: 404,
|
||||
},
|
||||
})
|
||||
);
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
const saveCurrentPageLayoutBtn = screen.getByText(
|
||||
'handleSaveCurrentPageLayout'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(saveCurrentPageLayoutBtn);
|
||||
});
|
||||
|
||||
expect(mockShowSuccessToast).toHaveBeenCalledWith(
|
||||
'server.page-layout-operation-success'
|
||||
);
|
||||
});
|
||||
|
||||
it('CustomizablePage should return null for invalid page FQN', async () => {
|
||||
(useParams as jest.Mock).mockImplementation(() => ({
|
||||
fqn: mockPersonaName,
|
||||
pageFqn: 'invalidName',
|
||||
}));
|
||||
|
||||
await act(async () => {
|
||||
render(<CustomizablePage />);
|
||||
});
|
||||
|
||||
expect(screen.queryByText('ErrorPlaceHolder')).toBeNull();
|
||||
expect(screen.queryByText('Loader')).toBeNull();
|
||||
expect(screen.queryByTestId('customize-my-data')).toBeNull();
|
||||
});
|
||||
});
|
@ -24,7 +24,7 @@ import {
|
||||
GlobalSettingOptions,
|
||||
GlobalSettingsMenuCategory,
|
||||
} from '../../constants/GlobalSettings.constants';
|
||||
import { ClientErrors } from '../../enums/axios.enum';
|
||||
import { ClientErrors } from '../../enums/Axios.enum';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { Document } from '../../generated/entity/docStore/document';
|
||||
@ -96,6 +96,8 @@ export const CustomizablePage = () => {
|
||||
page: { layout: customizePageClassBase.defaultLayout },
|
||||
},
|
||||
});
|
||||
} else {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
|
@ -44,7 +44,7 @@ import './my-data.less';
|
||||
|
||||
const ReactGridLayout = WidthProvider(RGL);
|
||||
|
||||
const MyDataPageV1 = () => {
|
||||
const MyDataPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentUser } = useAuthContext();
|
||||
const { selectedPersona } = useApplicationConfigContext();
|
||||
@ -142,7 +142,15 @@ const MyDataPageV1 = () => {
|
||||
|
||||
const widgets = useMemo(
|
||||
() =>
|
||||
layout.map((widget) => (
|
||||
// Adding announcement widget to the layout when announcements are present
|
||||
// Since the widget wont be in the layout config of the page
|
||||
// ok
|
||||
[
|
||||
...(isEmpty(announcements)
|
||||
? []
|
||||
: [customizePageClassBase.announcementWidget]),
|
||||
...layout,
|
||||
].map((widget) => (
|
||||
<div data-grid={widget} key={widget.i}>
|
||||
{getWidgetFromKey({
|
||||
announcements: announcements,
|
||||
@ -214,4 +222,4 @@ const MyDataPageV1 = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default MyDataPageV1;
|
||||
export default MyDataPage;
|
@ -0,0 +1,275 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { useApplicationConfigContext } from '../../components/ApplicationConfigProvider/ApplicationConfigProvider';
|
||||
import {
|
||||
mockActiveAnnouncementData,
|
||||
mockCustomizePageClassBase,
|
||||
mockDocumentData,
|
||||
mockPersonaName,
|
||||
mockUserData,
|
||||
} from '../../mocks/MyDataPage.mock';
|
||||
import { getDocumentByFQN } from '../../rest/DocStoreAPI';
|
||||
import { getActiveAnnouncement } from '../../rest/feedsAPI';
|
||||
import MyDataPage from './MyDataPage.component';
|
||||
|
||||
const mockLocalStorage = (() => {
|
||||
let store: Record<string, string> = {};
|
||||
|
||||
return {
|
||||
getItem(key: string) {
|
||||
return store[key] || '';
|
||||
},
|
||||
setItem(key: string, value: string) {
|
||||
store[key] = value.toString();
|
||||
},
|
||||
clear() {
|
||||
store = {};
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: mockLocalStorage,
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
|
||||
() => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="activity-feed-provider">{children}</div>
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('../../components/Loader/Loader', () => {
|
||||
return jest.fn().mockImplementation(() => <div>Loader</div>);
|
||||
});
|
||||
|
||||
jest.mock('../../utils/CustomizePageClassBase', () => {
|
||||
return mockCustomizePageClassBase;
|
||||
});
|
||||
|
||||
jest.mock('../../components/PageLayoutV1/PageLayoutV1', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="page-layout-v1">{children}</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../components/WelcomeScreen/WelcomeScreen.component', () => {
|
||||
return jest
|
||||
.fn()
|
||||
.mockImplementation(({ onClose }) => (
|
||||
<div onClick={onClose}>WelcomeScreen</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'../../components/ApplicationConfigProvider/ApplicationConfigProvider',
|
||||
() => ({
|
||||
useApplicationConfigContext: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ selectedPersona: mockPersonaName })),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../components/Auth/AuthProviders/AuthProvider', () => ({
|
||||
useAuthContext: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ currentUser: mockUserData })),
|
||||
}));
|
||||
|
||||
jest.mock('../../rest/DocStoreAPI', () => ({
|
||||
getDocumentByFQN: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockDocumentData)),
|
||||
}));
|
||||
|
||||
jest.mock('../../rest/feedsAPI', () => ({
|
||||
getActiveAnnouncement: jest
|
||||
.fn()
|
||||
.mockImplementation(() => mockActiveAnnouncementData),
|
||||
}));
|
||||
|
||||
jest.mock('../../rest/userAPI', () => ({
|
||||
getUserById: jest.fn().mockImplementation(() => mockUserData),
|
||||
}));
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: jest.fn().mockImplementation(() => ({ pathname: '' })),
|
||||
}));
|
||||
|
||||
jest.mock('react-grid-layout', () => ({
|
||||
WidthProvider: jest
|
||||
.fn()
|
||||
.mockImplementation(() =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(({ children }) => (
|
||||
<div data-testid="react-grid-layout">{children}</div>
|
||||
))
|
||||
),
|
||||
__esModule: true,
|
||||
default: '',
|
||||
}));
|
||||
|
||||
jest.mock('../../hooks/authHooks', () => ({
|
||||
useAuth: jest.fn().mockImplementation(() => ({ isAuthDisabled: false })),
|
||||
}));
|
||||
|
||||
describe('MyDataPage component', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.setItem('loggedInUsers', mockUserData.name);
|
||||
});
|
||||
|
||||
it('MyDataPage should only display WelcomeScreen when user logs in for the first time', async () => {
|
||||
// Simulate no user is logged in condition
|
||||
localStorage.clear();
|
||||
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('WelcomeScreen')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('activity-feed-provider')).toBeNull();
|
||||
});
|
||||
|
||||
it('MyDataPage should display the main content after the WelcomeScreen is closed', async () => {
|
||||
// Simulate no user is logged in condition
|
||||
localStorage.clear();
|
||||
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
const welcomeScreen = screen.getByText('WelcomeScreen');
|
||||
|
||||
expect(welcomeScreen).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('activity-feed-provider')).toBeNull();
|
||||
|
||||
await act(async () => userEvent.click(welcomeScreen));
|
||||
|
||||
expect(screen.queryByText('WelcomeScreen')).toBeNull();
|
||||
expect(screen.getByTestId('activity-feed-provider')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('react-grid-layout')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('MyDataPage should display loader initially while loading data', async () => {
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
|
||||
expect(screen.queryByText('WelcomeScreen')).toBeNull();
|
||||
expect(screen.queryByTestId('react-grid-layout')).toBeNull();
|
||||
expect(screen.getByTestId('activity-feed-provider')).toBeInTheDocument();
|
||||
expect(screen.getByText('Loader')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('MyDataPage should display all the widgets in the config and the announcements widget if there are announcements', async () => {
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('KnowledgePanel.ActivityFeed')).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.Following')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.RecentlyViewed')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.Announcements')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('KnowledgePanel.KPI')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.TotalAssets')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.MyData')).toBeNull();
|
||||
});
|
||||
|
||||
it('MyDataPage should not render announcement widget if there are no announcements', async () => {
|
||||
(getActiveAnnouncement as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
response: { ...mockActiveAnnouncementData, data: [] },
|
||||
})
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('KnowledgePanel.ActivityFeed')).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.Following')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.RecentlyViewed')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.queryByText('KnowledgePanel.Announcements')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.KPI')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.TotalAssets')).toBeNull();
|
||||
expect(screen.queryByText('KnowledgePanel.MyData')).toBeNull();
|
||||
});
|
||||
|
||||
it('MyDataPage should render default widgets when getDocumentByFQN API fails', async () => {
|
||||
(getDocumentByFQN as jest.Mock).mockImplementationOnce(() =>
|
||||
Promise.reject(new Error('API failure'))
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
|
||||
expect(screen.getByText('KnowledgePanel.ActivityFeed')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.RecentlyViewed')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.Following')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText('KnowledgePanel.Announcements')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.KPI')).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.TotalAssets')).toBeInTheDocument();
|
||||
expect(screen.getByText('KnowledgePanel.MyData')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('MyDataPage should render default widgets when there is no selected persona', async () => {
|
||||
(useApplicationConfigContext as jest.Mock).mockImplementation(() => ({
|
||||
selectedPersona: {},
|
||||
}));
|
||||
|
||||
await act(async () => {
|
||||
render(<MyDataPage />);
|
||||
});
|
||||
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.ActivityFeed')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.RecentlyViewed')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.Following')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.Announcements')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByText('KnowledgePanel.KPI')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.TotalAssets')
|
||||
).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByText('KnowledgePanel.MyData')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -231,7 +231,10 @@ export const PersonaDetailsPage = () => {
|
||||
multiSelect
|
||||
selectedUsers={personaDetails.users ?? []}
|
||||
onUpdate={(users) => handlePersonaUpdate({ users })}>
|
||||
<Button size="small" type="primary">
|
||||
<Button
|
||||
data-testid="add-user-button"
|
||||
size="small"
|
||||
type="primary">
|
||||
{t('label.add-entity', { entity: t('label.user') })}
|
||||
</Button>
|
||||
</UserSelectableList>
|
||||
|
@ -123,7 +123,7 @@ export const PersonaPage = () => {
|
||||
<Col span={6}>
|
||||
<Space align="center" className="w-full justify-end" size={16}>
|
||||
<Button
|
||||
data-testid="add-user"
|
||||
data-testid="add-persona-button"
|
||||
type="primary"
|
||||
onClick={handleAddNewPersona}>
|
||||
{t('label.add-entity', { entity: t('label.persona') })}
|
||||
|
@ -18,7 +18,7 @@ import { TOUR_SEARCH_TERM } from '../../constants/constants';
|
||||
import { CurrentTourPageType } from '../../enums/tour.enum';
|
||||
import { getTourSteps } from '../../utils/TourUtils';
|
||||
import ExplorePageV1Component from '../ExplorePage/ExplorePageV1.component';
|
||||
import MyDataPageV1 from '../MyDataPage/MyDataPageV1.component';
|
||||
import MyDataPage from '../MyDataPage/MyDataPage.component';
|
||||
import TableDetailsPageV1 from '../TableDetailsPageV1/TableDetailsPageV1';
|
||||
|
||||
const TourPage = () => {
|
||||
@ -41,7 +41,7 @@ const TourPage = () => {
|
||||
const currentPageComponent = useMemo(() => {
|
||||
switch (currentTourPage) {
|
||||
case CurrentTourPageType.MY_DATA_PAGE:
|
||||
return <MyDataPageV1 />;
|
||||
return <MyDataPage />;
|
||||
|
||||
case CurrentTourPageType.EXPLORE_PAGE:
|
||||
return <ExplorePageV1Component />;
|
||||
|
@ -12,7 +12,14 @@
|
||||
*/
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { capitalize, isEmpty, isUndefined, max, uniqueId } from 'lodash';
|
||||
import {
|
||||
capitalize,
|
||||
isEmpty,
|
||||
isUndefined,
|
||||
max,
|
||||
uniqBy,
|
||||
uniqueId,
|
||||
} from 'lodash';
|
||||
import React from 'react';
|
||||
import { Layout } from 'react-grid-layout';
|
||||
import EmptyWidgetPlaceholder from '../components/CustomizableComponents/EmptyWidgetPlaceholder/EmptyWidgetPlaceholder';
|
||||
@ -190,7 +197,7 @@ export const getWidgetFromKey = ({
|
||||
handleOpenAddWidgetModal?: () => void;
|
||||
handlePlaceholderWidgetKey?: (key: string) => void;
|
||||
handleRemoveWidget?: (key: string) => void;
|
||||
announcements: Thread[];
|
||||
announcements?: Thread[];
|
||||
followedData?: EntityReference[];
|
||||
followedDataCount: number;
|
||||
isLoadingOwnedData: boolean;
|
||||
@ -250,3 +257,14 @@ export const getLayoutWithEmptyWidgetPlaceholder = (
|
||||
isDraggable: false,
|
||||
},
|
||||
];
|
||||
|
||||
// Function to filter out empty widget placeholders and only keep knowledge panels
|
||||
export const getUniqueFilteredLayout = (layout: WidgetConfig[]) =>
|
||||
uniqBy(
|
||||
layout.filter(
|
||||
(widget) =>
|
||||
widget.i.startsWith('KnowledgePanel') &&
|
||||
!widget.i.endsWith('.EmptyWidgetPlaceholder')
|
||||
),
|
||||
'i'
|
||||
);
|
||||
|
@ -57,6 +57,15 @@ class CustomizePageClassBase {
|
||||
totalAssets: 3,
|
||||
};
|
||||
|
||||
announcementWidget: WidgetConfig = {
|
||||
h: this.landingPageWidgetDefaultHeights.announcements,
|
||||
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 0,
|
||||
static: true, // Making announcement widget fixed on top right position
|
||||
};
|
||||
|
||||
defaultLayout: Array<WidgetConfig> = [
|
||||
{
|
||||
h: this.landingPageWidgetDefaultHeights.activityFeed,
|
||||
@ -90,14 +99,6 @@ class CustomizePageClassBase {
|
||||
y: 9,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: this.landingPageWidgetDefaultHeights.announcements,
|
||||
i: LandingPageWidgetKeys.ANNOUNCEMENTS,
|
||||
w: 1,
|
||||
x: 3,
|
||||
y: 0,
|
||||
static: false,
|
||||
},
|
||||
{
|
||||
h: this.landingPageWidgetDefaultHeights.following,
|
||||
i: LandingPageWidgetKeys.FOLLOWING,
|
||||
|
@ -24,7 +24,7 @@ import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTex
|
||||
import Loader from '../components/Loader/Loader';
|
||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||
import { getExplorePath } from '../constants/constants';
|
||||
import { SettledStatus } from '../enums/axios.enum';
|
||||
import { SettledStatus } from '../enums/Axios.enum';
|
||||
import { ExplorePageTabs } from '../enums/Explore.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { Classification } from '../generated/entity/classification/classification';
|
||||
|
@ -15,7 +15,7 @@ import { AxiosError } from 'axios';
|
||||
import { isEmpty, isString } from 'lodash';
|
||||
import React from 'react';
|
||||
import { toast } from 'react-toastify';
|
||||
import { ClientErrors } from '../enums/axios.enum';
|
||||
import { ClientErrors } from '../enums/Axios.enum';
|
||||
import i18n from './i18next/LocalUtil';
|
||||
import { getErrorText } from './StringsUtils';
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import { OidcUser } from '../components/Auth/AuthProviders/AuthProvider.interface';
|
||||
import { WILD_CARD_CHAR } from '../constants/char.constants';
|
||||
import { SettledStatus } from '../enums/axios.enum';
|
||||
import { SettledStatus } from '../enums/Axios.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { SearchResponse } from '../interface/search.interface';
|
||||
import { getSearchedTeams, getSearchedUsers } from '../rest/miscAPI';
|
||||
|
Loading…
x
Reference in New Issue
Block a user