diff --git a/cypress/integration/plugins/dashboards-assistant/conversation_history_spec.js b/cypress/integration/plugins/dashboards-assistant/conversation_history_spec.js new file mode 100644 index 000000000..0349481b9 --- /dev/null +++ b/cypress/integration/plugins/dashboards-assistant/conversation_history_spec.js @@ -0,0 +1,212 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { BASE_PATH } from '../../../utils/constants'; +import { setStorageItem } from '../../../utils/plugins/dashboards-assistant/helpers'; + +if (Cypress.env('DASHBOARDS_ASSISTANT_ENABLED')) { + describe('Assistant conversation history spec', () => { + let restoreShowHome; + let restoreNewThemeModal; + + before(() => { + // Set welcome screen tracking to false + restoreShowHome = setStorageItem( + localStorage, + 'home:welcome:show', + 'false' + ); + // Hide new theme modal + restoreNewThemeModal = setStorageItem( + localStorage, + 'home:newThemeModal:show', + 'false' + ); + // Visit OSD + cy.visit(`${BASE_PATH}/app/home`); + // Common text to wait for to confirm page loaded, give up to 60 seconds for initial load + cy.get(`input[placeholder="Ask question"]`, { timeout: 60000 }).should( + 'be.length', + 1 + ); + + // Open assistant flyout + // The flyout button will be detached and can't be clicked, add 10s delayed fix it. + cy.wait(10000); + cy.get('img[aria-label="toggle chat flyout icon"]').click(); + }); + after(() => { + if (restoreShowHome) { + restoreShowHome(); + } + if (restoreNewThemeModal) { + restoreNewThemeModal(); + } + // Close assistant flyout + cy.get('img[aria-label="toggle chat flyout icon"]').click(); + }); + + beforeEach(() => { + cy.get('.llm-chat-flyout', { timeout: 60000 }).should('be.visible'); + }); + + describe('panel operations', () => { + it('should toggle history list', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]') + .should('be.visible') + .click(); + cy.get('.llm-chat-flyout-body') + .contains('Conversations') + .should('be.visible'); + + cy.get('.llm-chat-flyout button[aria-label="history"]') + .should('be.visible') + .click(); + cy.get('textarea[placeholder="Ask me anything..."]').should('exist'); + cy.get('.llm-chat-flyout-body') + .contains('Conversations') + .should('not.be.visible'); + }); + + it('should back to chat panel', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + cy.get('.llm-chat-flyout') + .contains('Conversations') + .should('be.visible'); + + cy.get('.llm-chat-flyout-body').contains('Back').click(); + cy.get('textarea[placeholder="Ask me anything..."]').should('exist'); + }); + + it('should hide back button in fullscreen mode', () => { + cy.get('.llm-chat-flyout button[aria-label="fullScreen"]').click(); + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + + cy.get('.llm-chat-flyout') + .contains('Conversations') + .should('be.visible'); + cy.get('textarea[placeholder="Ask me anything..."]').should('exist'); + cy.get('.llm-chat-flyout-body') + .contains('Back', { timeout: 3000 }) + .should('not.exist'); + + // Back to default mode + cy.get('.llm-chat-flyout button[aria-label="fullScreen"]').click(); + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + }); + }); + describe('history item operations', () => { + const conversations = []; + + before(() => { + // Create conversations data + cy.sendAssistantMessage({ + input: { + type: 'input', + content: 'What are the indices in my cluster?', + contentType: 'text', + }, + }).then((result) => { + if (result.status !== 200) { + throw result.body; + } + conversations.push(result.body); + }); + }); + + after(() => { + // Clear created conversations in tests + conversations.map(({ conversationId }) => + cy.deleteConversation(conversationId) + ); + }); + + it('should show created conversation in the history list', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + + cy.get('.llm-chat-flyout').contains('Conversations'); + conversations.forEach(({ conversationId }) => { + cy.getElementByTestId(`chatHistoryItem-${conversationId}`).should( + 'exist' + ); + }); + cy.contains('What are the indices in my cluster?'); + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + }); + + it('should load conversation in chat panel', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + + const conversationToLoad = conversations[0]; + + cy.getElementByTestId( + `chatHistoryItem-${conversationToLoad.conversationId}` + ) + .contains(conversationToLoad.title) + .click(); + cy.get('textarea[placeholder="Ask me anything..."]').should('exist'); + cy.get('div.llm-chat-bubble-panel-input').contains( + conversationToLoad.title + ); + }); + + it('should able to update conversation title', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + + const conversationToUpdate = conversations[0]; + const newTitle = 'New title'; + + cy.getElementByTestId( + `chatHistoryItem-${conversationToUpdate.conversationId}` + ) + .find('button[aria-label="Edit conversation name"]') + .click(); + cy.contains('Edit conversation name'); + + cy.get('input[aria-label="Conversation name input"').type(newTitle); + cy.getElementByTestId('confirmModalConfirmButton') + .contains('Confirm name') + .click(); + + conversationToUpdate.title = newTitle; + cy.getElementByTestId( + `chatHistoryItem-${conversationToUpdate.conversationId}` + ).contains(conversationToUpdate.title); + cy.contains('Edit conversation name', { timeout: 3000 }).should( + 'not.exist' + ); + + // Reset to chat panel + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + }); + + it('should able to delete conversation', () => { + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + + const conversationToDelete = conversations[0]; + + cy.getElementByTestId( + `chatHistoryItem-${conversationToDelete.conversationId}` + ) + .find('button[aria-label="Delete conversation"]') + .click(); + cy.getElementByTestId('confirmModalTitleText').contains( + 'Delete conversation' + ); + + cy.getElementByTestId('confirmModalConfirmButton') + .contains('Delete conversation') + .click(); + + cy.getElementByTestId( + `chatHistoryItem-${conversationToDelete.conversationId}` + ).should('not.exist'); + conversations.shift(); + + // Reset to chat panel + cy.get('.llm-chat-flyout button[aria-label="history"]').click(); + }); + }); + }); +} diff --git a/cypress/utils/plugins/dashboards-assistant/commands.js b/cypress/utils/plugins/dashboards-assistant/commands.js index 51df1be8a..8197c9d80 100644 --- a/cypress/utils/plugins/dashboards-assistant/commands.js +++ b/cypress/utils/plugins/dashboards-assistant/commands.js @@ -4,9 +4,10 @@ */ import FlowTemplateJSON from '../../../fixtures/plugins/dashboards-assistant/flow-template.json'; -import { BACKEND_BASE_PATH } from '../../constants'; -import { ML_COMMONS_API } from './constants'; +import { BACKEND_BASE_PATH, BASE_PATH } from '../../constants'; +import { ML_COMMONS_API, ASSISTANT_API } from './constants'; import clusterSettings from '../../../fixtures/plugins/dashboards-assistant/cluster_settings.json'; +import { apiRequest } from '../../helpers'; Cypress.Commands.add('addAssistantRequiredSettings', () => { cy.request('PUT', `${BACKEND_BASE_PATH}/_cluster/settings`, clusterSettings); @@ -152,3 +153,14 @@ Cypress.Commands.add('stopDummyServer', () => { } }); }); + +Cypress.Commands.add('sendAssistantMessage', (body) => + apiRequest(`${BASE_PATH}${ASSISTANT_API.SEND_MESSAGE}`, 'POST', body) +); + +Cypress.Commands.add('deleteConversation', (conversationId) => + apiRequest( + `${BASE_PATH}${ASSISTANT_API.CONVERSATION}/${conversationId}`, + 'DELETE' + ) +); diff --git a/cypress/utils/plugins/dashboards-assistant/constants.js b/cypress/utils/plugins/dashboards-assistant/constants.js index 16592accb..c8340b5ed 100644 --- a/cypress/utils/plugins/dashboards-assistant/constants.js +++ b/cypress/utils/plugins/dashboards-assistant/constants.js @@ -15,3 +15,10 @@ export const ML_COMMONS_API = { CREATE_MODEL: `${ML_COMMONS_API_PREFIX}/models/_register`, CREATE_AGENT: `${ML_COMMONS_API_PREFIX}/agents/_register`, }; + +export const ASSISTANT_API_BASE = '/api/assistant'; + +export const ASSISTANT_API = { + SEND_MESSAGE: `${ASSISTANT_API_BASE}/send_message`, + CONVERSATION: `${ASSISTANT_API_BASE}/conversation`, +}; diff --git a/cypress/utils/plugins/dashboards-assistant/helpers.js b/cypress/utils/plugins/dashboards-assistant/helpers.js new file mode 100644 index 000000000..53093a530 --- /dev/null +++ b/cypress/utils/plugins/dashboards-assistant/helpers.js @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export const setStorageItem = (storage, key, value) => { + const oldValue = storage.getItem(key); + storage.setItem(key, value); + return () => { + if (oldValue === null) { + storage.removeItem(key); + } else { + storage.setItem(key, oldValue); + } + }; +};