chore(ui): added cypress tests for version pages and related bug fixes (#12943)

* Fixed version page flaky no data placeholder
removed code duplication in DataModelVersion component

* fixed clear tier functionality for containers and data models

* Added cypress tests for version pages

* fixed unit tests
This commit is contained in:
Aniket Katkar 2023-08-21 20:07:34 +05:30 committed by GitHub
parent f6fc2e3b0c
commit 4fa5a1d8bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1865 additions and 869 deletions

View File

@ -0,0 +1,96 @@
/*
* 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.
*/
// / <reference types="Cypress" />
import { isUndefined } from 'lodash';
import {
interceptURL,
verifyResponseStatusCode,
visitDataModelPage,
visitEntityDetailsPage,
} from './common';
export const visitEntityDetailsVersionPage = (
entityDetails,
id,
entityFQN,
version
) => {
visitEntityDetailsPage(
entityDetails.term,
entityDetails.serviceName,
entityDetails.entity,
undefined,
entityDetails.entityType
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/name/${entityFQN}?*include=all`,
'getTableDetails'
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${id}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${id}/versions/${version ?? '*'}`,
'getSelectedVersionDetails'
);
cy.get('[data-testid="version-button"]').as('versionButton');
if (!isUndefined(version)) {
cy.get('@versionButton').contains(version);
}
cy.get('@versionButton').click();
verifyResponseStatusCode('@getTableDetails', 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
};
export const visitDataModelVersionPage = (
dataModelFQN,
dataModelId,
dataModelName,
version
) => {
visitDataModelPage(dataModelFQN, dataModelName);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions/${version ?? '*'}`,
'getSelectedVersionDetails'
);
cy.get('[data-testid="version-button"]').as('versionButton');
if (!isUndefined(version)) {
cy.get('@versionButton').contains(version);
}
cy.get('@versionButton').click();
verifyResponseStatusCode('@getDataModelDetails', 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
};

View File

@ -1090,71 +1090,155 @@ export const updateDescriptionForIngestedTables = (
.should('contain', description);
};
export const followAndOwnTheEntity = (termObj) => {
// search for the term and redirect to the respective entity tab
export const addOwner = (ownerName, entity, isGlossaryPage) => {
cy.get('[data-testid="edit-owner"]').click();
interceptURL('GET', '/api/v1/users?&isBot=false&limit=15', 'getUsers');
cy.get('.ant-tabs [id*=tab-users]').click();
verifyResponseStatusCode('@getUsers', 200);
visitEntityDetailsPage(termObj.term, termObj.serviceName, termObj.entity);
// go to manage tab and search for logged in user and set the owner
interceptURL(
'GET',
'/api/v1/search/query?q=*%20AND%20teamType:Group&from=0&size=15&index=team_search_index',
'getTeams'
`api/v1/search/query?q=*${encodeURI(ownerName)}*`,
'searchOwner'
);
cy.get('[data-testid="edit-owner"]').should('be.visible').click();
verifyResponseStatusCode('@getTeams', 200);
// Clicking on users tab
cy.get('.user-team-select-popover')
.contains('Users')
.should('exist')
.should('be.visible')
.click();
cy.get('[data-testid="owner-select-users-search-bar"]').type(ownerName);
cy.get('[data-testid="selectable-list"]')
.eq(1)
.should('exist')
.should('be.visible')
.find('[title="admin"]')
.should('be.visible')
.click();
verifyResponseStatusCode('@searchOwner', 200);
cy.get('[data-testid="owner-link"]')
.scrollIntoView()
.invoke('text')
.then((text) => {
expect(text).equal('admin');
});
interceptURL('PATCH', `/api/v1/${entity}/*`, 'patchOwner');
interceptURL('PUT', '/api/v1/*/*/followers', 'waitAfterFollow');
cy.get('[data-testid="follow-button"]')
.scrollIntoView()
.should('be.visible')
.click();
verifyResponseStatusCode('@waitAfterFollow', 200);
cy.clickOnLogo();
cy.get('[data-testid="message-container"]')
.first()
.contains(`Followed ${termObj.entity.slice(0, -1)}`)
.should('be.visible');
// checks newly generated feed for follow and setting owner
cy.get('[data-testid="message-container"]')
.eq(1)
.scrollIntoView()
.contains('Added owner: admin')
.should('be.visible');
// Check followed entity on mydata page
cy.get('[data-testid="following-data-container"]')
.find(`[data-testid="following-${termObj.displayName}"]`)
.should('be.visible');
// Check owned entity
cy.get('[data-testid="my-data-container"]')
.find(`[data-testid="My data-${termObj.displayName}"]`)
.should('be.visible');
cy.clickOnLogo();
cy.get(`.ant-popover [title="${ownerName}"]`).click();
verifyResponseStatusCode('@patchOwner', 200);
if (isGlossaryPage) {
cy.get('[data-testid="glossary-owner-name"]').should('contain', ownerName);
} else {
cy.get('[data-testid="owner-link"]').should('contain', ownerName);
}
};
export const removeOwner = (entity, isGlossaryPage) => {
interceptURL('PATCH', `/api/v1/${entity}/*`, 'patchOwner');
cy.get('[data-testid="edit-owner"]').click();
cy.get('[data-testid="remove-owner"]').click();
verifyResponseStatusCode('@patchOwner', 200);
if (isGlossaryPage) {
cy.get('[data-testid="glossary-owner-name"] > [data-testid="Add"]').should(
'be.visible'
);
} else {
cy.get('[data-testid="owner-link"]').should('contain', 'No Owner');
}
};
export const addTier = (tier, entity) => {
interceptURL('PATCH', `/api/v1/${entity}/*`, 'patchTier');
interceptURL('GET', '/api/v1/tags?parent=Tier&limit=10', 'fetchTier');
cy.get('[data-testid="edit-tier"]').click();
verifyResponseStatusCode('@fetchTier', 200);
cy.get('[data-testid="radio-btn-Tier1"]').click({ waitForAnimations: true });
verifyResponseStatusCode('@patchTier', 200);
cy.get('[data-testid="radio-btn-Tier1"]').should('be.checked');
cy.clickOutside();
cy.get('[data-testid="Tier"]').should('contain', tier);
};
export const removeTier = (entity) => {
interceptURL('PATCH', `/api/v1/${entity}/*`, 'patchTier');
cy.get('[data-testid="edit-tier"]').click();
cy.get('[data-testid="clear-tier"]').should('be.visible').click();
verifyResponseStatusCode('@patchTier', 200);
cy.get('[data-testid="Tier"]').should('contain', 'No Tier');
};
export const deleteEntity = (
entityName,
serviceName,
entity,
entityType,
successMessageEntityName,
deletionType = 'hard'
) => {
visitEntityDetailsPage(
entityName,
serviceName,
entity,
undefined,
entityType
);
cy.get('[data-testid="manage-button"]').click();
cy.get('[data-testid="delete-button-title"]').click();
cy.get('.ant-modal-header').should('contain', `Delete ${entityName}`);
cy.get(`[data-testid="${deletionType}-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/${entity}/*?hardDelete=${deletionType === 'hard'}&recursive=false`,
`${deletionType}DeleteTable`
);
cy.get('[data-testid="confirm-button"]').should('not.be.disabled');
cy.get('[data-testid="confirm-button"]').click();
verifyResponseStatusCode(`@${deletionType}DeleteTable`, 200);
toastNotification(`${successMessageEntityName} deleted successfully!`, false);
};
export const visitDataModelPage = (dataModelFQN, dataModelName) => {
interceptURL('GET', '/api/v1/teams/name/*', 'getOrganization');
cy.get('[data-testid="appbar-item-settings"]').click();
verifyResponseStatusCode('@getOrganization', 200);
interceptURL('GET', '/api/v1/services/dashboardServices*', 'getServices');
cy.get('[data-menu-id*="services.dashboards"]').scrollIntoView().click();
verifyResponseStatusCode('@getServices', 200);
interceptURL(
'GET',
'api/v1/services/dashboardServices/name/sample_looker?fields=*',
'getDashboardDetails'
);
interceptURL(
'GET',
'/api/v1/dashboard/datamodels?service=sample_looker&fields=*',
'getDataModels'
);
cy.get('[data-testid="service-name-sample_looker"]').scrollIntoView().click();
verifyResponseStatusCode('@getDashboardDetails', 200);
verifyResponseStatusCode('@getDataModels', 200);
cy.get('[data-testid="data-model"]').scrollIntoView().click();
verifyResponseStatusCode('@getDataModels', 200);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/name/${dataModelFQN}*`,
'getDataModelDetails'
);
cy.get(`[data-testid="data-model-${dataModelName}"]`)
.scrollIntoView()
.click();
verifyResponseStatusCode('@getDataModelDetails', 200);
};

View File

@ -0,0 +1,677 @@
/*
* 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.
*/
export const OWNER = 'Amber Green';
export const TIER = 'Tier1';
const TABLE_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_table',
columns: [
{
name: 'user_id',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
description:
'Unique identifier for the user of your Shopify POS or your Shopify admin.',
},
{
name: 'shop_id',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
description:
'The ID of the store. This column is a foreign key reference to the shop_id column in the dim.shop table.',
},
{
name: 'name',
dataType: 'VARCHAR',
dataLength: 100,
dataTypeDisplay: 'varchar',
description: 'Name of the staff member.',
children: [
{
name: 'first_name',
dataType: 'VARCHAR',
dataLength: 100,
dataTypeDisplay: 'varchar',
description: 'First name of the staff member.',
},
{
name: 'last_name',
dataType: 'VARCHAR',
dataLength: 100,
dataTypeDisplay: 'varchar',
},
],
},
{
name: 'email',
dataType: 'VARCHAR',
dataLength: 100,
dataTypeDisplay: 'varchar',
description: 'Email address of the staff member.',
},
],
databaseSchema: 'sample_data.ecommerce_db.shopify',
};
export const TABLE_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/columns/2/children/1/description',
value: 'Last name of the staff member.',
},
{
op: 'remove',
path: '/columns/2/children/0/description',
},
{
op: 'add',
path: '/columns/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/columns/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_table',
},
];
const TOPIC_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_topic',
service: 'sample_kafka',
messageSchema: {
schemaText: `{"type":"object","required":["name","age","club_name"],"properties":{"name":{"type":"object","required":["first_name","last_name"],
"properties":{"first_name":{"type":"string"},"last_name":{"type":"string"}}},"age":{"type":"integer"},"club_name":{"type":"string"}}}`,
schemaType: 'JSON',
schemaFields: [
{
name: 'default',
dataType: 'RECORD',
fullyQualifiedName: 'sample_kafka.cypress_version_test_topic.default',
tags: [],
children: [
{
name: 'name',
dataType: 'RECORD',
fullyQualifiedName:
'sample_kafka.cypress_version_test_topic.default.name',
tags: [],
children: [
{
name: 'first_name',
dataType: 'STRING',
description: 'Description for schema field first_name',
fullyQualifiedName:
'sample_kafka.cypress_version_test_topic.default.name.first_name',
tags: [],
},
{
name: 'last_name',
dataType: 'STRING',
fullyQualifiedName:
'sample_kafka.cypress_version_test_topic.default.name.last_name',
tags: [],
},
],
},
{
name: 'age',
dataType: 'INT',
fullyQualifiedName:
'sample_kafka.cypress_version_test_topic.default.age',
tags: [],
},
{
name: 'club_name',
dataType: 'STRING',
fullyQualifiedName:
'sample_kafka.cypress_version_test_topic.default.club_name',
tags: [],
},
],
},
],
},
partitions: 128,
};
export const TOPIC_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/messageSchema/schemaFields/0/children/0/children/1/description',
value: 'Description for schema field last_name',
},
{
op: 'remove',
path: '/messageSchema/schemaFields/0/children/0/children/0/description',
},
{
op: 'add',
path: '/messageSchema/schemaFields/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/messageSchema/schemaFields/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_topic',
},
];
const DASHBOARD_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_dashboard',
service: 'sample_superset',
};
const DASHBOARD_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_dashboard',
},
];
const PIPELINE_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_pipeline',
tasks: [
{
name: 'cypress_task_1',
displayName: 'cypress_task_1',
fullyQualifiedName:
'sample_airflow.cypress_version_test_pipeline.cypress_task_1',
sourceUrl:
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
downstreamTasks: [],
taskType: 'SnowflakeOperator',
tags: [],
},
{
name: 'cypress_task_2',
displayName: 'cypress_task_2',
fullyQualifiedName:
'sample_airflow.cypress_version_test_pipeline.cypress_task_2',
description: 'Description for task cypress_task_2',
sourceUrl:
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
downstreamTasks: [],
taskType: 'HiveOperator',
tags: [],
},
{
name: 'cypress_task_3',
displayName: 'cypress_task_3',
fullyQualifiedName:
'sample_airflow.cypress_version_test_pipeline.cypress_task_3',
sourceUrl:
'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
downstreamTasks: [],
taskType: 'HiveOperator',
tags: [],
},
],
service: 'sample_airflow',
};
const PIPELINE_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/tasks/2/description',
value: 'Description for task cypress_task_3',
},
{
op: 'remove',
path: '/tasks/1/description',
},
{
op: 'add',
path: '/tasks/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/tasks/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_pipeline',
},
];
const ML_MODEL_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_ml_model',
algorithm: 'Neural Network',
mlFeatures: [
{
name: 'feature_1',
dataType: 'numerical',
fullyQualifiedName: 'mlflow_svc.cypress_version_test_ml_model.feature_1',
featureSources: [],
tags: [],
},
{
name: 'feature_2',
dataType: 'numerical',
description: 'Description for mlFeature feature_2',
fullyQualifiedName: 'mlflow_svc.cypress_version_test_ml_model.feature_2',
featureSources: [],
},
{
name: 'feature_3',
dataType: 'numerical',
fullyQualifiedName: 'mlflow_svc.cypress_version_test_ml_model.feature_3',
featureSources: [],
},
],
tags: [],
service: 'mlflow_svc',
};
const ML_MODEL_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/mlFeatures/2/description',
value: 'Description for mlFeature feature_3',
},
{
op: 'remove',
path: '/mlFeatures/1/description',
},
{
op: 'add',
path: '/mlFeatures/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/mlFeatures/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_ml_model',
},
];
const CONTAINER_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_container',
service: 's3_storage_sample',
dataModel: {
isPartitioned: false,
columns: [
{
name: 'column_1',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
fullyQualifiedName:
's3_storage_sample.departments.finance.cypress_version_test_container.column_1',
tags: [],
ordinalPosition: 1,
},
{
name: 'column_2',
dataType: 'BOOLEAN',
dataTypeDisplay: 'boolean',
description: 'Description for column column_2',
fullyQualifiedName:
's3_storage_sample.departments.finance.cypress_version_test_container.column_2',
tags: [],
ordinalPosition: 2,
},
{
name: 'column_3',
dataType: 'BOOLEAN',
dataTypeDisplay: 'boolean',
fullyQualifiedName:
's3_storage_sample.departments.finance.cypress_version_test_container.column_3',
tags: [],
ordinalPosition: 3,
},
{
name: 'column_4',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
fullyQualifiedName:
's3_storage_sample.departments.finance.cypress_version_test_container.column_4',
tags: [],
ordinalPosition: 4,
},
],
},
tags: [],
};
const CONTAINER_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/dataModel/columns/2/description',
value: 'Description for column column_3',
},
{
op: 'remove',
path: '/dataModel/columns/1/description',
},
{
op: 'add',
path: '/dataModel/columns/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/dataModel/columns/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_container',
},
];
export const ENTITY_DETAILS_FOR_VERSION_TEST = {
Table: {
name: 'cypress_version_test_table',
term: 'cypress_version_test_table',
serviceName: 'sample_data',
entity: 'tables',
entityCreationDetails: TABLE_DETAILS_FOR_VERSION_TEST,
entityPatchPayload: TABLE_PATCH_PAYLOAD,
isChildrenExist: true,
childSelector: 'data-row-key',
entityAddedDescription: 'Description for cypress_version_test_table',
updatedTagEntityChildName: 'user_id',
entityChildRemovedDescription: 'First name of the staff member.',
entityChildAddedDescription: 'Last name of the staff member.',
},
Topic: {
name: 'cypress_version_test_topic',
term: 'cypress_version_test_topic',
serviceName: 'sample_kafka',
entity: 'topics',
entityCreationDetails: TOPIC_DETAILS_FOR_VERSION_TEST,
entityPatchPayload: TOPIC_PATCH_PAYLOAD,
isChildrenExist: true,
childSelector: 'data-row-key',
entityAddedDescription: 'Description for cypress_version_test_topic',
updatedTagEntityChildName: 'default',
entityChildRemovedDescription: 'Description for schema field first_name',
entityChildAddedDescription: 'Description for schema field last_name',
},
// TODO - Remove the comment after this issue is resolved https://github.com/open-metadata/OpenMetadata/issues/12924
// Dashboard: {
// name: 'cypress_version_test_dashboard',
// term: 'cypress_version_test_dashboard',
// serviceName: 'sample_superset',
// entity: 'dashboards',
// entityCreationDetails: DASHBOARD_DETAILS_FOR_VERSION_TEST,
// entityPatchPayload: DASHBOARD_PATCH_PAYLOAD,
// isChildrenExist: false,
// entityAddedDescription: 'Description for cypress_version_test_dashboard',
// },
Pipeline: {
name: 'cypress_version_test_pipeline',
term: 'cypress_version_test_pipeline',
serviceName: 'sample_airflow',
entity: 'pipelines',
entityCreationDetails: PIPELINE_DETAILS_FOR_VERSION_TEST,
entityPatchPayload: PIPELINE_PATCH_PAYLOAD,
isChildrenExist: true,
childSelector: 'data-row-key',
entityAddedDescription: 'Description for cypress_version_test_pipeline',
updatedTagEntityChildName: 'cypress_task_1',
entityChildRemovedDescription: 'Description for task cypress_task_2',
entityChildAddedDescription: 'Description for task cypress_task_3',
},
'ML Model': {
name: 'cypress_version_test_ml_model',
term: 'cypress_version_test_ml_model',
serviceName: 'mlflow_svc',
entity: 'mlmodels',
entityCreationDetails: ML_MODEL_DETAILS_FOR_VERSION_TEST,
entityPatchPayload: ML_MODEL_PATCH_PAYLOAD,
isChildrenExist: true,
childSelector: 'data-testid',
entityAddedDescription: 'Description for cypress_version_test_ml_model',
updatedTagEntityChildName: 'feature-card-feature_1',
entityChildRemovedDescription: 'Description for mlFeature feature_2',
entityChildAddedDescription: 'Description for mlFeature feature_3',
},
Container: {
name: 'cypress_version_test_container',
term: 'cypress_version_test_container',
serviceName: 's3_storage_sample',
entity: 'containers',
entityCreationDetails: CONTAINER_DETAILS_FOR_VERSION_TEST,
entityPatchPayload: CONTAINER_PATCH_PAYLOAD,
isChildrenExist: true,
childSelector: 'data-row-key',
entityAddedDescription: 'Description for cypress_version_test_container',
updatedTagEntityChildName: 'column_1',
entityChildRemovedDescription: 'Description for column column_2',
entityChildAddedDescription: 'Description for column column_3',
},
};
export const DATA_MODEL_DETAILS_FOR_VERSION_TEST = {
name: 'cypress_version_test_data_model',
service: 'sample_looker',
dataModelType: 'LookMlExplore',
columns: [
{
name: 'column_1',
dataType: 'VARCHAR',
dataLength: 256,
dataTypeDisplay: 'varchar',
fullyQualifiedName:
'sample_looker.model.cypress_version_test_data_model.column_1',
tags: [],
ordinalPosition: 1,
},
{
name: 'column_2',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
description: 'Description for column column_2',
fullyQualifiedName:
'sample_looker.model.cypress_version_test_data_model.column_2',
tags: [],
ordinalPosition: 2,
},
{
name: 'column_3',
dataType: 'NUMERIC',
dataTypeDisplay: 'numeric',
fullyQualifiedName:
'sample_looker.model.cypress_version_test_data_model.column_3',
tags: [],
ordinalPosition: 3,
},
],
};
export const DATA_MODEL_PATCH_PAYLOAD = [
{
op: 'add',
path: '/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.SpecialCategory',
},
},
{
op: 'add',
path: '/columns/2/description',
value: 'Description for column column_3',
},
{
op: 'remove',
path: '/columns/1/description',
},
{
op: 'add',
path: '/columns/0/tags/0',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PersonalData.Personal',
},
},
{
op: 'add',
path: '/columns/0/tags/1',
value: {
labelType: 'Manual',
state: 'Confirmed',
source: 'Classification',
tagFQN: 'PII.Sensitive',
},
},
{
op: 'add',
path: '/description',
value: 'Description for cypress_version_test_data_model',
},
];
export const DATA_MODEL_DETAILS = {
name: 'cypress_version_test_data_model',
entity: 'containers',
entityAddedDescription: 'Description for cypress_version_test_data_model',
updatedTagEntityChildName: 'column_1',
entityChildRemovedDescription: 'Description for column column_2',
entityChildAddedDescription: 'Description for column column_3',
};

View File

@ -13,8 +13,12 @@
// / <reference types="Cypress" />
import {
addOwner,
addTier,
descriptionBox,
interceptURL,
removeOwner,
removeTier,
verifyResponseStatusCode,
visitEntityDetailsPage,
} from '../../common/common';
@ -44,58 +48,15 @@ const glossaryTerm = 'GlossaryTermOwnerTest';
const OWNER = 'Amber Green';
const TIER = 'Tier1';
const addRemoveOwner = (isGlossaryPage) => {
cy.get('[data-testid="edit-owner"]').click();
cy.get('.ant-tabs [id*=tab-users]').click();
verifyResponseStatusCode('@getUsers', 200);
interceptURL(
'GET',
`api/v1/search/query?q=*${encodeURI(OWNER)}*`,
'searchOwner'
);
cy.get('[data-testid="owner-select-users-search-bar"]').type(OWNER);
verifyResponseStatusCode('@searchOwner', 200);
cy.get(`.ant-popover [title="${OWNER}"]`).click();
verifyResponseStatusCode('@patchOwner', 200);
if (isGlossaryPage) {
cy.get('[data-testid="glossary-owner-name"]').should('contain', OWNER);
} else {
cy.get('[data-testid="owner-link"]').should('contain', OWNER);
}
cy.get('[data-testid="edit-owner"]').click();
cy.get('[data-testid="remove-owner"]').click();
verifyResponseStatusCode('@patchOwner', 200);
if (isGlossaryPage) {
cy.get('[data-testid="glossary-owner-name"] > [data-testid="Add"]').should(
'be.visible'
);
} else {
cy.get('[data-testid="owner-link"]').should('contain', 'No Owner');
}
const addRemoveOwner = (ownerName, entity, isGlossaryPage) => {
addOwner(ownerName, entity, isGlossaryPage);
removeOwner(entity, isGlossaryPage);
};
const addRemoveTier = () => {
interceptURL('GET', '/api/v1/tags?parent=Tier&limit=10', 'fetchTier');
cy.get('[data-testid="edit-tier"]').click();
verifyResponseStatusCode('@fetchTier', 200);
cy.get('[data-testid="radio-btn-Tier1"]').click({ waitForAnimations: true });
verifyResponseStatusCode('@patchOwner', 200);
cy.get('[data-testid="radio-btn-Tier1"]').should('be.checked');
const addRemoveTier = (tier, entity) => {
addTier(tier, entity);
cy.clickOutside();
cy.get('[data-testid="Tier"]').should('contain', TIER);
cy.get('[data-testid="edit-tier"]').click();
cy.get('[data-testid="clear-tier"]').should('be.visible').click();
verifyResponseStatusCode('@patchOwner', 200);
cy.get('[data-testid="Tier"]').should('contain', 'No Tier');
removeTier(entity);
};
describe('Add and Remove Owner', () => {
@ -113,8 +74,6 @@ describe('Add and Remove Owner', () => {
Object.entries(ENTITIES).map(([key, value]) => {
it(`${key} details page`, () => {
interceptURL('PATCH', `/api/v1/${value.entity}/*`, 'patchOwner');
visitEntityDetailsPage(
value.term,
value.serviceName,
@ -125,7 +84,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@entityPermission', 200);
verifyResponseStatusCode('@activityFeed', 200);
addRemoveOwner();
addRemoveOwner(OWNER, value.entity);
});
});
@ -144,7 +103,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@entityPermission', 200);
verifyResponseStatusCode('@schemaDetails', 200);
verifyResponseStatusCode('@activityFeed', 200);
addRemoveOwner();
addRemoveOwner(OWNER, 'databaseSchemas');
});
it('database details page', () => {
@ -162,7 +121,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@entityPermission', 200);
verifyResponseStatusCode('@databaseDetails', 200);
verifyResponseStatusCode('@activityFeed', 200);
addRemoveOwner();
addRemoveOwner(OWNER, 'databases');
});
it('service details page', () => {
@ -192,7 +151,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@serviceDetails', 200);
verifyResponseStatusCode('@databases', 200);
addRemoveOwner();
addRemoveOwner(OWNER, 'databaseServices');
});
it('Test suite details page', () => {
@ -219,7 +178,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@entityPermission', 200);
verifyResponseStatusCode('@testSuiteDetails', 200);
verifyResponseStatusCode('@testCases', 200);
addRemoveOwner();
addRemoveOwner(OWNER, 'testSuites');
});
it('Teams details page', () => {
@ -235,7 +194,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@getOrganization', 200);
verifyResponseStatusCode('@teamPermission', 200);
addRemoveOwner();
addRemoveOwner(OWNER, 'teams');
});
it('Glossary details page', () => {
@ -259,7 +218,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@getGlossaries', 200);
verifyResponseStatusCode('@glossaryPermission', 200);
addRemoveOwner(true);
addRemoveOwner(OWNER, 'glossaries', true);
});
it('GlossaryTerm details page', () => {
@ -307,7 +266,7 @@ describe('Add and Remove Owner', () => {
verifyResponseStatusCode('@glossaryTermPermission', 200);
verifyResponseStatusCode('@getGlossaryTerms', 200);
addRemoveOwner(true);
addRemoveOwner(OWNER, 'glossaryTerms', true);
});
it('Delete glossary and glossaryTerm', () => {
@ -365,8 +324,6 @@ describe('Add and Remove Tier', () => {
Object.entries(ENTITIES).map(([key, value]) => {
it(`${key} details page`, () => {
interceptURL('PATCH', `/api/v1/${value.entity}/*`, 'patchOwner');
visitEntityDetailsPage(
value.term,
value.serviceName,
@ -377,12 +334,11 @@ describe('Add and Remove Tier', () => {
verifyResponseStatusCode('@entityPermission', 200);
verifyResponseStatusCode('@activityFeed', 200);
addRemoveTier();
addRemoveTier(TIER, value.entity);
});
});
it('database details page', () => {
interceptURL('PATCH', '/api/v1/databases/*', 'patchOwner');
interceptURL('GET', '/api/v1/databases/name/*', 'databaseDetails');
const value = ENTITIES.table;
visitEntityDetailsPage(value.term, value.serviceName, value.entity);
@ -397,6 +353,6 @@ describe('Add and Remove Tier', () => {
verifyResponseStatusCode('@databaseDetails', 200);
verifyResponseStatusCode('@activityFeed', 200);
addRemoveTier();
addRemoveTier(TIER, 'databases');
});
});

View File

@ -0,0 +1,279 @@
/*
* 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.
*/
// / <reference types="Cypress" />
import {
addOwner,
addTier,
interceptURL,
removeOwner,
removeTier,
toastNotification,
verifyResponseStatusCode,
visitDataModelPage,
} from '../../common/common';
import { visitDataModelVersionPage } from '../../common/VersionUtils';
import { DELETE_TERM } from '../../constants/constants';
import {
DATA_MODEL_DETAILS,
DATA_MODEL_DETAILS_FOR_VERSION_TEST,
DATA_MODEL_PATCH_PAYLOAD,
OWNER,
TIER,
} from '../../constants/Version.constants';
describe('Data model version page should work properly', () => {
const dataModelName = DATA_MODEL_DETAILS.name;
let dataModelId;
let dataModelFQN;
beforeEach(() => {
cy.login();
});
it('Prerequisite for data model version page tests', () => {
const token = localStorage.getItem('oidcIdToken');
cy.request({
method: 'PUT',
url: `/api/v1/dashboard/datamodels`,
headers: { Authorization: `Bearer ${token}` },
body: DATA_MODEL_DETAILS_FOR_VERSION_TEST,
}).then((response) => {
expect(response.status).to.eq(201);
dataModelId = response.body.id;
dataModelFQN = response.body.fullyQualifiedName;
cy.request({
method: 'PATCH',
url: `/api/v1/dashboard/datamodels/${dataModelId}`,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json-patch+json',
},
body: DATA_MODEL_PATCH_PAYLOAD,
}).then((response) => {
expect(response.status).to.eq(200);
});
});
});
it('Data model version page should show description and tag changes properly', () => {
visitDataModelVersionPage(dataModelFQN, dataModelId, dataModelName, '0.2');
cy.get(
`[data-testid="diff-added-${DATA_MODEL_DETAILS.entityAddedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="entity-right-panel"] .diff-added [data-testid="tag-PersonalData.SpecialCategory"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-row-key="${DATA_MODEL_DETAILS.updatedTagEntityChildName}"] .diff-added [data-testid="tag-PersonalData.Personal"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-row-key="${DATA_MODEL_DETAILS.updatedTagEntityChildName}"] .diff-added [data-testid="tag-PII.Sensitive"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="diff-removed-${DATA_MODEL_DETAILS.entityChildRemovedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="diff-added-${DATA_MODEL_DETAILS.entityChildAddedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
});
it(`Data model version page should show removed tags changes properly`, () => {
visitDataModelPage(dataModelFQN, dataModelName);
cy.get(
'[data-testid="entity-right-panel"] [data-testid="edit-button"]'
).click();
cy.get(
'[data-testid="selected-tag-PersonalData.SpecialCategory"] [data-testid="remove-tags"]'
).click();
interceptURL(
'PATCH',
`/api/v1/dashboard/datamodels/${dataModelId}`,
`patchDataModel`
);
cy.get('[data-testid="saveAssociatedTag"]').click();
verifyResponseStatusCode(`@patchDataModel`, 200);
cy.get('[data-testid="version-button"]').contains('0.3').click();
cy.get(
`[data-testid="entity-right-panel"] .diff-removed [data-testid="tag-PersonalData.SpecialCategory"]`
)
.scrollIntoView()
.should('be.visible');
});
it(`Data model version page should show owner changes properly`, () => {
visitDataModelPage(dataModelFQN, dataModelName);
cy.get('[data-testid="version-button"]').as('versionButton');
cy.get('@versionButton').contains('0.3');
addOwner(OWNER, 'dashboard/datamodels');
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/name/${dataModelFQN}*`,
`getDataModelDetails`
);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions/0.4`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.4').click();
verifyResponseStatusCode(`@getDataModelDetails`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-added-${OWNER}"]`)
.scrollIntoView()
.should('be.visible');
cy.get('@versionButton').contains('0.4').click();
removeOwner('dashboard/datamodels');
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions/0.5`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.5').click();
verifyResponseStatusCode(`@getDataModelDetails`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-removed-${OWNER}"]`)
.scrollIntoView()
.should('be.visible');
});
it(`Data model version page should show tier changes properly`, () => {
visitDataModelPage(dataModelFQN, dataModelName);
cy.get('[data-testid="version-button"]').as('versionButton');
cy.get('@versionButton').contains('0.5');
addTier(TIER, 'dashboard/datamodels');
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/name/${dataModelFQN}*`,
`getDataModelDetails`
);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions/0.6`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.6').click();
verifyResponseStatusCode(`@getDataModelDetails`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-added-${TIER}"]`)
.scrollIntoView()
.should('be.visible');
cy.get('@versionButton').contains('0.6').click();
removeTier('dashboard/datamodels');
interceptURL(
'GET',
`/api/v1/dashboard/datamodels/${dataModelId}/versions/0.7`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.7').click();
verifyResponseStatusCode(`@getDataModelDetails`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-removed-${TIER}"]`)
.scrollIntoView()
.should('be.visible');
});
it(`Cleanup for data model version page test`, () => {
visitDataModelPage(dataModelFQN, dataModelName);
cy.get('[data-testid="manage-button"]').click();
cy.get('[data-testid="delete-button-title"]').click();
cy.get('.ant-modal-header').should('contain', `Delete ${dataModelName}`);
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/dashboard/datamodels/*?hardDelete=true&recursive=false`,
`hardDeleteTable`
);
cy.get('[data-testid="confirm-button"]').should('not.be.disabled');
cy.get('[data-testid="confirm-button"]').click();
verifyResponseStatusCode(`@hardDeleteTable`, 200);
toastNotification(`Dashboard Data Model deleted successfully!`, false);
});
});

View File

@ -0,0 +1,289 @@
/*
* 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.
*/
// / <reference types="Cypress" />
import {
addOwner,
addTier,
deleteEntity,
interceptURL,
removeOwner,
removeTier,
verifyResponseStatusCode,
visitEntityDetailsPage,
} from '../../common/common';
import { visitEntityDetailsVersionPage } from '../../common/VersionUtils';
import {
ENTITY_DETAILS_FOR_VERSION_TEST,
OWNER,
TIER,
} from '../../constants/Version.constants';
Object.entries(ENTITY_DETAILS_FOR_VERSION_TEST).map(
([entityType, entityDetails]) => {
describe(`${entityType} version page should work properly`, () => {
const successMessageEntityName =
entityType === 'ML Model' ? 'Mlmodel' : entityType;
let entityId;
let entityFQN;
beforeEach(() => {
cy.login();
});
it(`Prerequisite for ${entityType} version page test`, () => {
const token = localStorage.getItem('oidcIdToken');
cy.request({
method: 'PUT',
url: `/api/v1/${entityDetails.entity}`,
headers: { Authorization: `Bearer ${token}` },
body: entityDetails.entityCreationDetails,
}).then((response) => {
expect(response.status).to.eq(201);
entityId = response.body.id;
entityFQN = response.body.fullyQualifiedName;
cy.request({
method: 'PATCH',
url: `/api/v1/${entityDetails.entity}/${entityId}`,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json-patch+json',
},
body: entityDetails.entityPatchPayload,
}).then((response) => {
expect(response.status).to.eq(200);
});
});
});
it(`${entityType} version page should show description and tag changes properly`, () => {
visitEntityDetailsVersionPage(
entityDetails,
entityId,
entityFQN,
'0.2'
);
cy.get(
`[data-testid="diff-added-${entityDetails.entityAddedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="entity-right-panel"] .diff-added [data-testid="tag-PersonalData.SpecialCategory"]`
)
.scrollIntoView()
.should('be.visible');
if (entityDetails.isChildrenExist) {
cy.get(
`[${entityDetails.childSelector}="${entityDetails.updatedTagEntityChildName}"] .diff-added [data-testid="tag-PersonalData.Personal"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[${entityDetails.childSelector}="${entityDetails.updatedTagEntityChildName}"] .diff-added [data-testid="tag-PII.Sensitive"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="diff-removed-${entityDetails.entityChildRemovedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
cy.get(
`[data-testid="diff-added-${entityDetails.entityChildAddedDescription}"]`
)
.scrollIntoView()
.should('be.visible');
}
});
it(`${entityType} version page should show removed tags changes properly`, () => {
visitEntityDetailsPage(
entityDetails.term,
entityDetails.serviceName,
entityDetails.entity,
undefined,
entityType
);
cy.get(
'[data-testid="entity-right-panel"] [data-testid="edit-button"]'
).click();
cy.get(
'[data-testid="selected-tag-PersonalData.SpecialCategory"] [data-testid="remove-tags"]'
).click();
interceptURL(
'PATCH',
`/api/v1/${entityDetails.entity}/${entityId}`,
`patch${entityType}`
);
cy.get('[data-testid="saveAssociatedTag"]').click();
verifyResponseStatusCode(`@patch${entityType}`, 200);
cy.get('[data-testid="version-button"]').contains('0.3').click();
cy.get(
`[data-testid="entity-right-panel"] .diff-removed [data-testid="tag-PersonalData.SpecialCategory"]`
)
.scrollIntoView()
.should('be.visible');
});
it(`${entityType} version page should show owner changes properly`, () => {
visitEntityDetailsPage(
entityDetails.term,
entityDetails.serviceName,
entityDetails.entity,
undefined,
entityType
);
cy.get('[data-testid="version-button"]').as('versionButton');
cy.get('@versionButton').contains('0.3');
addOwner(OWNER, entityDetails.entity);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/name/${entityFQN}?*include=all`,
`get${entityType}Details`
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions/0.4`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.4').click();
verifyResponseStatusCode(`@get${entityType}Details`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-added-${OWNER}"]`)
.scrollIntoView()
.should('be.visible');
cy.get('@versionButton').contains('0.4').click();
removeOwner(entityDetails.entity);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions/0.5`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.5').click();
verifyResponseStatusCode(`@get${entityType}Details`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-removed-${OWNER}"]`)
.scrollIntoView()
.should('be.visible');
});
it(`${entityType} version page should show tier changes properly`, () => {
visitEntityDetailsPage(
entityDetails.term,
entityDetails.serviceName,
entityDetails.entity,
undefined,
entityType
);
cy.get('[data-testid="version-button"]').as('versionButton');
cy.get('@versionButton').contains('0.5');
addTier(TIER, entityDetails.entity);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/name/${entityFQN}?*include=all`,
`get${entityType}Details`
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions`,
'getVersionsList'
);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions/0.6`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.6').click();
verifyResponseStatusCode(`@get${entityType}Details`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-added-${TIER}"]`)
.scrollIntoView()
.should('be.visible');
cy.get('@versionButton').contains('0.6').click();
removeTier(entityDetails.entity);
interceptURL(
'GET',
`/api/v1/${entityDetails.entity}/${entityId}/versions/0.7`,
'getSelectedVersionDetails'
);
cy.get('@versionButton').contains('0.7').click();
verifyResponseStatusCode(`@get${entityType}Details`, 200);
verifyResponseStatusCode('@getVersionsList', 200);
verifyResponseStatusCode('@getSelectedVersionDetails', 200);
cy.get(`[data-testid="diff-removed-${TIER}"]`)
.scrollIntoView()
.should('be.visible');
});
it(`Cleanup for ${entityType} version page test`, () => {
deleteEntity(
entityDetails.name,
entityDetails.serviceName,
entityDetails.entity,
entityType,
successMessageEntityName
);
});
});
}
);

View File

@ -215,10 +215,6 @@ const ContainerVersion: React.FC<ContainerVersionProp> = ({
]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -117,37 +117,6 @@ describe('ContainerVersion tests', () => {
expect(versionTable).toBeNull();
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<ContainerVersion
{...containerVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
const loader = screen.queryByText('Loader');
const dataAssetsVersionHeader = screen.queryByText(
'DataAssetsVersionHeader'
);
const schemaTabLabel = screen.queryByText('label.schema');
const customPropertyTabLabel = screen.queryByText(
'label.custom-property-plural'
);
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
const versionTable = screen.queryByText('VersionTable');
expect(errorPlaceHolder).toBeInTheDocument();
expect(loader).toBeNull();
expect(entityVersionTimeLine).toBeNull();
expect(dataAssetsVersionHeader).toBeNull();
expect(schemaTabLabel).toBeNull();
expect(customPropertyTabLabel).toBeNull();
expect(versionTable).toBeNull();
});
it('Should update url on click of tab', async () => {
await act(async () => {
render(<ContainerVersion {...containerVersionMockProps} />);

View File

@ -234,10 +234,6 @@ const DashboardVersion: FC<DashboardVersionProp> = ({
[description, tableColumn, currentVersionData, entityPermissions]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -16,7 +16,6 @@ import userEvent from '@testing-library/user-event';
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import {
dashboardVersionProps,
mockNoChartData,
@ -222,39 +221,4 @@ describe('DashboardVersion tests', () => {
'/dashboard/sample_superset.eta_predictions_performance/versions/0.3/custom_properties'
);
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<DashboardVersion
{...dashboardVersionProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>,
{
wrapper: MemoryRouter,
}
);
});
const versionData = screen.queryByTestId('version-data');
const schemaTable = screen.queryByTestId('schema-table');
const tabs = screen.queryByTestId('tabs');
const description = screen.queryByText('Description.component');
const richTextEditorPreviewer = screen.queryByText(
'RichTextEditorPreviewer.component'
);
const entityVersionTimeLine = screen.queryByText(
'EntityVersionTimeLine.component'
);
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
expect(entityVersionTimeLine).toBeNull();
expect(versionData).toBeNull();
expect(schemaTable).toBeNull();
expect(tabs).toBeNull();
expect(description).toBeNull();
expect(richTextEditorPreviewer).toBeNull();
expect(errorPlaceHolder).toBeInTheDocument();
});
});

View File

@ -14,13 +14,11 @@
import { Col, Row, Space, Tabs, TabsProps } from 'antd';
import classNames from 'classnames';
import DescriptionV1 from 'components/common/description/DescriptionV1';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import DataAssetsVersionHeader from 'components/DataAssets/DataAssetsVersionHeader/DataAssetsVersionHeader';
import EntityVersionTimeLine from 'components/Entity/EntityVersionTimeLine/EntityVersionTimeLine';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import VersionTable from 'components/VersionTable/VersionTable.component';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { EntityTabs, EntityType, FqnPart } from 'enums/entity.enum';
import {
ChangeDescription,
@ -28,27 +26,18 @@ import {
DashboardDataModel,
} from 'generated/entity/data/dashboardDataModel';
import { TagSource } from 'generated/type/schema';
import { EntityDiffProps } from 'interface/EntityVersion.interface';
import { cloneDeep, isEqual } from 'lodash';
import { cloneDeep } from 'lodash';
import React, { FC, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { getPartialNameFromTableFQN } from 'utils/CommonUtils';
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
import { EntityField } from '../../constants/Feeds.constants';
import { TagLabel } from '../../generated/type/tagLabel';
import {
getChangedEntityName,
getChangedEntityNewValue,
getChangedEntityOldValue,
getColumnsDataWithVersionChanges,
getCommonExtraInfoForVersionDetails,
getDiffByFieldName,
getEntityVersionByField,
getEntityVersionTags,
getTagsDiff,
getTextDiff,
isEndsWithField,
} from '../../utils/EntityVersionUtils';
import { TagLabelWithStatus } from '../../utils/EntityVersionUtils.interface';
import Loader from '../Loader/Loader';
import { DataModelVersionProp } from './DataModelVersion.interface';
@ -64,181 +53,24 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
backHandler,
versionHandler,
dataModelFQN,
entityPermissions,
}: DataModelVersionProp) => {
const { t } = useTranslation();
const [changeDescription, setChangeDescription] = useState<ChangeDescription>(
currentVersionData.changeDescription as ChangeDescription
);
const getChangeColName = (name: string | undefined) => {
const nameArr = name?.split(FQN_SEPARATOR_CHAR);
if (nameArr?.length === 3) {
return nameArr.slice(-2, -1)[0];
} else {
return nameArr?.slice(-3, -1)?.join('.');
}
};
const { ownerDisplayName, ownerRef, tierDisplayName } = useMemo(
() => getCommonExtraInfoForVersionDetails(changeDescription, owner, tier),
[changeDescription, owner, tier]
);
const handleColumnDescriptionChangeDiff = (
colList: DashboardDataModel['columns'],
columnsDiff: EntityDiffProps,
changedColName: string | undefined
) => {
const oldDescription = getChangedEntityOldValue(columnsDiff);
const newDescription = getChangedEntityNewValue(columnsDiff);
const formatColumnData = (arr: DashboardDataModel['columns']) => {
arr?.forEach((i) => {
if (isEqual(i.name, changedColName)) {
i.description = getTextDiff(
oldDescription ?? '',
newDescription ?? '',
i.description
);
} else {
formatColumnData(i?.children as DashboardDataModel['columns']);
}
});
};
formatColumnData(colList);
};
const handleColumnTagChangeDiff = (
colList: DashboardDataModel['columns'],
columnsDiff: EntityDiffProps,
changedColName: string | undefined
) => {
const oldTags: Array<TagLabel> = JSON.parse(
getChangedEntityOldValue(columnsDiff) ?? '[]'
);
const newTags: Array<TagLabel> = JSON.parse(
getChangedEntityNewValue(columnsDiff) ?? '[]'
);
const formatColumnData = (arr: DashboardDataModel['columns']) => {
arr?.forEach((i) => {
if (isEqual(i.name, changedColName)) {
const flag: { [x: string]: boolean } = {};
const uniqueTags: Array<TagLabelWithStatus> = [];
const tagsDiff = getTagsDiff(oldTags, newTags);
[...tagsDiff, ...(i.tags as Array<TagLabelWithStatus>)].forEach(
(elem: TagLabelWithStatus) => {
if (!flag[elem.tagFQN]) {
flag[elem.tagFQN] = true;
uniqueTags.push(elem);
}
}
);
i.tags = uniqueTags;
} else {
formatColumnData(i?.children as DashboardDataModel['columns']);
}
});
};
formatColumnData(colList);
};
const handleColumnDiffAdded = (
colList: DashboardDataModel['columns'],
columnsDiff: EntityDiffProps
) => {
const newCol: Array<Column> = JSON.parse(
columnsDiff.added?.newValue ?? '[]'
);
newCol.forEach((col) => {
const formatColumnData = (arr: DashboardDataModel['columns']) => {
arr?.forEach((i) => {
if (isEqual(i.name, col.name)) {
i.tags = col.tags?.map((tag) => ({ ...tag, added: true }));
i.description = getTextDiff('', col.description ?? '');
i.dataTypeDisplay = getTextDiff('', col.dataTypeDisplay ?? '');
i.name = getTextDiff('', col.name);
} else {
formatColumnData(i?.children as DashboardDataModel['columns']);
}
});
};
formatColumnData(colList);
});
};
const handleColumnDiffDeleted = (columnsDiff: EntityDiffProps) => {
const newCol: Array<Column> = JSON.parse(
columnsDiff.deleted?.oldValue ?? '[]'
);
return newCol.map((col) => ({
...col,
tags: col.tags?.map((tag) => ({ ...tag, removed: true })),
description: getTextDiff(col.description ?? '', ''),
dataTypeDisplay: getTextDiff(col.dataTypeDisplay ?? '', ''),
name: getTextDiff(col.name, ''),
}));
};
const columns: DashboardDataModel['columns'] = useMemo(() => {
const colList = cloneDeep(
(currentVersionData as DashboardDataModel).columns || []
);
const columnsDiff = getDiffByFieldName(
EntityField.COLUMNS,
changeDescription
);
const changedColName = getChangeColName(getChangedEntityName(columnsDiff));
const colNameWithoutQuotes = changedColName?.replaceAll(/(^")|("$)/g, '');
if (
isEndsWithField(
EntityField.DESCRIPTION,
getChangedEntityName(columnsDiff)
)
) {
handleColumnDescriptionChangeDiff(
colList,
columnsDiff,
colNameWithoutQuotes
);
return colList;
} else if (
isEndsWithField(EntityField.TAGS, getChangedEntityName(columnsDiff))
) {
handleColumnTagChangeDiff(colList, columnsDiff, colNameWithoutQuotes);
return colList;
} else {
const columnsDiff = getDiffByFieldName(
EntityField.COLUMNS,
changeDescription,
true
);
let newColumns: Column[] = [];
if (columnsDiff.added) {
handleColumnDiffAdded(colList, columnsDiff);
}
if (columnsDiff.deleted) {
newColumns = handleColumnDiffDeleted(columnsDiff);
} else {
return colList;
}
return [...newColumns, ...colList];
}
}, [
currentVersionData,
changeDescription,
getChangeColName,
handleColumnDescriptionChangeDiff,
]);
return getColumnsDataWithVersionChanges<Column>(changeDescription, colList);
}, [currentVersionData, changeDescription]);
useEffect(() => {
setChangeDescription(
@ -319,10 +151,6 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
[description, dataModelFQN, columns]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
<div data-testid="data-model-version-container">

View File

@ -11,7 +11,6 @@
* limitations under the License.
*/
import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { VersionData } from 'pages/EntityVersionPage/EntityVersionPage.component';
import { EntityHistory } from '../../generated/type/entityHistory';
@ -31,5 +30,4 @@ export interface DataModelVersionProp {
deleted?: boolean;
backHandler: () => void;
versionHandler: (v: string) => void;
entityPermissions: OperationPermission;
}

View File

@ -43,9 +43,13 @@ const DataModelTable = ({
key: 'displayName',
width: 350,
render: (_, record: ServicePageData) => {
const dataModelDisplayName = getEntityName(record);
return (
<Link to={getDataModelDetailsPath(record.fullyQualifiedName || '')}>
{getEntityName(record)}
<Link
data-testid={`data-model-${dataModelDisplayName}`}
to={getDataModelDetailsPath(record.fullyQualifiedName || '')}>
{dataModelDisplayName}
</Link>
);
},

View File

@ -123,7 +123,7 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
</div>
</Col>
)}
<Col span={24}>
<Col data-testid={`${source.service?.name}-${source.name}`} span={24}>
{isTourOpen ? (
<Button data-testid={source.fullyQualifiedName} type="link">
<Typography.Text

View File

@ -17,7 +17,6 @@ import { Mlmodel } from '../../generated/entity/data/mlmodel';
export interface MlModelDetailProp extends HTMLAttributes<HTMLDivElement> {
mlModelDetail: Mlmodel;
version?: string;
fetchMlModel: () => void;
followMlModelHandler: () => Promise<void>;
unFollowMlModelHandler: () => Promise<void>;

View File

@ -163,7 +163,7 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
<Card
bordered
className="m-b-xlg"
data-testid="feature-card"
data-testid={`feature-card-${feature.name ?? ''}`}
key={feature.fullyQualifiedName}>
<Row>
<Col className="m-b-xs" span={24}>
@ -319,10 +319,6 @@ const MlModelVersion: FC<MlModelVersionProp> = ({
[description, mlFeaturesData, currentVersionData, entityPermissions]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -114,35 +114,6 @@ describe('MlModelVersion tests', () => {
expect(customPropertyTabLabel).toBeNull();
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<MlModelVersion
{...mlModelVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
const loader = screen.queryByText('Loader');
const dataAssetsVersionHeader = screen.queryByText(
'DataAssetsVersionHeader'
);
const featureTabLabel = screen.queryByText('label.feature-plural');
const customPropertyTabLabel = screen.queryByText(
'label.custom-property-plural'
);
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
expect(errorPlaceHolder).toBeInTheDocument();
expect(loader).toBeNull();
expect(entityVersionTimeLine).toBeNull();
expect(dataAssetsVersionHeader).toBeNull();
expect(featureTabLabel).toBeNull();
expect(customPropertyTabLabel).toBeNull();
});
it('No data placeholder should be displayed if no mlFeatures are present in the mlModel data', async () => {
await act(async () => {
render(

View File

@ -251,10 +251,6 @@ const PipelineVersion: FC<PipelineVersionProp> = ({
]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -131,37 +131,6 @@ describe('PipelineVersion tests', () => {
expect(schemaTable).toBeNull();
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<PipelineVersion
{...pipelineVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
const loader = screen.queryByText('Loader');
const dataAssetsVersionHeader = screen.queryByText(
'DataAssetsVersionHeader'
);
const schemaTabLabel = screen.queryByText('label.schema');
const customPropertyTabLabel = screen.queryByText(
'label.custom-property-plural'
);
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
const schemaTable = screen.queryByTestId('schema-table');
expect(errorPlaceHolder).toBeInTheDocument();
expect(loader).toBeNull();
expect(entityVersionTimeLine).toBeNull();
expect(dataAssetsVersionHeader).toBeNull();
expect(schemaTabLabel).toBeNull();
expect(customPropertyTabLabel).toBeNull();
expect(schemaTable).toBeNull();
});
it('Should update url on click of tab', async () => {
await act(async () => {
render(<PipelineVersion {...pipelineVersionMockProps} />, {

View File

@ -226,10 +226,6 @@ const TableVersion: React.FC<TableVersionProp> = ({
]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -115,37 +115,6 @@ describe('TableVersion tests', () => {
expect(versionTable).toBeNull();
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<TableVersion
{...tableVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
const loader = screen.queryByText('Loader');
const dataAssetsVersionHeader = screen.queryByText(
'DataAssetsVersionHeader'
);
const schemaTabLabel = screen.queryByText('label.schema');
const customPropertyTabLabel = screen.queryByText(
'label.custom-property-plural'
);
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
const versionTable = screen.queryByText('VersionTable');
expect(errorPlaceHolder).toBeInTheDocument();
expect(loader).toBeNull();
expect(entityVersionTimeLine).toBeNull();
expect(dataAssetsVersionHeader).toBeNull();
expect(schemaTabLabel).toBeNull();
expect(customPropertyTabLabel).toBeNull();
expect(versionTable).toBeNull();
});
it('Should update url on click of tab', async () => {
await act(async () => {
render(<TableVersion {...tableVersionMockProps} />);

View File

@ -205,10 +205,6 @@ const TopicVersion: FC<TopicVersionProp> = ({
]
);
if (!(entityPermissions.ViewAll || entityPermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
return (
<>
{isVersionLoading ? (

View File

@ -116,37 +116,6 @@ describe('TopicVersion tests', () => {
expect(topicSchema).toBeNull();
});
it('Should display ErrorPlaceholder if no viewing permission', async () => {
await act(async () => {
render(
<TopicVersion
{...topicVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
const loader = screen.queryByText('Loader');
const dataAssetsVersionHeader = screen.queryByText(
'DataAssetsVersionHeader'
);
const schemaTabLabel = screen.queryByText('label.schema');
const customPropertyTabLabel = screen.queryByText(
'label.custom-property-plural'
);
const entityVersionTimeLine = screen.queryByText('EntityVersionTimeLine');
const topicSchema = screen.queryByText('TopicSchema');
expect(errorPlaceHolder).toBeInTheDocument();
expect(loader).toBeNull();
expect(entityVersionTimeLine).toBeNull();
expect(dataAssetsVersionHeader).toBeNull();
expect(schemaTabLabel).toBeNull();
expect(customPropertyTabLabel).toBeNull();
expect(topicSchema).toBeNull();
});
it('Should display ErrorPlaceholder in Custom Property tab if no "viewAll" permission', async () => {
await act(async () => {
render(
@ -200,29 +169,4 @@ describe('TopicVersion tests', () => {
'/topic/sample_kafka.sales/versions/0.3/custom_properties'
);
});
it('ErrorPlaceholder should be displayed in case of no view permissions', async () => {
await act(async () => {
render(
<TopicVersion
{...topicVersionMockProps}
entityPermissions={DEFAULT_ENTITY_PERMISSION}
/>
);
});
const topicSchema = screen.queryByText('TopicSchema');
const description = screen.queryByText('Description.component');
const entityVersionTimeLine = screen.queryByText(
'EntityVersionTimeLine.component'
);
const errorPlaceHolder = screen.getByText('ErrorPlaceHolder');
expect(entityVersionTimeLine).toBeNull();
expect(topicSchema).toBeNull();
expect(description).toBeNull();
expect(errorPlaceHolder).toBeInTheDocument();
});
});

View File

@ -339,27 +339,29 @@ const ContainerPage = () => {
const handleUpdateTier = async (updatedTier?: string) => {
try {
if (updatedTier) {
const { tags: newTags, version } = await handleUpdateContainerData({
...(containerData as Container),
tags: [
...getTagsWithoutTier(containerData?.tags ?? []),
{
tagFQN: updatedTier,
labelType: LabelType.Manual,
state: State.Confirmed,
source: TagSource.Classification,
},
],
});
const { tags: newTags, version } = await handleUpdateContainerData({
...(containerData as Container),
tags: [
...getTagsWithoutTier(containerData?.tags ?? []),
...(updatedTier
? [
{
tagFQN: updatedTier,
labelType: LabelType.Manual,
state: State.Confirmed,
source: TagSource.Classification,
},
]
: []),
],
});
setContainerData((prev) => ({
...(prev as Container),
tags: newTags,
version,
}));
getEntityFeedCount();
}
setContainerData((prev) => ({
...(prev as Container),
tags: newTags,
version,
}));
getEntityFeedCount();
} catch (error) {
showErrorToast(error as AxiosError);
}

View File

@ -228,26 +228,28 @@ const DataModelsPage = () => {
const handleUpdateTier = async (updatedTier?: string) => {
try {
if (updatedTier) {
const { tags: newTags, version } = await handleUpdateDataModelData({
...(dataModelData as DashboardDataModel),
tags: [
...getTagsWithoutTier(dataModelData?.tags ?? []),
{
tagFQN: updatedTier,
labelType: LabelType.Manual,
state: State.Confirmed,
source: TagSource.Classification,
},
],
});
const { tags: newTags, version } = await handleUpdateDataModelData({
...(dataModelData as DashboardDataModel),
tags: [
...getTagsWithoutTier(dataModelData?.tags ?? []),
...(updatedTier
? [
{
tagFQN: updatedTier,
labelType: LabelType.Manual,
state: State.Confirmed,
source: TagSource.Classification,
},
]
: []),
],
});
setDataModelData((prev) => ({
...(prev as DashboardDataModel),
tags: newTags,
version,
}));
}
setDataModelData((prev) => ({
...(prev as DashboardDataModel),
tags: newTags,
version,
}));
} catch (error) {
showErrorToast(error as AxiosError);
}

View File

@ -27,6 +27,7 @@ import React, {
FunctionComponent,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
@ -88,12 +89,14 @@ import { EntityHistory } from '../../generated/type/entityHistory';
import { TagLabel } from '../../generated/type/tagLabel';
import { getPartialNameFromFQN } from '../../utils/CommonUtils';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
import {
OperationPermission,
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { isEmpty } from 'lodash';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { getTierTags } from '../../utils/TableUtils';
@ -127,11 +130,11 @@ const EntityVersionPage: FunctionComponent = () => {
const { getEntityPermissionByFqn } = usePermissionProvider();
const [entityPermissions, setEntityPermissions] =
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [versionList, setVersionList] = useState<EntityHistory>(
{} as EntityHistory
);
const [isVersionLoading, setIsVersionLoading] = useState<boolean>(false);
const [isVersionLoading, setIsVersionLoading] = useState<boolean>(true);
const [slashedEntityName, setSlashedEntityName] = useState<
TitleBreadcrumbProps['titleLinks']
>([]);
@ -207,11 +210,11 @@ const EntityVersionPage: FunctionComponent = () => {
const fetchResourcePermission = useCallback(
async (resourceEntity: ResourceEntity) => {
if (!isEmpty(currentVersionData)) {
if (!isEmpty(entityFQN)) {
try {
const permission = await getEntityPermissionByFqn(
resourceEntity,
currentVersionData.id ?? ''
entityFQN
);
setEntityPermissions(permission);
@ -220,7 +223,7 @@ const EntityVersionPage: FunctionComponent = () => {
}
}
},
[currentVersionData.id, getEntityPermissionByFqn, setEntityPermissions]
[entityFQN, getEntityPermissionByFqn, setEntityPermissions]
);
const fetchEntityPermissions = useCallback(async () => {
@ -228,37 +231,37 @@ const EntityVersionPage: FunctionComponent = () => {
try {
switch (entityType) {
case EntityType.TABLE: {
fetchResourcePermission(ResourceEntity.TABLE);
await fetchResourcePermission(ResourceEntity.TABLE);
break;
}
case EntityType.TOPIC: {
fetchResourcePermission(ResourceEntity.TOPIC);
await fetchResourcePermission(ResourceEntity.TOPIC);
break;
}
case EntityType.DASHBOARD: {
fetchResourcePermission(ResourceEntity.DASHBOARD);
await fetchResourcePermission(ResourceEntity.DASHBOARD);
break;
}
case EntityType.PIPELINE: {
fetchResourcePermission(ResourceEntity.PIPELINE);
await fetchResourcePermission(ResourceEntity.PIPELINE);
break;
}
case EntityType.MLMODEL: {
fetchResourcePermission(ResourceEntity.ML_MODEL);
await fetchResourcePermission(ResourceEntity.ML_MODEL);
break;
}
case EntityType.CONTAINER: {
fetchResourcePermission(ResourceEntity.CONTAINER);
await fetchResourcePermission(ResourceEntity.CONTAINER);
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
fetchResourcePermission(ResourceEntity.DASHBOARD_DATA_MODEL);
await fetchResourcePermission(ResourceEntity.DASHBOARD_DATA_MODEL);
break;
}
@ -266,241 +269,129 @@ const EntityVersionPage: FunctionComponent = () => {
break;
}
}
} finally {
setIsLoading(false);
} catch {
// Error
}
}, [entityType, fetchResourcePermission]);
const viewVersionPermission = useMemo(
() => entityPermissions.ViewAll || entityPermissions.ViewBasic,
[entityPermissions]
);
const fetchEntityVersions = useCallback(async () => {
setIsLoading(true);
try {
switch (entityType) {
case EntityType.TABLE: {
const { id } = await getTableDetailsByFQN(entityFQN, '');
setEntityId(id);
const versions = await getTableVersions(id);
setVersionList(versions);
break;
}
case EntityType.TOPIC: {
const { id } = await getTopicByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getTopicVersions(id);
setVersionList(versions);
break;
}
case EntityType.DASHBOARD: {
const { id } = await getDashboardByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getDashboardVersions(id);
setVersionList(versions);
break;
}
case EntityType.PIPELINE: {
const { id } = await getPipelineByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getPipelineVersions(id);
setVersionList(versions);
break;
}
case EntityType.MLMODEL: {
const { id } = await getMlModelByFQN(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getMlModelVersions(id);
setVersionList(versions);
break;
}
case EntityType.CONTAINER: {
const { id } = await getContainerByName(entityFQN, '');
setEntityId(id);
const versions = await getContainerVersions(id);
setVersionList(versions);
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
const { id } = await getDataModelDetailsByFQN(entityFQN, '');
setEntityId(id ?? '');
const versions = await getDataModelVersionsList(id ?? '');
setVersionList(versions);
break;
}
default:
break;
}
} catch (err) {
// Error
} finally {
setIsLoading(false);
}
}, [entityType, entityFQN]);
const fetchCurrentVersion = useCallback(
async (id: string) => {
setIsVersionLoading(true);
try {
if (viewVersionPermission) {
switch (entityType) {
case EntityType.TABLE: {
const currentVersion = await getTableVersion(id, version);
const { id } = await getTableDetailsByFQN(entityFQN, '');
const { owner, tags = [] } = currentVersion;
setEntityId(id);
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.TABLE)
);
const versions = await getTableVersions(id);
setVersionList(versions);
break;
}
case EntityType.TOPIC: {
const currentVersion = await getTopicVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.TOPIC)
const { id } = await getTopicByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getTopicVersions(id);
setVersionList(versions);
break;
}
case EntityType.DASHBOARD: {
const currentVersion = await getDashboardVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.DASHBOARD)
const { id } = await getDashboardByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getDashboardVersions(id);
setVersionList(versions);
break;
}
case EntityType.PIPELINE: {
const currentVersion = await getPipelineVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.PIPELINE)
const { id } = await getPipelineByFqn(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getPipelineVersions(id);
setVersionList(versions);
break;
}
case EntityType.MLMODEL: {
const currentVersion = await getMlModelVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.MLMODEL)
const { id } = await getMlModelByFQN(
getPartialNameFromFQN(
entityFQN,
['service', 'database'],
FQN_SEPARATOR_CHAR
),
''
);
setEntityId(id);
const versions = await getMlModelVersions(id);
setVersionList(versions);
break;
}
case EntityType.CONTAINER: {
const currentVersion = await getContainerVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.CONTAINER)
);
case EntityType.CONTAINER: {
const { id } = await getContainerByName(entityFQN, '');
setEntityId(id);
const versions = await getContainerVersions(id);
setVersionList(versions);
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
const currentVersion = await getDataModelVersion(id, version);
const { id } = await getDataModelDetailsByFQN(entityFQN, '');
const { owner, tags = [] } = currentVersion;
setEntityId(id ?? '');
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(
currentVersion,
EntityType.DASHBOARD_DATA_MODEL
)
);
const versions = await getDataModelVersionsList(id ?? '');
setVersionList(versions);
break;
}
@ -508,14 +399,143 @@ const EntityVersionPage: FunctionComponent = () => {
default:
break;
}
}
} catch (err) {
// Error
} finally {
setIsLoading(false);
}
}, [entityType, entityFQN, viewVersionPermission]);
const fetchCurrentVersion = useCallback(
async (id: string) => {
setIsVersionLoading(true);
try {
if (viewVersionPermission) {
switch (entityType) {
case EntityType.TABLE: {
const currentVersion = await getTableVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.TABLE)
);
break;
}
case EntityType.TOPIC: {
const currentVersion = await getTopicVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.TOPIC)
);
break;
}
case EntityType.DASHBOARD: {
const currentVersion = await getDashboardVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.DASHBOARD)
);
break;
}
case EntityType.PIPELINE: {
const currentVersion = await getPipelineVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.PIPELINE)
);
break;
}
case EntityType.MLMODEL: {
const currentVersion = await getMlModelVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.MLMODEL)
);
break;
}
case EntityType.CONTAINER: {
const currentVersion = await getContainerVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(currentVersion, EntityType.CONTAINER)
);
break;
}
case EntityType.DASHBOARD_DATA_MODEL: {
const currentVersion = await getDataModelVersion(id, version);
const { owner, tags = [] } = currentVersion;
setEntityState(
tags,
owner,
currentVersion,
getEntityBreadcrumbs(
currentVersion,
EntityType.DASHBOARD_DATA_MODEL
)
);
break;
}
default:
break;
}
}
} finally {
setIsVersionLoading(false);
}
},
[entityType, version, setEntityState]
[entityType, version, setEntityState, viewVersionPermission]
);
const versionComponent = () => {
if (isLoading) {
return <Loader />;
}
if (!viewVersionPermission) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
}
switch (entityType) {
case EntityType.TABLE: {
return (
@ -635,7 +655,6 @@ const EntityVersionPage: FunctionComponent = () => {
currentVersionData={currentVersionData}
dataModelFQN={entityFQN}
deleted={currentVersionData.deleted}
entityPermissions={entityPermissions}
isVersionLoading={isVersionLoading}
owner={owner}
slashedDataModelName={slashedEntityName}
@ -654,14 +673,12 @@ const EntityVersionPage: FunctionComponent = () => {
};
useEffect(() => {
if (currentVersionData) {
fetchEntityPermissions();
}
}, [currentVersionData]);
fetchEntityPermissions();
}, [entityFQN]);
useEffect(() => {
fetchEntityVersions();
}, [entityFQN]);
}, [entityFQN, viewVersionPermission]);
useEffect(() => {
if (entityId) {
@ -675,7 +692,7 @@ const EntityVersionPage: FunctionComponent = () => {
pageTitle={t('label.entity-detail-plural', {
entity: getEntityName(currentVersionData),
})}>
{isLoading ? <Loader /> : versionComponent()}
{versionComponent()}
</PageLayoutV1>
);
};

View File

@ -11,7 +11,8 @@
* limitations under the License.
*/
import { act, findByText, render, screen } from '@testing-library/react';
import { act, render, screen } from '@testing-library/react';
import { ENTITY_PERMISSIONS } from 'mocks/Permissions.mock';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import EntityVersionPage from './EntityVersionPage.component';
@ -97,16 +98,25 @@ jest.mock('rest/dataModelsAPI', () => ({
.mockImplementation(() => Promise.resolve({})),
}));
jest.mock('components/PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockImplementation(() => ({
getEntityPermissionByFqn: jest
.fn()
.mockImplementation(() => ENTITY_PERMISSIONS),
})),
}));
describe('Test EntityVersionPage component', () => {
it('Checks if the TableVersion component renderst if respective data pass', async () => {
await act(async () => {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const tableVersion = await screen.findByText(/TableVersion component/i);
expect(tableVersion).toBeInTheDocument();
});
const tableVersion = await screen.findByText(/TableVersion component/i);
expect(tableVersion).toBeInTheDocument();
});
it('Checks if the DashboardVersion component render if respective data pass', async () => {
@ -117,17 +127,16 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const DashboardVersion = await findByText(
container,
/DashboardVersion component/i
);
expect(DashboardVersion).toBeInTheDocument();
});
const DashboardVersion = await screen.findByText(
/DashboardVersion component/i
);
expect(DashboardVersion).toBeInTheDocument();
});
it('Checks if the PipelineVersion component render if respective data pass', async () => {
@ -138,17 +147,16 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const PipelineVersion = await findByText(
container,
/PipelineVersion component/i
);
expect(PipelineVersion).toBeInTheDocument();
});
const PipelineVersion = await screen.findByText(
/PipelineVersion component/i
);
expect(PipelineVersion).toBeInTheDocument();
});
it('Checks if the TopicVersion component render if respective data pass', async () => {
@ -159,17 +167,14 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const TopicVersion = await findByText(
container,
/TopicVersion component/i
);
expect(TopicVersion).toBeInTheDocument();
});
const TopicVersion = await screen.findByText(/TopicVersion component/i);
expect(TopicVersion).toBeInTheDocument();
});
it('Should render the mlModel Version Component', async () => {
@ -180,17 +185,14 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const MlModelVersion = await findByText(
container,
/MlModelVersion component/i
);
expect(MlModelVersion).toBeInTheDocument();
});
const MlModelVersion = await screen.findByText(/MlModelVersion component/i);
expect(MlModelVersion).toBeInTheDocument();
});
it('Should render the container Version Component', async () => {
@ -201,17 +203,16 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const ContainerVersion = await findByText(
container,
/ContainerVersion component/i
);
expect(ContainerVersion).toBeInTheDocument();
});
const ContainerVersion = await screen.findByText(
/ContainerVersion component/i
);
expect(ContainerVersion).toBeInTheDocument();
});
it('Should render the DataModel Version Component', async () => {
@ -222,16 +223,15 @@ describe('Test EntityVersionPage component', () => {
};
await act(async () => {
const { container } = render(<EntityVersionPage />, {
render(<EntityVersionPage />, {
wrapper: MemoryRouter,
});
const ContainerVersion = await findByText(
container,
/DataModelVersion component/i
);
expect(ContainerVersion).toBeInTheDocument();
});
const ContainerVersion = await screen.findByText(
/DataModelVersion component/i
);
expect(ContainerVersion).toBeInTheDocument();
});
});

View File

@ -19,7 +19,7 @@ import { usePermissionProvider } from 'components/PermissionProvider/PermissionP
import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface';
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
import { compare } from 'fast-json-patch';
import { isEmpty, isNil, isUndefined, omitBy } from 'lodash';
import { isEmpty, isNil, isUndefined, omitBy, toString } from 'lodash';
import { observer } from 'mobx-react';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
@ -58,8 +58,6 @@ const MlModelPage = () => {
DEFAULT_ENTITY_PERMISSION
);
const [currentVersion, setCurrentVersion] = useState<string>();
const { getEntityPermissionByFqn } = usePermissionProvider();
const fetchResourcePermission = async (entityFqn: string) => {
@ -103,7 +101,6 @@ const MlModelPage = () => {
timestamp: 0,
id: res.id,
});
setCurrentVersion(res.version?.toString());
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
@ -127,10 +124,10 @@ const MlModelPage = () => {
try {
const response = await saveUpdatedMlModelData(updatedMlModel);
const { description, version } = response;
setCurrentVersion(version?.toString());
setMlModelDetail((preVDetail) => ({
...preVDetail,
description: description,
description,
version,
}));
} catch (error) {
showErrorToast(error as AxiosError);
@ -177,12 +174,12 @@ const MlModelPage = () => {
const onTagUpdate = async (updatedMlModel: Mlmodel) => {
try {
const res = await saveUpdatedMlModelData(updatedMlModel);
const { tags, version } = await saveUpdatedMlModelData(updatedMlModel);
setMlModelDetail((preVDetail) => ({
...preVDetail,
tags: sortTagsCaseInsensitive(res.tags ?? []),
tags: sortTagsCaseInsensitive(tags ?? []),
version: version,
}));
setCurrentVersion(res.version?.toString());
} catch (error) {
showErrorToast(
error as AxiosError,
@ -197,14 +194,15 @@ const MlModelPage = () => {
updatedMlModel: Mlmodel
): Promise<void> => {
try {
const res = await saveUpdatedMlModelData(updatedMlModel);
const { displayName, owner, tags, version } =
await saveUpdatedMlModelData(updatedMlModel);
setMlModelDetail((preVDetail) => ({
...preVDetail,
displayName: res.displayName,
owner: res.owner,
tags: res.tags,
displayName,
owner,
tags,
version,
}));
setCurrentVersion(res.version?.toString());
} catch (error) {
showErrorToast(
error as AxiosError,
@ -217,12 +215,14 @@ const MlModelPage = () => {
const updateMlModelFeatures = async (updatedMlModel: Mlmodel) => {
try {
const response = await saveUpdatedMlModelData(updatedMlModel);
const { mlFeatures, version } = await saveUpdatedMlModelData(
updatedMlModel
);
setMlModelDetail((preVDetail) => ({
...preVDetail,
mlFeatures: response.mlFeatures,
mlFeatures,
version,
}));
setCurrentVersion(response.version?.toString());
} catch (error) {
showErrorToast(error as AxiosError);
}
@ -232,7 +232,6 @@ const MlModelPage = () => {
try {
const data = await saveUpdatedMlModelData(updatedMlModel);
setMlModelDetail(data);
setCurrentVersion(data.version?.toString());
} catch (error) {
showErrorToast(
error as AxiosError,
@ -258,7 +257,11 @@ const MlModelPage = () => {
const versionHandler = () => {
history.push(
getVersionPath(EntityType.MLMODEL, mlModelFqn, currentVersion as string)
getVersionPath(
EntityType.MLMODEL,
mlModelFqn,
toString(mlModelDetail.version)
)
);
};
@ -293,7 +296,6 @@ const MlModelPage = () => {
tagUpdateHandler={onTagUpdate}
unFollowMlModelHandler={unFollowMlModel}
updateMlModelFeatures={updateMlModelFeatures}
version={currentVersion}
versionHandler={versionHandler}
onExtensionUpdate={handleExtensionUpdate}
/>

View File

@ -55,7 +55,7 @@ export const getContainers = async (args: {
export const getContainerByName = async (
name: string,
fields: string | string[],
include: Include = Include.NonDeleted
include: Include = Include.All
) => {
const response = await APIClient.get<Container>(
`containers/name/${name}?fields=${fields}`,

View File

@ -24,6 +24,7 @@ import {
diffWords,
diffWordsWithSpace,
} from 'diff';
import { Column as DataModelColumn } from 'generated/entity/data/dashboardDataModel';
import { Glossary } from 'generated/entity/data/glossary';
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
import { Field } from 'generated/entity/data/topic';
@ -652,7 +653,7 @@ export const getAllChangedEntityNames = (
};
export function getColumnsDataWithVersionChanges<
A extends TableColumn | ContainerColumn
A extends TableColumn | ContainerColumn | DataModelColumn
>(
changeDescription: ChangeDescription,
colList?: A[],