Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions cypress/e2e/2_cache-detail-search.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@ describe('Cache Detail Overview', () => {
it('successfully deletes by query', () => {
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').click().type('from org.infinispan.Person where age = 9');
cy.get('button[aria-label=searchButton]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age = 9', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('1 - 1 of 1');
cy.contains('Oihana');
cy.get('[data-cy=deleteByQueryButton]').click();
cy.get('[data-cy=deleteButton]').click();
cy.get('button[aria-label=searchButton]').click();
cy.get('[data-cy=searchButton]').click();
cy.contains('Values not found.');
cy.get('[data-cy=deleteByQueryButton]').should('be.disabled');
})
Expand All @@ -32,25 +32,27 @@ describe('Cache Detail Overview', () => {
// Going back to cache entries page
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').click().type('from org.infinispan.Person where age > 2');
cy.get('button[aria-label=searchButton]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('1 - 1 of 1');
cy.contains('Elaia');

// Going back to cache entries page
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').click().clear().type("from org.infinispan.Person where name = 'Elaia'");
cy.get('button[aria-label=searchButton]').click();
cy.get('#textSearchByQuery').find('textarea').type('{selectall}{backspace}', { force: true });
cy.get('#textSearchByQuery').find('textarea').type("from org.infinispan.Person where name = 'Elaia'", { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('1 - 1 of 1');
cy.contains('Elaia');

// Going back to cache entries page
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();

cy.get('#textSearchByQuery').click().clear().type('from org.infinispan.Person where age=2');
cy.get('button[aria-label=searchButton]').click();
cy.get('#textSearchByQuery').find('textarea').type('{selectall}{backspace}', { force: true });
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age=2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('Values not found.');

// Verify query metrics available
Expand Down
144 changes: 144 additions & 0 deletions cypress/e2e/2_cache-query-history.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
describe('Query History', () => {
const username = Cypress.env('username');
const password = Cypress.env('password');

before(() => {
// Ensure test data exists
cy.cleanupTest(username, password, '/caches/indexed-cache/query-history-test', 'DELETE');
const payload = '{"_type": "org.infinispan.Person", "name": "HistoryTest", "age": "25", "city": "Madrid"}';
cy.cleanupTest(username, password, '/caches/indexed-cache/query-history-test', 'POST', payload);
});

beforeEach(() => {
// Clear localStorage to start with clean query history
cy.clearLocalStorage('cache-query-history');
cy.login(username, password, '/cache/indexed-cache');
});

it('successfully records a search query in history', () => {
// Navigate to queries tab and execute a search
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('Elaia');

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();

// Verify the query appears in the history table
cy.get('[data-cy=queryHistoryTable]').should('exist');
cy.contains('from org.infinispan.Person where age > 2');
cy.contains('Search');
});

it('successfully records a delete query in history', () => {
// Navigate to queries tab and execute a delete query
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type("delete from org.infinispan.Person where name = 'HistoryTest'", { force: true });
cy.get('[data-cy=deleteByQueryButton]').click();
cy.get('[data-cy=deleteButton]').click();

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();

// Verify the delete query appears in history with Delete type label
cy.get('[data-cy=queryHistoryTable]').should('exist');
cy.contains('delete from org.infinispan.Person');
cy.contains('Delete');

// Re-create test data for subsequent tests
cy.cleanupTest(username, password, '/caches/indexed-cache/query-history-test', 'DELETE');
const payload = '{"_type": "org.infinispan.Person", "name": "HistoryTest", "age": "25", "city": "Madrid"}';
cy.cleanupTest(username, password, '/caches/indexed-cache/query-history-test', 'POST', payload);
});

it('successfully deletes a single history item', () => {
// First, create a history entry by running a query
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('Elaia');

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();
cy.get('[data-cy=queryHistoryTable]').should('exist');
cy.contains('from org.infinispan.Person where age > 2');

// Open actions menu on the first row and click delete
cy.get('[data-cy=actions-0]').find('button').click();
cy.contains('button', 'Delete').click();

// Verify the query is removed from history
cy.contains('from org.infinispan.Person where age > 2').should('not.exist');
});

it('successfully clears all query history', () => {
// Create history entries by running queries
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('Elaia');

cy.get('#textSearchByQuery').find('textarea').type('{selectall}{backspace}', { force: true });
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 10', { force: true });
cy.get('[data-cy=searchButton]').click();

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();
cy.get('[data-cy=queryHistoryTable]').should('exist');

// Click clear all button
cy.get('[data-cy=removeAllQueryHistory]').click();

// Verify all history is cleared
cy.contains('from org.infinispan.Person where age > 2').should('not.exist');
cy.contains('from org.infinispan.Person where age > 10').should('not.exist');
});

it('successfully executes a query from history', () => {
// Create a history entry
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type('from org.infinispan.Person where age > 2', { force: true });
cy.get('[data-cy=searchButton]').click();
cy.contains('Elaia');

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();
cy.get('[data-cy=queryHistoryTable]').should('exist');

// Execute the query from history
cy.get('[data-cy=actions-0]').find('button').click();
cy.contains('button', 'Execute').click();

// Verify we are back on the queries tab
cy.get('[data-cy=queriesTab]').should('have.attr', 'aria-selected', 'true');
});

it('successfully records an error query in history', () => {
// Execute a query with an invalid type
cy.get('[data-cy=manageEntriesTab]').click();
cy.get('[data-cy=queriesTab]').click();
cy.get('#textSearchByQuery').find('textarea').type('from com.NonExistent', { force: true });
cy.get('[data-cy=searchButton]').click();

// Wait for the error response
cy.contains('Query error');

// Navigate to query history tab
cy.get('[data-cy=queryHistoryTab]').click();
cy.get('[data-cy=queryHistoryTable]').should('exist');

// Verify the error query is shown with Error label
cy.contains('from com.NonExistent');
cy.contains('Error');
});

after(() => {
cy.cleanupTest(username, password, '/caches/indexed-cache/query-history-test', 'DELETE');
});
});
2 changes: 1 addition & 1 deletion src/app/AppLayout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import {
import { ConsoleACL } from '@services/securityService';
import { DARK, LIGHT, ThemeContext } from '@app/providers/ThemeProvider';
import { PopoverHelp } from '@app/Common/PopoverHelp';
import { LanguageSelector } from `@app/Common/LanguageSelector`;
import { LanguageSelector } from '@app/Common/LanguageSelector';

interface IAppLayout {
children: React.ReactNode;
Expand Down
54 changes: 35 additions & 19 deletions src/app/Caches/DetailCache.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ import { InfinispanComponentStatus } from '@app/Common/InfinispanComponentStatus
import { PageHeader } from '@patternfly/react-component-groups';
import { UpdateAliasCache } from '@app/Caches/UpdateAliasCache';
import { DeleteCache } from '@app/Caches/DeleteCache';
import { QueryHistory } from '@app/Caches/Query/QueryHistory';
import { QueryContextProvider } from '@app/providers/QueryContextProvider';

const DetailCache = (props: { cacheName: string }) => {
const cacheName = props.cacheName;
Expand Down Expand Up @@ -110,26 +112,40 @@ const DetailCache = (props: { cacheName: string }) => {
}

return (
<Tabs
unmountOnExit
isSubtab={true}
activeKey={activeTabKey2}
aria-label="Entries tab"
component={TabsComponent.nav}
style={theme === DARK ? {} : { backgroundColor: t_global_background_color_100.value }}
onSelect={(event, tabIndex) => setActiveTabKey2(tabIndex)}
>
<Tab
eventKey={10}
title={<TabTitleText>{t('caches.tabs.entries-manage')}</TabTitleText>}
data-cy="manageEntriesTab"
<QueryContextProvider>
<Tabs
unmountOnExit
isSubtab={true}
activeKey={activeTabKey2}
aria-label="Entries tab"
component={TabsComponent.nav}
style={theme === DARK ? {} : { backgroundColor: t_global_background_color_100.value }}
onSelect={(event, tabIndex) => setActiveTabKey2(tabIndex)}
>
<CacheEntries />
</Tab>
<Tab eventKey={11} data-cy="queriesTab" title={<TabTitleText>{t('caches.tabs.query-values')}</TabTitleText>}>
<QueryEntries cacheName={cacheName} changeTab={() => setActiveTabKey1(2)} />
</Tab>
</Tabs>
<Tab
eventKey={10}
title={<TabTitleText>{t('caches.tabs.entries-manage')}</TabTitleText>}
data-cy="manageEntriesTab"
>
<CacheEntries />
</Tab>
<Tab eventKey={11} data-cy="queriesTab" title={<TabTitleText>{t('caches.tabs.query-values')}</TabTitleText>}>
<QueryEntries cacheName={cacheName} changeTab={() => setActiveTabKey1(2)} />
</Tab>
<Tab
eventKey={12}
data-cy="queryHistoryTab"
title={<TabTitleText>{t('caches.tabs.query-history')}</TabTitleText>}
>
<QueryHistory
cacheName={cacheName}
changeTab={() => {
setActiveTabKey2(11);
}}
/>
</Tab>
</Tabs>
</QueryContextProvider>
);
};

Expand Down
25 changes: 6 additions & 19 deletions src/app/Caches/Query/DeleteByQueryEntries.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
import React from 'react';
import { Button, ButtonVariant, Content, Modal, ModalBody, ModalFooter, ModalHeader } from '@patternfly/react-core';
import { useApiAlert } from '@app/utils/useApiAlert';
import { useTranslation } from 'react-i18next';
import { ConsoleServices } from '@services/ConsoleServices';
import { useDeleteByQuery } from '@app/services/searchHook';

const DeleteByQueryEntries = (props: {
cacheName: string;
query: string;
isModalOpen: boolean;
closeModal: () => void;
}) => {
const { addAlert } = useApiAlert();
const { t } = useTranslation();

const onClickOnDeleteByQuery = () => {
const match = props.query.match(/FROM\s+.+$/i);
if (match) {
ConsoleServices.search()
.deleteByQuery(props.cacheName, `DELETE ${match[0]}`, t('caches.query.modal-action-entries-success'))
.then((actionResponse) => {
addAlert(actionResponse);
})
.finally(props.closeModal);
} else {
props.closeModal();
}
};
const { setExecute } = useDeleteByQuery(props.cacheName, props.query, props.closeModal);

return (
<Modal
Expand All @@ -37,12 +22,14 @@ const DeleteByQueryEntries = (props: {
<ModalHeader titleIconVariant={'warning'} title={t('caches.query.modal-delete-entries-title')} />
<ModalBody>
<Content component={'p'}>
{t('caches.query.modal-delete-entries-body-line-one', { cacheName: props.cacheName })}
{t('caches.query.modal-delete-entries-body-line-one', {
cacheName: props.cacheName
})}
</Content>
<Content component={'p'}>{t('caches.query.modal-delete-entries-body-line-two')}</Content>
</ModalBody>
<ModalFooter>
<Button data-cy="deleteButton" key="confirm" variant={ButtonVariant.danger} onClick={onClickOnDeleteByQuery}>
<Button data-cy="deleteButton" key="confirm" variant={ButtonVariant.danger} onClick={() => setExecute(true)}>
{t('common.actions.delete')}
</Button>
<Button data-cy="cancelButton" key="cancel" variant="link" onClick={props.closeModal}>
Expand Down
Loading
Loading