mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cases] Enable case search by ID (#149233)
Fixes #148084 [The uuid PR was merged](https://github.com/elastic/kibana/pull/149135) so I am removing the `draft` status here. ## Summary This PR introduces search by UUID in the Cases table. If a user puts a UUID in the search bar and presses enter the search result will now return the case with that ID. Additionally, we look for the matches of that search text in the title and description fields. See the example below: <img width="1554" alt="Screenshot 2023-01-19 at 16 06 53" src="https://user-images.githubusercontent.com/1533137/213477884-498d34c0-d4d1-405d-8d76-f077d46157aa.png"> We are searching for `733e1c40-9586-11ed-a29f-8b57be9cf211`. There are two matches because that search text matches the ID of a case and the title of another. ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) ### Release notes Users can now search for Cases by ID. Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
7f2bf935ec
commit
a04a03b438
16 changed files with 324 additions and 43 deletions
|
@ -213,6 +213,10 @@ export const CasesFindRequestRt = rt.partial({
|
|||
* The fields to perform the simple_query_string parsed query against
|
||||
*/
|
||||
searchFields: rt.union([rt.array(rt.string), rt.string]),
|
||||
/**
|
||||
* The root fields to perform the simple_query_string parsed query against
|
||||
*/
|
||||
rootSearchFields: rt.array(rt.string),
|
||||
/**
|
||||
* The field to use for sorting the found objects.
|
||||
*
|
||||
|
|
|
@ -64,7 +64,7 @@ export const INCIDENT_MANAGEMENT_SYSTEM = i18n.translate('xpack.cases.caseTable.
|
|||
});
|
||||
|
||||
export const SEARCH_PLACEHOLDER = i18n.translate('xpack.cases.caseTable.searchPlaceholder', {
|
||||
defaultMessage: 'e.g. case name',
|
||||
defaultMessage: 'Search cases',
|
||||
});
|
||||
|
||||
export const CLOSED = i18n.translate('xpack.cases.caseTable.closed', {
|
||||
|
|
|
@ -14,7 +14,9 @@ export type AuthorizationMock = jest.Mocked<Schema>;
|
|||
export const createAuthorizationMock = () => {
|
||||
const mocked: AuthorizationMock = {
|
||||
ensureAuthorized: jest.fn(),
|
||||
getAuthorizationFilter: jest.fn(),
|
||||
getAuthorizationFilter: jest.fn().mockImplementation(async () => {
|
||||
return { filter: undefined, ensureSavedObjectsAreAuthorized: () => {} };
|
||||
}),
|
||||
getAndEnsureAuthorizedEntities: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
|
|
66
x-pack/plugins/cases/server/client/cases/find.test.ts
Normal file
66
x-pack/plugins/cases/server/client/cases/find.test.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { v1 as uuidv1 } from 'uuid';
|
||||
|
||||
import type { CaseResponse } from '../../../common/api';
|
||||
|
||||
import { flattenCaseSavedObject } from '../../common/utils';
|
||||
import { mockCases } from '../../mocks';
|
||||
import { createCasesClientMockArgs, createCasesClientMockFindRequest } from '../mocks';
|
||||
import { find } from './find';
|
||||
|
||||
describe('find', () => {
|
||||
describe('constructSearch', () => {
|
||||
const clientArgs = createCasesClientMockArgs();
|
||||
const casesMap = new Map<string, CaseResponse>(
|
||||
mockCases.map((obj) => {
|
||||
return [obj.id, flattenCaseSavedObject({ savedObject: obj, totalComment: 2 })];
|
||||
})
|
||||
);
|
||||
clientArgs.services.caseService.findCasesGroupedByID.mockResolvedValue({
|
||||
page: 1,
|
||||
perPage: 10,
|
||||
total: casesMap.size,
|
||||
casesMap,
|
||||
});
|
||||
clientArgs.services.caseService.getCaseStatusStats.mockResolvedValue({
|
||||
open: 1,
|
||||
'in-progress': 2,
|
||||
closed: 3,
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('search by uuid updates search term and adds rootSearchFields', async () => {
|
||||
const search = uuidv1();
|
||||
const findRequest = createCasesClientMockFindRequest({ search });
|
||||
|
||||
await find(findRequest, clientArgs);
|
||||
await expect(clientArgs.services.caseService.findCasesGroupedByID).toHaveBeenCalled();
|
||||
|
||||
const call = clientArgs.services.caseService.findCasesGroupedByID.mock.calls[0][0];
|
||||
|
||||
expect(call.caseOptions.search).toBe(`"${search}" "cases:${search}"`);
|
||||
expect(call.caseOptions).toHaveProperty('rootSearchFields');
|
||||
expect(call.caseOptions.rootSearchFields).toStrictEqual(['_id']);
|
||||
});
|
||||
|
||||
it('regular search term does not cause rootSearchFields to be appended', async () => {
|
||||
const search = 'foobar';
|
||||
const findRequest = createCasesClientMockFindRequest({ search });
|
||||
await find(findRequest, clientArgs);
|
||||
await expect(clientArgs.services.caseService.findCasesGroupedByID).toHaveBeenCalled();
|
||||
|
||||
const call = clientArgs.services.caseService.findCasesGroupedByID.mock.calls[0][0];
|
||||
|
||||
expect(call.caseOptions.search).toBe(search);
|
||||
expect(call.caseOptions).not.toHaveProperty('rootSearchFields');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -16,7 +16,7 @@ import { CasesFindRequestRt, throwErrors, CasesFindResponseRt, excess } from '..
|
|||
|
||||
import { createCaseError } from '../../common/error';
|
||||
import { asArray, transformCases } from '../../common/utils';
|
||||
import { constructQueryOptions } from '../utils';
|
||||
import { constructQueryOptions, constructSearch } from '../utils';
|
||||
import { includeFieldsRequiredForAuthentication } from '../../authorization/utils';
|
||||
import { Operations } from '../../authorization';
|
||||
import type { CasesClientArgs } from '..';
|
||||
|
@ -36,6 +36,8 @@ export const find = async (
|
|||
services: { caseService, licensingService },
|
||||
authorization,
|
||||
logger,
|
||||
savedObjectsSerializer,
|
||||
spaceId,
|
||||
} = clientArgs;
|
||||
|
||||
try {
|
||||
|
@ -85,11 +87,14 @@ export const find = async (
|
|||
|
||||
const caseQueryOptions = constructQueryOptions({ ...queryArgs, authorizationFilter });
|
||||
|
||||
const caseSearch = constructSearch(queryParams.search, spaceId, savedObjectsSerializer);
|
||||
|
||||
const [cases, statusStats] = await Promise.all([
|
||||
caseService.findCasesGroupedByID({
|
||||
caseOptions: {
|
||||
...queryParams,
|
||||
...caseQueryOptions,
|
||||
...caseSearch,
|
||||
searchFields: asArray(queryParams.searchFields),
|
||||
fields: includeFieldsRequiredForAuthentication(fields),
|
||||
},
|
||||
|
|
|
@ -145,6 +145,7 @@ export class CasesClientFactory {
|
|||
securityStartPlugin: this.options.securityPluginStart,
|
||||
publicBaseUrl: this.options.publicBaseUrl,
|
||||
spaceId: this.options.spacesPluginStart.spacesService.getSpaceId(request),
|
||||
savedObjectsSerializer,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -181,9 +181,6 @@ function createMockClientArgs() {
|
|||
});
|
||||
|
||||
const authorization = createAuthorizationMock();
|
||||
authorization.getAuthorizationFilter.mockImplementation(async () => {
|
||||
return { filter: undefined, ensureSavedObjectsAreAuthorized: () => {} };
|
||||
});
|
||||
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
|
||||
|
|
|
@ -19,9 +19,6 @@ export function createMockClient() {
|
|||
|
||||
export function createMockClientArgs() {
|
||||
const authorization = createAuthorizationMock();
|
||||
authorization.getAuthorizationFilter.mockImplementation(async () => {
|
||||
return { filter: undefined, ensureSavedObjectsAreAuthorized: () => {} };
|
||||
});
|
||||
|
||||
const soClient = savedObjectsClientMock.create();
|
||||
|
||||
|
|
|
@ -7,11 +7,29 @@
|
|||
|
||||
import type { PublicContract, PublicMethodsOf } from '@kbn/utility-types';
|
||||
import { loggingSystemMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server';
|
||||
|
||||
import { securityMock } from '@kbn/security-plugin/server/mocks';
|
||||
import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client.mock';
|
||||
import { makeLensEmbeddableFactory } from '@kbn/lens-plugin/server/embeddable/make_lens_embeddable_factory';
|
||||
import { serializerMock } from '@kbn/core-saved-objects-base-server-mocks';
|
||||
|
||||
import type { CasesFindRequest } from '../../common/api';
|
||||
import type { CasesClient } from '.';
|
||||
import type { AttachmentsSubClient } from './attachments/client';
|
||||
import type { CasesSubClient } from './cases/client';
|
||||
import type { ConfigureSubClient } from './configure/client';
|
||||
import type { CasesClientFactory } from './factory';
|
||||
import type { MetricsSubClient } from './metrics/client';
|
||||
import type { UserActionsSubClient } from './user_actions/client';
|
||||
|
||||
import { CaseStatuses } from '../../common';
|
||||
import { CaseSeverity } from '../../common/api';
|
||||
import { SortFieldCase } from '../../public/containers/types';
|
||||
import {
|
||||
createExternalReferenceAttachmentTypeRegistryMock,
|
||||
createPersistableStateAttachmentTypeRegistryMock,
|
||||
} from '../attachment_framework/mocks';
|
||||
import { createAuthorizationMock } from '../authorization/mock';
|
||||
import {
|
||||
connectorMappingsServiceMock,
|
||||
|
@ -23,16 +41,6 @@ import {
|
|||
createUserActionServiceMock,
|
||||
createNotificationServiceMock,
|
||||
} from '../services/mocks';
|
||||
import type { AttachmentsSubClient } from './attachments/client';
|
||||
import type { CasesSubClient } from './cases/client';
|
||||
import type { ConfigureSubClient } from './configure/client';
|
||||
import type { CasesClientFactory } from './factory';
|
||||
import type { MetricsSubClient } from './metrics/client';
|
||||
import type { UserActionsSubClient } from './user_actions/client';
|
||||
import {
|
||||
createExternalReferenceAttachmentTypeRegistryMock,
|
||||
createPersistableStateAttachmentTypeRegistryMock,
|
||||
} from '../attachment_framework/mocks';
|
||||
|
||||
type CasesSubClientMock = jest.Mocked<CasesSubClient>;
|
||||
|
||||
|
@ -127,6 +135,20 @@ export const createCasesClientFactory = (): CasesClientFactoryMock => {
|
|||
return factory as unknown as CasesClientFactoryMock;
|
||||
};
|
||||
|
||||
type SavedObjectsSerializerMock = jest.Mocked<ISavedObjectsSerializer>;
|
||||
|
||||
export const createSavedObjectsSerializerMock = (): SavedObjectsSerializerMock => {
|
||||
const serializer = serializerMock.create();
|
||||
serializer.generateRawId.mockImplementation(
|
||||
(namespace: string | undefined, type: string, id: string) => {
|
||||
const namespacePrefix = namespace ? `${namespace}:` : '';
|
||||
return `${namespacePrefix}${type}:${id}`;
|
||||
}
|
||||
);
|
||||
|
||||
return serializer;
|
||||
};
|
||||
|
||||
export const createCasesClientMockArgs = () => {
|
||||
return {
|
||||
services: {
|
||||
|
@ -160,5 +182,22 @@ export const createCasesClientMockArgs = () => {
|
|||
{}
|
||||
)
|
||||
),
|
||||
savedObjectsSerializer: createSavedObjectsSerializerMock(),
|
||||
};
|
||||
};
|
||||
|
||||
export const createCasesClientMockFindRequest = (
|
||||
overwrites?: CasesFindRequest
|
||||
): CasesFindRequest => ({
|
||||
search: '',
|
||||
searchFields: ['title', 'description'],
|
||||
severity: CaseSeverity.LOW,
|
||||
assignees: [],
|
||||
reporters: [],
|
||||
status: CaseStatuses.open,
|
||||
tags: [],
|
||||
owner: [],
|
||||
sortField: SortFieldCase.createdAt,
|
||||
sortOrder: 'desc',
|
||||
...overwrites,
|
||||
});
|
||||
|
|
|
@ -11,6 +11,7 @@ import type { ActionsClient } from '@kbn/actions-plugin/server';
|
|||
import type { LensServerPluginSetup } from '@kbn/lens-plugin/server';
|
||||
import type { SecurityPluginStart } from '@kbn/security-plugin/server';
|
||||
import type { IBasePath } from '@kbn/core-http-browser';
|
||||
import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server';
|
||||
import type { KueryNode } from '@kbn/es-query';
|
||||
import type { CasesFindRequest, User } from '../../common/api';
|
||||
import type { Authorization } from '../authorization/authorization';
|
||||
|
@ -53,6 +54,7 @@ export interface CasesClientArgs {
|
|||
readonly externalReferenceAttachmentTypeRegistry: ExternalReferenceAttachmentTypeRegistry;
|
||||
readonly securityStartPlugin: SecurityPluginStart;
|
||||
readonly spaceId: string;
|
||||
readonly savedObjectsSerializer: ISavedObjectsSerializer;
|
||||
readonly publicBaseUrl?: IBasePath['publicBaseUrl'];
|
||||
}
|
||||
|
||||
|
|
|
@ -5,17 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { v1 as uuidv1 } from 'uuid';
|
||||
|
||||
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';
|
||||
import { toElasticsearchQuery } from '@kbn/es-query';
|
||||
|
||||
import { CaseStatuses } from '../../common';
|
||||
import { CaseSeverity } from '../../common/api';
|
||||
import { ESCaseSeverity, ESCaseStatus } from '../services/cases/types';
|
||||
import { createSavedObjectsSerializerMock } from './mocks';
|
||||
import {
|
||||
arraysDifference,
|
||||
buildNestedFilter,
|
||||
buildRangeFilter,
|
||||
constructQueryOptions,
|
||||
constructSearch,
|
||||
convertSortField,
|
||||
} from './utils';
|
||||
import { toElasticsearchQuery } from '@kbn/es-query';
|
||||
import { CaseStatuses } from '../../common';
|
||||
import { CaseSeverity } from '../../common/api';
|
||||
import { ESCaseSeverity, ESCaseStatus } from '../services/cases/types';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('convertSortField', () => {
|
||||
|
@ -916,4 +922,36 @@ describe('utils', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructSearchById', () => {
|
||||
const savedObjectsSerializer = createSavedObjectsSerializerMock();
|
||||
|
||||
it('returns the rootSearchFields and search with correct values when given a uuid', () => {
|
||||
const uuid = uuidv1(); // the specific version is irrelevant
|
||||
|
||||
expect(constructSearch(uuid, DEFAULT_NAMESPACE_STRING, savedObjectsSerializer))
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"rootSearchFields": Array [
|
||||
"_id",
|
||||
],
|
||||
"search": "\\"${uuid}\\" \\"cases:${uuid}\\"",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('search value not changed and no rootSearchFields when search is non-uuid', () => {
|
||||
const search = 'foobar';
|
||||
const result = constructSearch(search, DEFAULT_NAMESPACE_STRING, savedObjectsSerializer);
|
||||
|
||||
expect(result).not.toHaveProperty('rootSearchFields');
|
||||
expect(result).toEqual({ search });
|
||||
});
|
||||
|
||||
it('returns undefined if search term undefined', () => {
|
||||
expect(constructSearch(undefined, DEFAULT_NAMESPACE_STRING, savedObjectsSerializer)).toEqual(
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -11,22 +11,24 @@ import deepEqual from 'fast-deep-equal';
|
|||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
|
||||
import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server';
|
||||
import type { KueryNode } from '@kbn/es-query';
|
||||
import { nodeBuilder, fromKueryExpression, escapeKuery } from '@kbn/es-query';
|
||||
import {
|
||||
isCommentRequestTypeExternalReference,
|
||||
isCommentRequestTypePersistableState,
|
||||
} from '../../common/utils/attachments';
|
||||
import { CASE_SAVED_OBJECT, NO_ASSIGNEES_FILTERING_KEYWORD } from '../../common/constants';
|
||||
|
||||
import { SEVERITY_EXTERNAL_TO_ESMODEL, STATUS_EXTERNAL_TO_ESMODEL } from '../common/constants';
|
||||
import { nodeBuilder, fromKueryExpression, escapeKuery } from '@kbn/es-query';
|
||||
import { spaceIdToNamespace } from '@kbn/spaces-plugin/server/lib/utils/namespace';
|
||||
|
||||
import type {
|
||||
CaseStatuses,
|
||||
CommentRequest,
|
||||
CaseSeverity,
|
||||
CommentRequestExternalReferenceType,
|
||||
CasesFindRequest,
|
||||
} from '../../common/api';
|
||||
import type { SavedObjectFindOptionsKueryNode } from '../common/types';
|
||||
import type { CasesFindQueryParams } from './types';
|
||||
|
||||
import {
|
||||
OWNER_FIELD,
|
||||
AlertCommentRequestRt,
|
||||
|
@ -39,7 +41,13 @@ import {
|
|||
ExternalReferenceNoSORt,
|
||||
PersistableStateAttachmentRt,
|
||||
} from '../../common/api';
|
||||
import { CASE_SAVED_OBJECT, NO_ASSIGNEES_FILTERING_KEYWORD } from '../../common/constants';
|
||||
import {
|
||||
isCommentRequestTypeExternalReference,
|
||||
isCommentRequestTypePersistableState,
|
||||
} from '../../common/utils/attachments';
|
||||
import { combineFilterWithAuthorizationFilter } from '../authorization/utils';
|
||||
import { SEVERITY_EXTERNAL_TO_ESMODEL, STATUS_EXTERNAL_TO_ESMODEL } from '../common/constants';
|
||||
import {
|
||||
getIDsAndIndicesAsArrays,
|
||||
isCommentRequestTypeAlert,
|
||||
|
@ -47,8 +55,6 @@ import {
|
|||
isCommentRequestTypeActions,
|
||||
assertUnreachable,
|
||||
} from '../common/utils';
|
||||
import type { SavedObjectFindOptionsKueryNode } from '../common/types';
|
||||
import type { CasesFindQueryParams } from './types';
|
||||
|
||||
export const decodeCommentRequest = (comment: CommentRequest) => {
|
||||
if (isCommentRequestTypeUser(comment)) {
|
||||
|
@ -537,3 +543,28 @@ export const convertSortField = (sortField: string | undefined): SortFieldCase =
|
|||
return SortFieldCase.createdAt;
|
||||
}
|
||||
};
|
||||
|
||||
export const constructSearch = (
|
||||
search: string | undefined,
|
||||
spaceId: string,
|
||||
savedObjectsSerializer: ISavedObjectsSerializer
|
||||
): Pick<CasesFindRequest, 'search' | 'rootSearchFields'> | undefined => {
|
||||
if (!search) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (uuidValidate(search)) {
|
||||
const rawId = savedObjectsSerializer.generateRawId(
|
||||
spaceIdToNamespace(spaceId),
|
||||
CASE_SAVED_OBJECT,
|
||||
search
|
||||
);
|
||||
|
||||
return {
|
||||
search: `"${search}" "${rawId}"`,
|
||||
rootSearchFields: ['_id'],
|
||||
};
|
||||
}
|
||||
|
||||
return { search };
|
||||
};
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"@kbn/ecs",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/core-saved-objects-base-server-mocks",
|
||||
"@kbn/core-saved-objects-utils-server",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { v1 as uuidv1 } from 'uuid';
|
||||
|
||||
import expect from '@kbn/expect';
|
||||
import { CASES_URL } from '@kbn/cases-plugin/common/constants';
|
||||
import {
|
||||
|
@ -345,6 +347,10 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
await createCase(supertest, postCaseReq);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await deleteAllCaseItems(es);
|
||||
});
|
||||
|
||||
it('should successfully find a case when using valid searchFields', async () => {
|
||||
const cases = await findCases({
|
||||
supertest,
|
||||
|
@ -363,6 +369,44 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(cases.total).to.be(1);
|
||||
});
|
||||
|
||||
it('should successfully find a case when using a valid uuid', async () => {
|
||||
const caseWithId = await createCase(supertest, postCaseReq);
|
||||
|
||||
const cases = await findCases({
|
||||
supertest,
|
||||
query: { searchFields: ['title', 'description'], search: caseWithId.id },
|
||||
});
|
||||
|
||||
expect(cases.total).to.be(1);
|
||||
expect(cases.cases[0].id).to.equal(caseWithId.id);
|
||||
});
|
||||
|
||||
it('should successfully find a case with a valid uuid in title', async () => {
|
||||
const uuid = uuidv1();
|
||||
await createCase(supertest, { ...postCaseReq, title: uuid });
|
||||
|
||||
const cases = await findCases({
|
||||
supertest,
|
||||
query: { searchFields: ['title', 'description'], search: uuid },
|
||||
});
|
||||
|
||||
expect(cases.total).to.be(1);
|
||||
expect(cases.cases[0].title).to.equal(uuid);
|
||||
});
|
||||
|
||||
it('should successfully find a case with a valid uuid in title', async () => {
|
||||
const uuid = uuidv1();
|
||||
await createCase(supertest, { ...postCaseReq, description: uuid });
|
||||
|
||||
const cases = await findCases({
|
||||
supertest,
|
||||
query: { searchFields: ['title', 'description'], search: uuid },
|
||||
});
|
||||
|
||||
expect(cases.total).to.be(1);
|
||||
expect(cases.cases[0].description).to.equal(uuid);
|
||||
});
|
||||
|
||||
it('should not find any cases when it does not use a wildcard and the string does not match', async () => {
|
||||
const cases = await findCases({
|
||||
supertest,
|
||||
|
|
|
@ -115,7 +115,20 @@ export function CasesTableServiceProvider(
|
|||
await testSubjects.missingOrFail('cases-table-loading', { timeout: 5000 });
|
||||
},
|
||||
|
||||
async getCaseFromTable(index: number) {
|
||||
async getCaseById(caseId: string) {
|
||||
const targetCase = await find.allByCssSelector(
|
||||
`[data-test-subj*="cases-table-row-${caseId}"`,
|
||||
100
|
||||
);
|
||||
|
||||
if (!targetCase.length) {
|
||||
throw new Error(`Cannot find case with id ${caseId} on table.`);
|
||||
}
|
||||
|
||||
return targetCase[0];
|
||||
},
|
||||
|
||||
async getCaseByIndex(index: number) {
|
||||
const rows = await find.allByCssSelector('[data-test-subj*="cases-table-row-"', 100);
|
||||
|
||||
assertCaseExists(index, rows.length);
|
||||
|
@ -361,7 +374,7 @@ export function CasesTableServiceProvider(
|
|||
|
||||
async getCaseTitle(index: number) {
|
||||
const titleElement = await (
|
||||
await this.getCaseFromTable(index)
|
||||
await this.getCaseByIndex(index)
|
||||
).findByTestSubject('case-details-link');
|
||||
|
||||
return await titleElement.getVisibleText();
|
||||
|
|
|
@ -281,6 +281,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
|
||||
describe('filtering', () => {
|
||||
const caseTitle = 'matchme';
|
||||
const caseIds: string[] = [];
|
||||
|
||||
before(async () => {
|
||||
await createUsersAndRoles(getService, users, roles);
|
||||
|
@ -288,14 +289,26 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
|
||||
const profiles = await cases.api.suggestUserProfiles({ name: 'all', owners: ['cases'] });
|
||||
|
||||
await cases.api.createCase({
|
||||
const case1 = await cases.api.createCase({
|
||||
title: caseTitle,
|
||||
tags: ['one'],
|
||||
description: 'lots of information about an incident',
|
||||
});
|
||||
await cases.api.createCase({ title: 'test2', tags: ['two'] });
|
||||
await cases.api.createCase({ title: 'test3', assignees: [{ uid: profiles[0].uid }] });
|
||||
await cases.api.createCase({ title: 'test4', assignees: [{ uid: profiles[1].uid }] });
|
||||
const case2 = await cases.api.createCase({ title: 'test2', tags: ['two'] });
|
||||
const case3 = await cases.api.createCase({
|
||||
title: case2.id,
|
||||
assignees: [{ uid: profiles[0].uid }],
|
||||
});
|
||||
const case4 = await cases.api.createCase({
|
||||
title: 'test4',
|
||||
assignees: [{ uid: profiles[1].uid }],
|
||||
description: case2.id,
|
||||
});
|
||||
|
||||
caseIds.push(case1.id);
|
||||
caseIds.push(case2.id);
|
||||
caseIds.push(case3.id);
|
||||
caseIds.push(case4.id);
|
||||
|
||||
await header.waitUntilLoadingHasFinished();
|
||||
await cases.casesTable.waitForCasesToBeListed();
|
||||
|
@ -329,6 +342,34 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
await cases.casesTable.validateCasesTableHasNthRows(4);
|
||||
});
|
||||
|
||||
it('filters cases from the list using an id search', async () => {
|
||||
await testSubjects.missingOrFail('cases-table-loading', { timeout: 5000 });
|
||||
|
||||
const input = await testSubjects.find('search-cases');
|
||||
await input.type(caseIds[0]);
|
||||
await input.pressKeys(browser.keys.ENTER);
|
||||
|
||||
await cases.casesTable.validateCasesTableHasNthRows(1);
|
||||
await cases.casesTable.getCaseById(caseIds[0]);
|
||||
await testSubjects.click('clearSearchButton');
|
||||
await cases.casesTable.validateCasesTableHasNthRows(4);
|
||||
});
|
||||
|
||||
it('id search also matches title and description', async () => {
|
||||
await testSubjects.missingOrFail('cases-table-loading', { timeout: 5000 });
|
||||
|
||||
const input = await testSubjects.find('search-cases');
|
||||
await input.type(caseIds[1]);
|
||||
await input.pressKeys(browser.keys.ENTER);
|
||||
|
||||
await cases.casesTable.validateCasesTableHasNthRows(3);
|
||||
await cases.casesTable.getCaseById(caseIds[1]); // id match
|
||||
await cases.casesTable.getCaseById(caseIds[2]); // title match
|
||||
await cases.casesTable.getCaseById(caseIds[3]); // description match
|
||||
await testSubjects.click('clearSearchButton');
|
||||
await cases.casesTable.validateCasesTableHasNthRows(4);
|
||||
});
|
||||
|
||||
it('only shows cases with a wildcard query "test*" matching the title', async () => {
|
||||
await testSubjects.missingOrFail('cases-table-loading', { timeout: 5000 });
|
||||
|
||||
|
@ -336,7 +377,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
await input.type('test*');
|
||||
await input.pressKeys(browser.keys.ENTER);
|
||||
|
||||
await cases.casesTable.validateCasesTableHasNthRows(3);
|
||||
await cases.casesTable.validateCasesTableHasNthRows(2);
|
||||
await testSubjects.click('clearSearchButton');
|
||||
await cases.casesTable.validateCasesTableHasNthRows(4);
|
||||
});
|
||||
|
@ -381,7 +422,7 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
await cases.casesTable.filterByTag('one');
|
||||
await cases.casesTable.refreshTable();
|
||||
await cases.casesTable.validateCasesTableHasNthRows(1);
|
||||
const row = await cases.casesTable.getCaseFromTable(0);
|
||||
const row = await cases.casesTable.getCaseByIndex(0);
|
||||
const tags = await row.findByTestSubject('case-table-column-tags-one');
|
||||
expect(await tags.getVisibleText()).to.be('one');
|
||||
});
|
||||
|
@ -426,11 +467,11 @@ export default ({ getPageObject, getService }: FtrProviderContext) => {
|
|||
await cases.casesTable.validateCasesTableHasNthRows(2);
|
||||
|
||||
const firstCaseTitle = await (
|
||||
await cases.casesTable.getCaseFromTable(0)
|
||||
await cases.casesTable.getCaseByIndex(0)
|
||||
).findByTestSubject('case-details-link');
|
||||
|
||||
const secondCaseTitle = await (
|
||||
await cases.casesTable.getCaseFromTable(1)
|
||||
await cases.casesTable.getCaseByIndex(1)
|
||||
).findByTestSubject('case-details-link');
|
||||
|
||||
expect(await firstCaseTitle.getVisibleText()).be('test2');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue