diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/TaskUtils.js b/openmetadata-ui/src/main/resources/ui/cypress/common/TaskUtils.js new file mode 100644 index 00000000000..1a2b747681f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/TaskUtils.js @@ -0,0 +1,108 @@ +/* + * 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 { + descriptionBox, + interceptURL, + toastNotification, + verifyResponseStatusCode, +} from './common'; + +const assignee = 'admin'; +const secondAssignee = 'aaron_johnson0'; + +export const verifyTaskDetails = (regexPattern) => { + cy.get('#task-panel').should('be.visible'); + cy.get('[data-testid="task-title"]') + .invoke('text') + .then((textContent) => { + const matches = textContent.match(regexPattern); + + expect(matches).to.not.be.null; + }); + + cy.get('[data-testid="owner-link"]').contains(assignee); + + cy.get(`[data-testid="assignee-${assignee}"]`).should('be.visible'); +}; + +export const editAssignee = () => { + interceptURL('PATCH', 'api/v1/feed/*', 'editAssignee'); + + cy.get('[data-testid="edit-assignees"]').click(); + + cy.get('[data-testid="select-assignee"] > .ant-select-selector').type( + secondAssignee + ); + // select value from dropdown + verifyResponseStatusCode('@suggestApi', 200); + + cy.get(`[data-testid="assignee-option-${secondAssignee}"]`) + .should('be.visible') + .trigger('mouseover') + .trigger('click'); + + cy.clickOutside(); + + cy.get('[data-testid="inline-save-btn"]').click(); + + verifyResponseStatusCode('@editAssignee', 200); + + cy.get(`[data-testid="assignee-${assignee}"]`).should('be.visible'); +}; + +export const createDescriptionTask = (value) => { + interceptURL('POST', 'api/v1/feed', 'createTask'); + + cy.get('#title').should( + 'have.value', + `Update description for table ${value.term}` + ); + + cy.get('[data-testid="select-assignee"] > .ant-select-selector').type( + assignee + ); + // select value from dropdown + verifyResponseStatusCode('@suggestApi', 200); + + cy.get(`[data-testid="assignee-option-${assignee}"]`) + .should('be.visible') + .trigger('mouseover') + .trigger('click'); + + cy.clickOutside(); + + cy.get(descriptionBox).scrollIntoView().clear().type('Updated description'); + + cy.get('button[type="submit"]').click(); + verifyResponseStatusCode('@createTask', 201); + toastNotification('Task created successfully.'); +}; + +export const createAndUpdateDescriptionTask = (value) => { + createDescriptionTask(value); + + // verify the task details + verifyTaskDetails(/#(\d+) UpdateDescriptionfordescription/); + + // edit task assignees + editAssignee(); + + // Accept the description suggestion which is created + cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); + + verifyResponseStatusCode('@taskResolve', 200); + + toastNotification('Task resolved successfully'); + + verifyResponseStatusCode('@entityFeed', 200); +}; diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/ActivityFeed.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/ActivityFeed.spec.js new file mode 100644 index 00000000000..72cf03eb80a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/ActivityFeed.spec.js @@ -0,0 +1,254 @@ +/* + * 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 { + interceptURL, + verifyResponseStatusCode, + visitEntityDetailsPage, +} from '../../common/common'; +import { createDescriptionTask } from '../../common/TaskUtils'; +import { SEARCH_ENTITY_TABLE } from '../../constants/constants'; + +// eslint-disable-next-line spaced-comment +/// + +const reactOnFeed = (feedSelector, reaction) => { + cy.get(feedSelector).within(() => { + cy.get('[data-testid="feed-actions"]').invoke('show'); + cy.get('[data-testid="feed-actions"]').within(() => { + cy.get('[data-testid="add-reactions"]').click(); + }); + }); + + cy.get( + `#reaction-popover [data-testid="reaction-button"][title="${reaction}"]` + ).click(); +}; + +describe('Recently viwed data assets', () => { + beforeEach(() => { + cy.login(); + }); + + it('Feed widget should be visible', () => { + cy.get('[data-testid="activity-feed-widget"]').as('feedWidget'); + cy.get('@feedWidget').should('be.visible'); + cy.get('@feedWidget').should('contain', 'All'); + cy.get('@feedWidget').should('contain', '@Mentions'); + cy.get('@feedWidget').should('contain', 'Tasks'); + cy.get('@feedWidget').should('contain', '0'); + }); + + it('Feed widget should have some feeds', () => { + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]' + ).should('have.length.gte', 1); + }); + + it('Emoji reaction on feed should be working fine', () => { + // Assign reaction for latest feed + [ + 'thumbsUp', + 'thumbsDown', + 'laugh', + 'hooray', + 'confused', + 'heart', + 'eyes', + 'rocket', + ].map((reaction) => + reactOnFeed( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child', + reaction + ) + ); + + // Verify if reaction is working or not + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child' + ).within(() => { + ['🚀', '😕', '👀', '❤️', '🎉', '😄', '👎', '👍'].map((reaction) => + cy + .get('[data-testid="feed-reaction-container"]') + .should('contain', reaction) + ); + }); + }); + + it('User should be able to reply to feed', () => { + interceptURL('GET', '/api/v1/feed/*', 'fetchFeed'); + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child' + ).within(() => { + cy.get('[data-testid="feed-actions"]').invoke('show'); + cy.get('[data-testid="feed-actions"]').within(() => { + cy.get('[data-testid="add-reply"]').click(); + }); + }); + verifyResponseStatusCode('@fetchFeed', 200); + + interceptURL('POST', '/api/v1/feed/*/posts', 'postReply'); + interceptURL( + 'GET', + '/api/v1/search/suggest?q=aa&index=user_search_index%2Cteam_search_index', + 'suggestUser' + ); + interceptURL( + 'GET', + // eslint-disable-next-line max-len + '/api/v1/search/suggest?q=dim_add&index=dashboard_search_index%2Ctable_search_index%2Ctopic_search_index%2Cpipeline_search_index%2Cmlmodel_search_index%2Ccontainer_search_index%2Cglossary_search_index%2Ctag_search_index', + 'suggestAsset' + ); + + cy.get('[data-testid="editor-wrapper"]').should('be.visible'); + cy.get( + '[data-testid="editor-wrapper"] [contenteditable="true"].ql-editor' + ).as('editor'); + cy.get('@editor').click(); + cy.get('@editor').type('Cypress has replied here. Thanks! @aa'); + + verifyResponseStatusCode('@suggestUser', 200); + cy.get('[data-value="@aaron_johnson0"]').click(); + cy.get('@editor').type(' #dim_add'); + verifyResponseStatusCode('@suggestAsset', 200); + cy.get('[data-value="#table/dim_address"]').click(); + + cy.get('[data-testid="send-button"]') + .should('be.visible') + .and('not.be.disabled'); + cy.get('[data-testid="send-button"]').click(); + + verifyResponseStatusCode('@postReply', 201); + + cy.get('[data-testid="replies"]').should('contain', '1 reply'); + cy.get('[data-testid="replies"] .activity-feed-card.activity-feed-card-v1') + .children('.ant-row') + .eq(1) + .invoke('text') + .should( + 'eq', + 'Cypress has replied here. Thanks! @aaron_johnson0 #table/dim_address\n' + ); + + cy.get('[data-testid="closeDrawer"]').click(); + + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child' + ).within(() => { + cy.get('[data-testid="thread-count"]').should('contain', 1); + }); + }); + + it('Mention should work for the feed reply', () => { + interceptURL('GET', '/api/v1/feed/*', 'fetchFeed'); + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child' + ).within(() => { + cy.get('[data-testid="feed-actions"]').invoke('show'); + cy.get('[data-testid="feed-actions"]').within(() => { + cy.get('[data-testid="add-reply"]').click(); + }); + }); + verifyResponseStatusCode('@fetchFeed', 200); + + interceptURL('POST', '/api/v1/feed/*/posts', 'postReply'); + interceptURL( + 'GET', + '/api/v1/search/suggest?q=aa&index=user_search_index%2Cteam_search_index', + 'suggestUser' + ); + + cy.get('[data-testid="editor-wrapper"]').should('be.visible'); + cy.get( + '[data-testid="editor-wrapper"] [contenteditable="true"].ql-editor' + ).as('editor'); + cy.get('@editor').click(); + cy.get('@editor').type('Can you resolve this thread for me? @admin'); + // verifyResponseStatusCode('@suggestUser', 200); + cy.get('[data-value="@admin"]').click(); + + cy.get('[data-testid="send-button"]') + .should('be.visible') + .and('not.be.disabled'); + cy.get('[data-testid="send-button"]').click(); + + verifyResponseStatusCode('@postReply', 201); + + cy.get('[data-testid="closeDrawer"]').click(); + + let feedText1 = ''; + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="message-container"]:first-child [data-testid="viewer-container"]' + ) + .invoke('text') + .then((text) => (feedText1 = text)); + + cy.get('[data-testid="activity-feed-widget"]') + .contains('@Mentions') + .click(); + + // Verify mentioned thread should be there int he mentioned tab + cy.get( + '[data-testid="message-container"] > .activity-feed-card [data-testid="viewer-container"]' + ) + .invoke('text') + .then((text) => expect(text).to.contain(feedText1)); + }); + + it('Assigned task should appear to task tab', () => { + cy.get('[data-testid="activity-feed-widget"]') + .contains('Tasks') + .should('contain', 0); + + cy.get('[data-testid="activity-feed-widget"]').contains('Tasks').click(); + + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="no-data-placeholder"]' + ).should('be.visible'); + + const value = SEARCH_ENTITY_TABLE.table_1; + interceptURL('GET', `/api/v1/${value.entity}/name/*`, 'getEntityDetails'); + + visitEntityDetailsPage(value.term, value.serviceName, value.entity); + + cy.get('[data-testid="request-description"]').click(); + + verifyResponseStatusCode('@getEntityDetails', 200); + + interceptURL('GET', '/api/v1/search/suggest?q=*', 'suggestApi'); + + // create description task + createDescriptionTask(value); + + cy.clickOnLogo(); + + cy.get('[data-testid="activity-feed-widget"]') + .contains('Tasks') + .should('contain', 1) + .click(); + + cy.get( + '[data-testid="activity-feed-widget"] [data-testid="no-data-placeholder"]' + ).should('not.exist'); + + cy.get('[data-testid="message-container"]') + .invoke('text') + .then((textContent) => { + const matches = textContent.match(/#(\d+) UpdateDescriptionfortable/); + + expect(matches).to.not.be.null; + }); + + cy.get(`[data-testid="assignee-admin"]`).should('be.visible'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Task.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Task.spec.js index dc6be6391ed..68882b0ab3b 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Task.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/Task.spec.js @@ -14,12 +14,16 @@ /// import { - descriptionBox, interceptURL, toastNotification, verifyResponseStatusCode, visitEntityDetailsPage, } from '../../common/common'; +import { + createAndUpdateDescriptionTask, + editAssignee, + verifyTaskDetails, +} from '../../common/TaskUtils'; import { SEARCH_ENTITY_TABLE } from '../../constants/constants'; describe('Task flow should work', () => { @@ -37,92 +41,8 @@ describe('Task flow should work', () => { }); const assignee = 'admin'; - const secondAssignee = 'aaron_johnson0'; const tag = 'Personal'; - const editAssignee = () => { - interceptURL('PATCH', 'api/v1/feed/*', 'editAssignee'); - - cy.get('[data-testid="edit-assignees"]').click(); - - cy.get('[data-testid="select-assignee"] > .ant-select-selector').type( - secondAssignee - ); - // select value from dropdown - verifyResponseStatusCode('@suggestApi', 200); - - cy.get(`[data-testid="assignee-option-${secondAssignee}"]`) - .should('be.visible') - .trigger('mouseover') - .trigger('click'); - - cy.clickOutside(); - - cy.get('[data-testid="inline-save-btn"]').click(); - - verifyResponseStatusCode('@editAssignee', 200); - - cy.get(`[data-testid="assignee-${assignee}"]`).should('be.visible'); - }; - - const verifyTaskDetails = (regexPattern) => { - cy.get('#task-panel').should('be.visible'); - cy.get('[data-testid="task-title"]') - .invoke('text') - .then((textContent) => { - const matches = textContent.match(regexPattern); - - expect(matches).to.not.be.null; - }); - - cy.get('[data-testid="owner-link"]').contains(assignee); - - cy.get(`[data-testid="assignee-${assignee}"]`).should('be.visible'); - }; - - const createDescriptionTask = (value) => { - interceptURL('POST', 'api/v1/feed', 'createTask'); - - cy.get('#title').should( - 'have.value', - `Update description for table ${value.term}` - ); - - cy.get('[data-testid="select-assignee"] > .ant-select-selector').type( - assignee - ); - // select value from dropdown - verifyResponseStatusCode('@suggestApi', 200); - - cy.get(`[data-testid="assignee-option-${assignee}"]`) - .should('be.visible') - .trigger('mouseover') - .trigger('click'); - - cy.clickOutside(); - - cy.get(descriptionBox).scrollIntoView().clear().type('Updated description'); - - cy.get('button[type="submit"]').click(); - verifyResponseStatusCode('@createTask', 201); - toastNotification('Task created successfully.'); - - // verify the task details - verifyTaskDetails(/#(\d+) UpdateDescriptionfordescription/); - - // edit task assignees - editAssignee(); - - // Accept the description suggestion which is created - cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); - - verifyResponseStatusCode('@taskResolve', 200); - - toastNotification('Task resolved successfully'); - - verifyResponseStatusCode('@entityFeed', 200); - }; - const createTagTask = (value) => { interceptURL('POST', 'api/v1/feed', 'createTask'); @@ -186,7 +106,7 @@ describe('Task flow should work', () => { verifyResponseStatusCode('@getEntityDetails', 200); // create description task - createDescriptionTask(value); + createAndUpdateDescriptionTask(value); }); it('Task flow for table tags', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1.tsx index ab84582776a..89294fcebaf 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1.tsx @@ -129,6 +129,7 @@ const ActivityFeedCardV1 = ({
{' '} {postLength} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx index f70b4bba355..90348372a8c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/Shared/ActivityFeedActions.tsx @@ -139,7 +139,7 @@ const ActivityFeedActions = ({ return ( <> - + {feed.type !== ThreadType.Task && !isPost && ( = ({ setIsClicked(false); }, [reaction]); + const element = React.createElement( + 'g-emoji', + { + alias: reactionObject?.alias, + className: 'd-flex', + 'data-testid': 'emoji', + 'fallback-src': image, + }, + reactionObject?.emoji + ); + return ( = ({ size="small" onClick={handleEmojiOnClick} onMouseOver={() => setVisible(true)}> -
- ${reactionObject?.emoji} - `, - }} - /> + {element} {reactionList.length} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reaction.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reaction.tsx index 1983ec8eb39..1f054837e5b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reaction.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reaction.tsx @@ -51,6 +51,17 @@ const Reaction: FC = ({ onHide(); }; + const element = React.createElement( + 'g-emoji', + { + alias: reaction?.alias, + className: 'd-flex', + 'data-testid': 'emoji', + 'fallback-src': image, + }, + reaction?.emoji + ); + return ( ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reactions.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reactions.tsx index 2961fae6902..8005d9e5352 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reactions.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Reactions/Reactions.tsx @@ -104,7 +104,7 @@ const Reactions: FC = ({ reactions, onReactionSelect }) => { }); return ( -
+
{emojis} { }, [activeTab, entityThread]); return ( -
+