mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-29 17:49:14 +00:00
558 lines
15 KiB
TypeScript
558 lines
15 KiB
TypeScript
/*
|
|
* 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 { DELETE_TERM } from '../../constants/constants';
|
|
import {
|
|
EntityType,
|
|
EXPLORE_PAGE_TABS,
|
|
SEARCH_INDEX,
|
|
} from '../../constants/Entity.interface';
|
|
import {
|
|
ENTITIES_WITHOUT_FOLLOWING_BUTTON,
|
|
LIST_OF_FIELDS_TO_EDIT_NOT_TO_BE_PRESENT,
|
|
LIST_OF_FIELDS_TO_EDIT_TO_BE_DISABLED,
|
|
} from '../../constants/SoftDeleteFlow.constants';
|
|
import {
|
|
interceptURL,
|
|
toastNotification,
|
|
verifyResponseStatusCode,
|
|
} from '../common';
|
|
|
|
/**
|
|
* create full hierarchy of database service (service > database > schema > tables)
|
|
*/
|
|
export const createEntityTableViaREST = ({
|
|
service,
|
|
database,
|
|
schema,
|
|
tables,
|
|
storeProcedure,
|
|
token,
|
|
}: {
|
|
service: Cypress.RequestBody;
|
|
database: Cypress.RequestBody;
|
|
schema?: Cypress.RequestBody;
|
|
tables: Cypress.RequestBody[];
|
|
storeProcedure?: Cypress.RequestBody;
|
|
token: Cypress.Storable;
|
|
}) => {
|
|
const createdEntityIds = {
|
|
databaseId: undefined,
|
|
databaseSchemaId: undefined,
|
|
};
|
|
|
|
// Create service
|
|
cy.request({
|
|
method: 'POST',
|
|
url: `/api/v1/services/databaseServices`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: service,
|
|
}).then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
});
|
|
|
|
// Create Database
|
|
cy.request({
|
|
method: 'POST',
|
|
url: `/api/v1/databases`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: database,
|
|
}).then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
|
|
createdEntityIds.databaseId = response.body.id;
|
|
});
|
|
|
|
// Create Database Schema
|
|
schema &&
|
|
cy
|
|
.request({
|
|
method: 'POST',
|
|
url: `/api/v1/databaseSchemas`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: schema,
|
|
})
|
|
.then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
|
|
createdEntityIds.databaseSchemaId = response.body.id;
|
|
});
|
|
|
|
storeProcedure &&
|
|
cy
|
|
.request({
|
|
method: 'POST',
|
|
url: `/api/v1/storedProcedures`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: storeProcedure,
|
|
})
|
|
.then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
});
|
|
|
|
tables.forEach((body) => {
|
|
cy.request({
|
|
method: 'POST',
|
|
url: `/api/v1/tables`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body,
|
|
}).then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
});
|
|
});
|
|
|
|
return createdEntityIds;
|
|
};
|
|
|
|
export const createEntityViaREST = ({
|
|
body,
|
|
endPoint,
|
|
token,
|
|
}: {
|
|
body: Cypress.RequestBody;
|
|
endPoint: EntityType;
|
|
token: Cypress.Storable;
|
|
}) => {
|
|
// Create entity
|
|
cy.request({
|
|
method: 'POST',
|
|
url: `/api/v1/${endPoint}`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
body: body,
|
|
}).then((response) => {
|
|
expect(response.status).to.eq(201);
|
|
});
|
|
};
|
|
|
|
export const deleteEntityViaREST = ({
|
|
entityName,
|
|
endPoint,
|
|
token,
|
|
}: {
|
|
entityName: string;
|
|
endPoint: EntityType;
|
|
token: Cypress.Storable;
|
|
}) => {
|
|
// Create entity
|
|
cy.request({
|
|
method: 'DELETE',
|
|
url: `/api/v1/${endPoint}/name/${entityName}?recursive=true&hardDelete=true`,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
}).then((response) => {
|
|
expect(response.status).to.eq(200);
|
|
});
|
|
};
|
|
|
|
export const visitEntityDetailsPage = ({
|
|
term,
|
|
serviceName,
|
|
entity,
|
|
dataTestId,
|
|
entityType,
|
|
entityFqn,
|
|
}: {
|
|
term: string;
|
|
serviceName: string;
|
|
entity: EntityType;
|
|
dataTestId?: string;
|
|
entityType?: EntityType;
|
|
entityFqn?: string;
|
|
}) => {
|
|
if (entity === EntityType.DataModel) {
|
|
interceptURL(
|
|
'GET',
|
|
'/api/v1/dashboard/datamodels/name/*',
|
|
'getEntityDetails'
|
|
);
|
|
} else {
|
|
interceptURL('GET', '/api/v1/*/name/*', 'getEntityDetails');
|
|
}
|
|
|
|
interceptURL(
|
|
'GET',
|
|
`/api/v1/search/query?q=**&index=${SEARCH_INDEX[entity]}&from=*&size=**`,
|
|
'explorePageTabSearch'
|
|
);
|
|
interceptURL(
|
|
'GET',
|
|
`/api/v1/search/query?q=**&from=*&size=*&index=all`,
|
|
'explorePageSearch'
|
|
);
|
|
const id = dataTestId ?? `${serviceName}-${term}`;
|
|
|
|
if (entityType) {
|
|
cy.get('[data-testid="global-search-selector"]').click();
|
|
cy.get(`[data-testid="global-search-select-option-${entityType}"]`).click();
|
|
}
|
|
|
|
// searching term in search box
|
|
cy.get('[data-testid="searchBox"]').scrollIntoView().should('be.visible');
|
|
cy.get('[data-testid="searchBox"]').type(entityFqn ?? term);
|
|
|
|
verifyResponseStatusCode('@explorePageSearch', 200).then(() => {
|
|
cy.wait(500);
|
|
|
|
cy.get('body').then(($body) => {
|
|
// checking if requested term is available in search suggestion
|
|
if (
|
|
$body.find(`[data-testid="${id}"] [data-testid="data-name"]`).length
|
|
) {
|
|
// if term is available in search suggestion, redirecting to entity details page
|
|
cy.get(`[data-testid="${id}"] [data-testid="data-name"]`)
|
|
.first()
|
|
.click();
|
|
} else {
|
|
// if term is not available in search suggestion,
|
|
// hitting enter to search box so it will redirect to explore page
|
|
cy.clickOutside();
|
|
cy.get('[data-testid="searchBox"]').type('{enter}');
|
|
verifyResponseStatusCode('@explorePageSearch', 200);
|
|
|
|
const tabName = EXPLORE_PAGE_TABS?.[entity] ?? entity;
|
|
|
|
cy.get(`[data-testid="${tabName}-tab"]`).click();
|
|
|
|
verifyResponseStatusCode('@explorePageTabSearch', 200);
|
|
|
|
cy.get(`[data-testid="${id}"] [data-testid="entity-link"]`)
|
|
.scrollIntoView()
|
|
.click();
|
|
}
|
|
});
|
|
|
|
verifyResponseStatusCode('@getEntityDetails', 200);
|
|
cy.clickOutside();
|
|
cy.get('[data-testid="searchBox"]').clear();
|
|
});
|
|
};
|
|
|
|
export const checkCustomPropertyEditButton = ({ deleted }) => {
|
|
interceptURL(
|
|
'GET',
|
|
`/api/v1/metadata/types/name/*fields=customProperties*`,
|
|
'getCustomProperties'
|
|
);
|
|
|
|
cy.get('[data-testid="custom_properties"]').click();
|
|
|
|
verifyResponseStatusCode('@getCustomProperties', 200);
|
|
|
|
if (!deleted) {
|
|
cy.get(
|
|
`[data-row-key="${'CUSTOM_ATTRIBUTE_NAME'}"] [data-testid="edit-icon"]`
|
|
)
|
|
.scrollIntoView()
|
|
.should(`be.visible`);
|
|
} else {
|
|
cy.get(
|
|
`[data-row-key="${'CUSTOM_ATTRIBUTE_NAME'}"] [data-testid="edit-icon"]`
|
|
).should(`not.exist`);
|
|
}
|
|
};
|
|
|
|
export const checkLineageTabActions = ({ deleted }) => {
|
|
interceptURL(
|
|
'GET',
|
|
`/api/v1/lineage/getLineage?fqn=*&upstreamDepth=3&downstreamDepth=3&query_filter=*&includeDeleted=false`,
|
|
'getLineageData'
|
|
);
|
|
|
|
cy.get('[data-testid="lineage"]').click();
|
|
|
|
verifyResponseStatusCode('@getLineageData', 200);
|
|
|
|
if (!deleted) {
|
|
cy.get('[data-testid="edit-lineage"]').should('be.visible');
|
|
} else {
|
|
cy.get('[data-testid="edit-lineage"]').should(`not.exist`);
|
|
}
|
|
};
|
|
|
|
export const checkForEditActions = ({ entityType, deleted }) => {
|
|
LIST_OF_FIELDS_TO_EDIT_TO_BE_DISABLED.filter(({ elementSelector }) => {
|
|
if (elementSelector === '[data-testid="entity-follow-button"]') {
|
|
return !ENTITIES_WITHOUT_FOLLOWING_BUTTON.includes(entityType);
|
|
}
|
|
|
|
return !entityType.startsWith('services/');
|
|
}).map(({ containerSelector, elementSelector }) => {
|
|
cy.get(`${containerSelector} ${elementSelector}`).should(
|
|
`${!deleted ? 'not.' : ''}be.disabled`
|
|
);
|
|
});
|
|
|
|
LIST_OF_FIELDS_TO_EDIT_NOT_TO_BE_PRESENT.map(
|
|
({ containerSelector, elementSelector }) => {
|
|
if (!deleted) {
|
|
cy.get(`${containerSelector} ${elementSelector}`)
|
|
.scrollIntoView()
|
|
.should('be.visible');
|
|
} else {
|
|
cy.get(`${containerSelector} ${elementSelector}`).should('not.exist');
|
|
}
|
|
}
|
|
);
|
|
|
|
// checkCustomPropertyEditButton({ deleted });
|
|
};
|
|
|
|
export const checkForTableSpecificFields = ({ deleted }) => {
|
|
interceptURL('GET', `/api/v1/queries*`, 'getQueryData');
|
|
|
|
cy.get('[data-testid="table_queries"]').click();
|
|
|
|
verifyResponseStatusCode('@getQueryData', 200);
|
|
|
|
if (!deleted) {
|
|
cy.get('[data-testid="add-query-btn"]').should('be.enabled');
|
|
} else {
|
|
cy.get('[data-testid="no-data-placeholder"]').should(
|
|
'contain',
|
|
'Queries data is not available for deleted entities.'
|
|
);
|
|
}
|
|
|
|
interceptURL('GET', `/api/v1/tables/*/systemProfile*`, 'getSystemProfile');
|
|
|
|
cy.get('[data-testid="profiler"]').click();
|
|
|
|
verifyResponseStatusCode('@getSystemProfile', 200);
|
|
|
|
cy.get('[data-testid="profiler-add-table-test-btn"]').should(
|
|
!deleted ? 'be.visible' : 'not.exist'
|
|
);
|
|
cy.get('[data-testid="profiler-setting-btn"]').should(
|
|
!deleted ? 'be.visible' : 'not.exist'
|
|
);
|
|
};
|
|
|
|
export const deletedEntityCommonChecks = ({
|
|
entityType,
|
|
deleted,
|
|
}: {
|
|
entityType: EntityType;
|
|
deleted?: boolean;
|
|
}) => {
|
|
const isTableEntity = entityType === EntityType.Table;
|
|
|
|
// Go to first tab before starts validating
|
|
cy.get('.ant-tabs-tab').first().click();
|
|
|
|
// Check if all the edit actions are available for the entity
|
|
checkForEditActions({
|
|
entityType,
|
|
deleted,
|
|
});
|
|
|
|
if (isTableEntity) {
|
|
checkLineageTabActions({ deleted });
|
|
}
|
|
|
|
if (isTableEntity) {
|
|
checkForTableSpecificFields({ deleted });
|
|
}
|
|
|
|
cy.get('[data-testid="manage-button"]').click();
|
|
|
|
if (deleted) {
|
|
// only two menu options (restore and delete) should be present
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="announcement-button"]'
|
|
).should('not.exist');
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="rename-button"]'
|
|
).should('not.exist');
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="profiler-setting-button"]'
|
|
).should('not.exist');
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="restore-button"]'
|
|
).should('be.visible');
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="delete-button"]'
|
|
).should('be.visible');
|
|
} else {
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="announcement-button"]'
|
|
).should('exist');
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="rename-button"]'
|
|
).should('exist');
|
|
[EntityType.Database, EntityType.DatabaseSchema].includes(entityType) &&
|
|
cy
|
|
.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="profiler-setting-button"]'
|
|
)
|
|
.should('exist');
|
|
|
|
cy.get(
|
|
'[data-testid="manage-dropdown-list-container"] [data-testid="delete-button"]'
|
|
).should('be.visible');
|
|
}
|
|
cy.clickOutside();
|
|
};
|
|
|
|
export const restoreEntity = () => {
|
|
cy.get('[data-testid="deleted-badge"]').should('be.visible');
|
|
cy.get('[data-testid="manage-button"]').click();
|
|
cy.get('[data-testid="restore-button"]').click();
|
|
|
|
cy.get('[type="button"]').contains('Restore').click();
|
|
toastNotification('restored successfully');
|
|
|
|
cy.get('[data-testid="deleted-badge"]').should('not.exist');
|
|
};
|
|
|
|
export const deleteEntity = (entityName: string, endPoint: EntityType) => {
|
|
deletedEntityCommonChecks({ entityType: endPoint, deleted: false });
|
|
|
|
cy.get('[data-testid="manage-button"]').click();
|
|
cy.get('[data-testid="delete-button"]').scrollIntoView().click();
|
|
cy.get('[data-testid="delete-modal"]').then(() => {
|
|
cy.get('[role="dialog"]').should('be.visible');
|
|
});
|
|
|
|
cy.get('[data-testid="delete-modal"] .ant-modal-title').should(
|
|
'contain',
|
|
`Delete ${entityName}`
|
|
);
|
|
|
|
cy.get('[data-testid="confirmation-text-input"]').type(DELETE_TERM);
|
|
|
|
interceptURL(
|
|
'DELETE',
|
|
`/api/v1/${endPoint}/*?hardDelete=false&recursive=true`,
|
|
'deleteEntity'
|
|
);
|
|
|
|
cy.get('[data-testid="confirm-button"]').click();
|
|
|
|
verifyResponseStatusCode('@deleteEntity', 200);
|
|
|
|
toastNotification('deleted successfully!');
|
|
|
|
cy.reload();
|
|
cy.get('[data-testid="deleted-badge"]').should('have.text', 'Deleted');
|
|
|
|
deletedEntityCommonChecks({ entityType: endPoint, deleted: true });
|
|
|
|
if (endPoint === EntityType.Table) {
|
|
interceptURL(
|
|
'GET',
|
|
'/api/v1/tables?databaseSchema=*&include=deleted',
|
|
'queryDeletedTables'
|
|
);
|
|
interceptURL(
|
|
'GET',
|
|
'/api/v1/databaseSchemas/name/*?fields=*&include=all',
|
|
'getDatabaseSchemas'
|
|
);
|
|
|
|
cy.get('[data-testid="breadcrumb-link"]').last().click();
|
|
verifyResponseStatusCode('@getDatabaseSchemas', 200);
|
|
|
|
cy.get('[data-testid="show-deleted"]')
|
|
.scrollIntoView()
|
|
.click({ waitForAnimations: true });
|
|
|
|
verifyResponseStatusCode('@queryDeletedTables', 200);
|
|
|
|
cy.get('[data-testid="table"] [data-testid="count"]').should(
|
|
'contain',
|
|
'1'
|
|
);
|
|
|
|
cy.get('.ant-table-row > :nth-child(1)').should('contain', entityName);
|
|
cy.get('.ant-table-row > :nth-child(1)').contains(entityName).click();
|
|
}
|
|
|
|
restoreEntity();
|
|
cy.reload();
|
|
|
|
deletedEntityCommonChecks({ entityType: endPoint, deleted: false });
|
|
};
|
|
|
|
export const hardDeleteEntity = (entityName: string, endPoint: EntityType) => {
|
|
cy.get('[data-testid="manage-button"]').click();
|
|
cy.get('[data-testid="delete-button"]').scrollIntoView().click();
|
|
cy.get('[data-testid="delete-modal"]').then(() => {
|
|
cy.get('[role="dialog"]').should('be.visible');
|
|
});
|
|
|
|
cy.get('[data-testid="delete-modal"] .ant-modal-title').should(
|
|
'contain',
|
|
`Delete ${entityName}`
|
|
);
|
|
|
|
cy.get('[data-testid="hard-delete-option"]').click();
|
|
cy.get('[data-testid="hard-delete"]').check();
|
|
|
|
cy.get('[data-testid="confirmation-text-input"]').type(DELETE_TERM);
|
|
|
|
interceptURL(
|
|
'DELETE',
|
|
`/api/v1/${endPoint}/*?hardDelete=true&recursive=true`,
|
|
'deleteEntity'
|
|
);
|
|
|
|
cy.get('[data-testid="confirm-button"]').click();
|
|
|
|
verifyResponseStatusCode('@deleteEntity', 200);
|
|
|
|
toastNotification('deleted successfully!');
|
|
};
|
|
|
|
export const updateDisplayNameForEntity = (
|
|
displayName: string,
|
|
endPoint: EntityType
|
|
) => {
|
|
interceptURL('PATCH', `/api/v1/${endPoint}/*`, 'patchDisplayName');
|
|
|
|
cy.get('[data-testid="manage-button"]').click();
|
|
|
|
cy.get('[data-testid="rename-button"]').click();
|
|
|
|
cy.get('#name').should('be.visible').should('be.disabled');
|
|
cy.get('#displayName').should('be.visible').should('not.be.disabled').clear();
|
|
cy.get('.ant-modal-footer').should('contain', 'Cancel');
|
|
cy.get('#displayName').type(displayName);
|
|
cy.get('[data-testid="save-button"]').click();
|
|
|
|
verifyResponseStatusCode('@patchDisplayName', 200);
|
|
|
|
cy.get('[data-testid="entity-header-display-name"]').should(
|
|
'contain',
|
|
displayName
|
|
);
|
|
};
|
|
|
|
export const updateDescriptioForEntity = (
|
|
description: string,
|
|
endPoint: EntityType
|
|
) => {
|
|
interceptURL('PATCH', `/api/v1/${endPoint}/*`, 'updateEntity');
|
|
cy.get('[data-testid="edit-description"]').click();
|
|
|
|
cy.get('.toastui-editor-md-container > .toastui-editor > .ProseMirror')
|
|
.click()
|
|
.clear()
|
|
.type(description);
|
|
|
|
cy.get('[data-testid="save"]').click();
|
|
|
|
verifyResponseStatusCode('@updateEntity', 200);
|
|
|
|
cy.get(
|
|
'[data-testid="asset-description-container"] [data-testid="viewer-container"]'
|
|
).should('contain', description);
|
|
};
|