diff --git a/docs/api/cases/cases-api-find-cases.asciidoc b/docs/api/cases/cases-api-find-cases.asciidoc index 68e620aece7b6..b6e87ad502d21 100644 --- a/docs/api/cases/cases-api-find-cases.asciidoc +++ b/docs/api/cases/cases-api-find-cases.asciidoc @@ -34,6 +34,9 @@ Defaults to `OR`. `fields`:: (Optional, array of strings) The fields in the entity to return in the response. +`from`:: +(Optional, string) Returns only cases that were created after a specific date. The date must be specified as a <> data range or date match expression. preview:[] + `owner`:: (Optional, string or array of strings) A filter to limit the retrieved cases to a specific set of applications. Valid values are: `cases`, `observability`, @@ -78,6 +81,9 @@ Defaults to `desc`. `tags`:: (Optional, string or array of strings) Filters the returned cases by tags. +`to`:: +(Optional, string) Returns only cases that were created before a specific date. The date must be specified as a <> data range or date match expression. preview:[] + === Response code `200`:: diff --git a/x-pack/plugins/cases/common/api/cases/case.ts b/x-pack/plugins/cases/common/api/cases/case.ts index 1bc14fa8d3ab9..251c02d931fcd 100644 --- a/x-pack/plugins/cases/common/api/cases/case.ts +++ b/x-pack/plugins/cases/common/api/cases/case.ts @@ -154,6 +154,10 @@ export const CasesFindRequestRt = rt.partial({ * The fields in the entity to return in the response */ fields: rt.union([rt.array(rt.string), rt.string]), + /** + * A KQL date. If used all cases created after (gte) the from date will be returned + */ + from: rt.string, /** * The page of objects to return */ @@ -180,11 +184,17 @@ export const CasesFindRequestRt = rt.partial({ * The order to sort by */ sortOrder: rt.union([rt.literal('desc'), rt.literal('asc')]), + + /** + * A KQL date. If used all cases created before (lte) the to date will be returned. + */ + to: rt.string, /** * The owner(s) to filter by. The user making the request must have privileges to retrieve cases of that * ownership or they will be ignored. If no owner is included, then all ownership types will be included in the response * that the user has access to. */ + owner: rt.union([rt.array(rt.string), rt.string]), }); diff --git a/x-pack/plugins/cases/common/api/cases/status.ts b/x-pack/plugins/cases/common/api/cases/status.ts index d37e68007a21d..fa3b584f07286 100644 --- a/x-pack/plugins/cases/common/api/cases/status.ts +++ b/x-pack/plugins/cases/common/api/cases/status.ts @@ -28,6 +28,14 @@ export const CasesStatusResponseRt = rt.type({ }); export const CasesStatusRequestRt = rt.partial({ + /** + * A KQL date. If used all cases created after (gte) the from date will be returned + */ + from: rt.string, + /** + * A KQL date. If used all cases created before (lte) the to date will be returned. + */ + to: rt.string, /** * The owner of the cases to retrieve the status stats from. If no owner is provided the stats for all cases * that the user has access to will be returned. diff --git a/x-pack/plugins/cases/common/constants.ts b/x-pack/plugins/cases/common/constants.ts index 122234d473d13..a1ac829b33cce 100644 --- a/x-pack/plugins/cases/common/constants.ts +++ b/x-pack/plugins/cases/common/constants.ts @@ -51,6 +51,7 @@ export const SAVED_OBJECT_TYPES = [ */ export const CASES_URL = '/api/cases' as const; +export const CASE_FIND_URL = `${CASES_URL}/_find` as const; export const CASE_DETAILS_URL = `${CASES_URL}/{case_id}` as const; export const CASE_CONFIGURE_URL = `${CASES_URL}/configure` as const; export const CASE_CONFIGURE_DETAILS_URL = `${CASES_URL}/configure/{configuration_id}` as const; diff --git a/x-pack/plugins/cases/public/client/api/index.test.ts b/x-pack/plugins/cases/public/client/api/index.test.ts index 6e52649d1e680..9dd5245c03dd4 100644 --- a/x-pack/plugins/cases/public/client/api/index.test.ts +++ b/x-pack/plugins/cases/public/client/api/index.test.ts @@ -7,16 +7,16 @@ import { httpServiceMock } from '../../../../../../src/core/public/mocks'; import { createClientAPI } from '.'; +import { allCases, casesStatus } from '../../containers/mock'; describe('createClientAPI', () => { - const http = httpServiceMock.createStartContract({ basePath: '' }); - const api = createClientAPI({ http }); - beforeEach(() => { jest.clearAllMocks(); }); describe('getRelatedCases', () => { + const http = httpServiceMock.createStartContract({ basePath: '' }); + const api = createClientAPI({ http }); const res = [ { id: 'test-id', @@ -43,4 +43,40 @@ describe('createClientAPI', () => { }); }); }); + + describe('cases', () => { + describe('find', () => { + const http = httpServiceMock.createStartContract({ basePath: '' }); + const api = createClientAPI({ http }); + http.get.mockResolvedValue(allCases); + + it('should return the correct response', async () => { + expect(await api.cases.find({ from: 'now-1d' })).toEqual(allCases); + }); + + it('should have been called with the correct path', async () => { + await api.cases.find({ perPage: 10 }); + expect(http.get).toHaveBeenCalledWith('/api/cases/_find', { + query: { perPage: 10 }, + }); + }); + }); + + describe('getAllCasesMetrics', () => { + const http = httpServiceMock.createStartContract({ basePath: '' }); + const api = createClientAPI({ http }); + http.get.mockResolvedValue(casesStatus); + + it('should return the correct response', async () => { + expect(await api.cases.getAllCasesMetrics({ from: 'now-1d' })).toEqual(casesStatus); + }); + + it('should have been called with the correct path', async () => { + await api.cases.getAllCasesMetrics({ from: 'now-1d' }); + expect(http.get).toHaveBeenCalledWith('/api/cases/status', { + query: { from: 'now-1d' }, + }); + }); + }); + }); }); diff --git a/x-pack/plugins/cases/public/client/api/index.ts b/x-pack/plugins/cases/public/client/api/index.ts index 0f98812649276..b35fff84fdb09 100644 --- a/x-pack/plugins/cases/public/client/api/index.ts +++ b/x-pack/plugins/cases/public/client/api/index.ts @@ -6,7 +6,16 @@ */ import { HttpStart } from 'kibana/public'; -import { CasesByAlertId, CasesByAlertIDRequest, getCasesFromAlertsUrl } from '../../../common/api'; +import { + CasesByAlertId, + CasesByAlertIDRequest, + CasesFindRequest, + getCasesFromAlertsUrl, + CasesResponse, + CasesStatusRequest, + CasesStatusResponse, +} from '../../../common/api'; +import { CASE_FIND_URL, CASE_STATUS_URL } from '../../../common/constants'; import { CasesUiStart } from '../../types'; export const createClientAPI = ({ http }: { http: HttpStart }): CasesUiStart['api'] => { @@ -16,5 +25,11 @@ export const createClientAPI = ({ http }: { http: HttpStart }): CasesUiStart['ap query: CasesByAlertIDRequest ): Promise => http.get(getCasesFromAlertsUrl(alertId), { query }), + cases: { + find: (query: CasesFindRequest): Promise => + http.get(CASE_FIND_URL, { query }), + getAllCasesMetrics: (query: CasesStatusRequest): Promise => + http.get(CASE_STATUS_URL, { query }), + }, }; }; diff --git a/x-pack/plugins/cases/public/mocks.ts b/x-pack/plugins/cases/public/mocks.ts index fd34e9ae4940b..f8c0eaaaef7de 100644 --- a/x-pack/plugins/cases/public/mocks.ts +++ b/x-pack/plugins/cases/public/mocks.ts @@ -10,6 +10,7 @@ import { CasesUiStart } from './types'; const apiMock: jest.Mocked = { getRelatedCases: jest.fn(), + cases: { find: jest.fn(), getAllCasesMetrics: jest.fn() }, }; const uiMock: jest.Mocked = { diff --git a/x-pack/plugins/cases/public/types.ts b/x-pack/plugins/cases/public/types.ts index 6013a27830931..63c767f412456 100644 --- a/x-pack/plugins/cases/public/types.ts +++ b/x-pack/plugins/cases/public/types.ts @@ -23,6 +23,10 @@ import type { TriggersAndActionsUIPublicPluginStart as TriggersActionsStart } fr import { CasesByAlertId, CasesByAlertIDRequest, + CasesFindRequest, + CasesResponse, + CasesStatusRequest, + CasesStatusResponse, CommentRequestAlertType, CommentRequestUserType, } from '../common/api'; @@ -74,6 +78,10 @@ export interface RenderAppProps { export interface CasesUiStart { api: { getRelatedCases: (alertId: string, query: CasesByAlertIDRequest) => Promise; + cases: { + find: (query: CasesFindRequest) => Promise; + getAllCasesMetrics: (query: CasesStatusRequest) => Promise; + }; }; ui: { /** diff --git a/x-pack/plugins/cases/server/client/cases/find.ts b/x-pack/plugins/cases/server/client/cases/find.ts index 26ac4603c51e5..b5d3cee05ced6 100644 --- a/x-pack/plugins/cases/server/client/cases/find.ts +++ b/x-pack/plugins/cases/server/client/cases/find.ts @@ -54,6 +54,8 @@ export const find = async ( sortByField: queryParams.sortField, status: queryParams.status, owner: queryParams.owner, + from: queryParams.from, + to: queryParams.to, }; const statusStatsOptions = constructQueryOptions({ diff --git a/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts index 82c3a52a10d63..e02f882820fa7 100644 --- a/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts +++ b/x-pack/plugins/cases/server/client/metrics/get_cases_metrics.ts @@ -41,6 +41,8 @@ export async function getStatusTotalsByType( const options = constructQueryOptions({ owner: queryParams.owner, + from: queryParams.from, + to: queryParams.to, authorizationFilter, }); diff --git a/x-pack/plugins/cases/server/client/utils.test.ts b/x-pack/plugins/cases/server/client/utils.test.ts index 4db98f7618715..0210ce9eaf3d4 100644 --- a/x-pack/plugins/cases/server/client/utils.test.ts +++ b/x-pack/plugins/cases/server/client/utils.test.ts @@ -8,31 +8,32 @@ import { CaseConnector, ConnectorTypes } from '../../common/api'; import { newCase } from '../routes/api/__mocks__/request_responses'; import { transformNewCase } from '../common/utils'; -import { sortToSnake } from './utils'; +import { buildRangeFilter, sortToSnake } from './utils'; +import { toElasticsearchQuery } from '@kbn/es-query'; describe('utils', () => { describe('sortToSnake', () => { - it('it transforms status correctly', () => { + it('transforms status correctly', () => { expect(sortToSnake('status')).toBe('status'); }); - it('it transforms createdAt correctly', () => { + it('transforms createdAt correctly', () => { expect(sortToSnake('createdAt')).toBe('created_at'); }); - it('it transforms created_at correctly', () => { + it('transforms created_at correctly', () => { expect(sortToSnake('created_at')).toBe('created_at'); }); - it('it transforms closedAt correctly', () => { + it('transforms closedAt correctly', () => { expect(sortToSnake('closedAt')).toBe('closed_at'); }); - it('it transforms closed_at correctly', () => { + it('transforms closed_at correctly', () => { expect(sortToSnake('closed_at')).toBe('closed_at'); }); - it('it transforms default correctly', () => { + it('transforms default correctly', () => { expect(sortToSnake('not-exist')).toBe('created_at'); }); }); @@ -103,4 +104,154 @@ describe('utils', () => { `); }); }); + + describe('buildRangeFilter', () => { + it('returns undefined if both the from and or are undefined', () => { + const node = buildRangeFilter({}); + expect(node).toBeFalsy(); + }); + + it('returns undefined if both the from and or are null', () => { + // @ts-expect-error + const node = buildRangeFilter({ from: null, to: null }); + expect(node).toBeFalsy(); + }); + + it('returns undefined if the from is malformed', () => { + expect(() => buildRangeFilter({ from: '<' })).toThrowError( + 'Invalid "from" and/or "to" query parameters' + ); + }); + + it('returns undefined if the to is malformed', () => { + expect(() => buildRangeFilter({ to: '<' })).toThrowError( + 'Invalid "from" and/or "to" query parameters' + ); + }); + + it('creates a range filter with only the from correctly', () => { + const node = buildRangeFilter({ from: 'now-1M' }); + expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "cases.attributes.created_at": Object { + "gte": "now-1M", + }, + }, + }, + ], + }, + } + `); + }); + + it('creates a range filter with only the to correctly', () => { + const node = buildRangeFilter({ to: 'now' }); + expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "cases.attributes.created_at": Object { + "lte": "now", + }, + }, + }, + ], + }, + } + `); + }); + + it('creates a range filter correctly', () => { + const node = buildRangeFilter({ from: 'now-1M', to: 'now' }); + expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "cases.attributes.created_at": Object { + "gte": "now-1M", + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "cases.attributes.created_at": Object { + "lte": "now", + }, + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + + it('creates a range filter with different field and saved object type provided', () => { + const node = buildRangeFilter({ + from: 'now-1M', + to: 'now', + field: 'test', + savedObjectType: 'test-type', + }); + expect(toElasticsearchQuery(node!)).toMatchInlineSnapshot(` + Object { + "bool": Object { + "filter": Array [ + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "test-type.attributes.test": Object { + "gte": "now-1M", + }, + }, + }, + ], + }, + }, + Object { + "bool": Object { + "minimum_should_match": 1, + "should": Array [ + Object { + "range": Object { + "test-type.attributes.test": Object { + "lte": "now", + }, + }, + }, + ], + }, + }, + ], + }, + } + `); + }); + }); }); diff --git a/x-pack/plugins/cases/server/client/utils.ts b/x-pack/plugins/cases/server/client/utils.ts index 24f661b5f41fe..0c78118e3285b 100644 --- a/x-pack/plugins/cases/server/client/utils.ts +++ b/x-pack/plugins/cases/server/client/utils.ts @@ -183,6 +183,35 @@ export function stringToKueryNode(expression?: string): KueryNode | undefined { return fromKueryExpression(expression); } +export const buildRangeFilter = ({ + from, + to, + field = 'created_at', + savedObjectType = CASE_SAVED_OBJECT, +}: { + from?: string; + to?: string; + field?: string; + savedObjectType?: string; +}): KueryNode | undefined => { + if (from == null && to == null) { + return; + } + + try { + const fromKQL = from != null ? `${savedObjectType}.attributes.${field} >= ${from}` : undefined; + const toKQL = to != null ? `${savedObjectType}.attributes.${field} <= ${to}` : undefined; + + const rangeKQLQuery = `${fromKQL != null ? fromKQL : ''} ${ + fromKQL != null && toKQL != null ? 'and' : '' + } ${toKQL != null ? toKQL : ''}`; + + return stringToKueryNode(rangeKQLQuery); + } catch (error) { + throw badRequest('Invalid "from" and/or "to" query parameters'); + } +}; + export const constructQueryOptions = ({ tags, reporters, @@ -190,6 +219,8 @@ export const constructQueryOptions = ({ sortByField, owner, authorizationFilter, + from, + to, }: { tags?: string | string[]; reporters?: string | string[]; @@ -197,6 +228,8 @@ export const constructQueryOptions = ({ sortByField?: string; owner?: string | string[]; authorizationFilter?: KueryNode; + from?: string; + to?: string; }): SavedObjectFindOptionsKueryNode => { const kueryNodeExists = (filter: KueryNode | null | undefined): filter is KueryNode => filter != null; @@ -211,10 +244,15 @@ export const constructQueryOptions = ({ const ownerFilter = buildFilter({ filters: owner ?? [], field: OWNER_FIELD, operator: 'or' }); const statusFilter = status != null ? addStatusFilter({ status }) : undefined; - - const filters: KueryNode[] = [statusFilter, tagsFilter, reportersFilter, ownerFilter].filter( - kueryNodeExists - ); + const rangeFilter = buildRangeFilter({ from, to }); + + const filters: KueryNode[] = [ + statusFilter, + tagsFilter, + reportersFilter, + rangeFilter, + ownerFilter, + ].filter(kueryNodeExists); const caseFilters = filters.length > 1 ? nodeBuilder.and(filters) : filters[0]; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts index 89f6f96aeb7d1..5cf7c4947c23c 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/find_cases.ts @@ -52,6 +52,7 @@ export default ({ getService }: FtrProviderContext): void => { const es = getService('es'); const supertestWithoutAuth = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); describe('find_cases', () => { describe('basic tests', () => { @@ -478,6 +479,53 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + describe('range queries', () => { + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json' + ); + await deleteAllCaseItems(es); + }); + + it('returns all cases without a range filter', async () => { + const EXPECTED_CASES = 3; + const cases = await findCases({ supertest }); + + expect(cases.total).to.be(EXPECTED_CASES); + expect(cases.count_open_cases).to.be(EXPECTED_CASES); + expect(cases.cases.length).to.be(EXPECTED_CASES); + }); + + it('respects the range parameters', async () => { + const queries = [ + { expectedCases: 2, query: { from: '2022-03-16' } }, + { expectedCases: 2, query: { to: '2022-03-21' } }, + { expectedCases: 2, query: { from: '2022-03-15', to: '2022-03-21' } }, + ]; + + for (const query of queries) { + const cases = await findCases({ + supertest, + query: query.query, + }); + + expect(cases.total).to.be(query.expectedCases); + expect(cases.count_open_cases).to.be(query.expectedCases); + expect(cases.cases.length).to.be(query.expectedCases); + } + }); + + it('returns a bad request on malformed parameter', async () => { + await findCases({ supertest, query: { from: '<' }, expectedHttpCode: 400 }); + }); + }); + describe('rbac', () => { afterEach(async () => { await deleteAllCaseItems(es); @@ -717,6 +765,40 @@ export default ({ getService }: FtrProviderContext): void => { // Only security solution cases are being returned ensureSavedObjectIsAuthorized(res.cases, 1, ['securitySolutionFixture']); }); + + describe('range queries', () => { + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json', + { space: 'space1' } + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json', + { space: 'space1' } + ); + await deleteAllCaseItems(es); + }); + + it('should respect the owner filter when using range queries', async () => { + const res = await findCases({ + supertest: supertestWithoutAuth, + query: { + from: '2022-03-15', + to: '2022-03-21', + }, + auth: { + user: secOnly, + space: 'space1', + }, + }); + + // Only security solution cases are being returned + ensureSavedObjectIsAuthorized(res.cases, 1, ['securitySolutionFixture']); + }); + }); }); }); }; diff --git a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts index c170dc0ff3ccd..27433c09cd2e0 100644 --- a/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts +++ b/x-pack/test/cases_api_integration/security_and_spaces/tests/common/cases/status/get_status.ts @@ -34,12 +34,9 @@ import { assertWarningHeader } from '../../../../../common/lib/validation'; export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const kibanaServer = getService('kibanaServer'); describe('get_status', () => { - afterEach(async () => { - await deleteAllCaseItems(es); - }); - it('should return case statuses', async () => { const [, inProgressCase, postedCase] = await Promise.all([ createCase(supertest, postCaseReq), @@ -74,7 +71,58 @@ export default ({ getService }: FtrProviderContext): void => { }); }); + describe('range queries', () => { + before(async () => { + await deleteAllCaseItems(es); + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json' + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json' + ); + await deleteAllCaseItems(es); + }); + + it('returns all cases without a range filter', async () => { + const statuses = await getAllCasesStatuses({ supertest }); + + expect(statuses).to.eql({ + count_open_cases: 3, + count_closed_cases: 0, + count_in_progress_cases: 0, + }); + }); + + it('respects the range parameters', async () => { + const queries = [ + { expectedCases: 2, query: { from: '2022-03-16' } }, + { expectedCases: 2, query: { to: '2022-03-21' } }, + { expectedCases: 2, query: { from: '2022-03-15', to: '2022-03-21' } }, + ]; + + for (const query of queries) { + const statuses = await getAllCasesStatuses({ supertest, query: query.query }); + expect(statuses).to.eql({ + count_open_cases: query.expectedCases, + count_closed_cases: 0, + count_in_progress_cases: 0, + }); + } + }); + + it('returns a bad request on malformed parameter', async () => { + await getAllCasesStatuses({ supertest, query: { from: '<' }, expectedHttpCode: 400 }); + }); + }); + describe('rbac', () => { + afterEach(async () => { + await deleteAllCaseItems(es); + }); + const supertestWithoutAuth = getService('supertestWithoutAuth'); it('should return the correct status stats', async () => { @@ -183,6 +231,43 @@ export default ({ getService }: FtrProviderContext): void => { }); }); } + + describe('range queries', () => { + before(async () => { + await kibanaServer.importExport.load( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json', + { space: 'space1' } + ); + }); + + after(async () => { + await kibanaServer.importExport.unload( + 'x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json', + { space: 'space1' } + ); + await deleteAllCaseItems(es); + }); + + it('should respect the owner filter when using range queries', async () => { + const res = await getAllCasesStatuses({ + supertest: supertestWithoutAuth, + query: { + from: '2022-03-15', + to: '2022-03-21', + }, + auth: { + user: secOnly, + space: 'space1', + }, + }); + + expect(res).to.eql({ + count_open_cases: 1, + count_closed_cases: 0, + count_in_progress_cases: 0, + }); + }); + }); }); describe('deprecations', () => { diff --git a/x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json b/x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json new file mode 100644 index 0000000000000..ffdfef08735fd --- /dev/null +++ b/x-pack/test/functional/fixtures/kbn_archiver/cases/8.2.0/cases_various_dates.json @@ -0,0 +1,125 @@ +{ + "attributes": { + "closed_at": null, + "closed_by": null, + "connector": { + "fields": null, + "name": "none", + "type": ".none" + }, + "created_at": "2022-03-15T10:16:56.252Z", + "created_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + }, + "description": "test", + "external_service": null, + "owner": "securitySolutionFixture", + "settings": { + "syncAlerts": false + }, + "status": "open", + "tags": [], + "title": "stack", + "updated_at": "2022-03-29T10:33:09.754Z", + "updated_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + } + }, + "coreMigrationVersion": "8.2.0", + "id": "1537b380-a512-11ec-b94f-85999e89e434", + "migrationVersion": { + "cases": "8.1.0" + }, + "references": [], + "type": "cases", + "updated_at": "2022-03-29T10:33:09.754Z", + "version": "WzE2OTYyNCwxNF0=" +} + +{ + "attributes": { + "closed_at": null, + "closed_by": null, + "connector": { + "fields": null, + "name": "none", + "type": ".none" + }, + "created_at": "2022-03-20T10:16:56.252Z", + "created_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + }, + "description": "test 2", + "external_service": null, + "owner": "observabilityFixture", + "settings": { + "syncAlerts": false + }, + "status": "open", + "tags": [], + "title": "stack", + "updated_at": "2022-03-29T10:33:09.754Z", + "updated_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + } + }, + "coreMigrationVersion": "8.2.0", + "id": "3537b580-a512-11ec-b94f-85979e89e434", + "migrationVersion": { + "cases": "8.1.0" + }, + "references": [], + "type": "cases", + "updated_at": "2022-03-29T10:33:09.754Z", + "version": "WzE2OTYyNCwxNF0=" +} + +{ + "attributes": { + "closed_at": null, + "closed_by": null, + "connector": { + "fields": null, + "name": "none", + "type": ".none" + }, + "created_at": "2022-03-25T10:16:56.252Z", + "created_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + }, + "description": "test 2", + "external_service": null, + "owner": "securitySolutionFixture", + "settings": { + "syncAlerts": false + }, + "status": "open", + "tags": [], + "title": "stack", + "updated_at": "2022-03-29T10:33:09.754Z", + "updated_by": { + "email": "", + "full_name": "", + "username": "cnasikas" + } + }, + "coreMigrationVersion": "8.2.0", + "id": "4537b380-a512-11ec-b92f-859b9e89e434", + "migrationVersion": { + "cases": "8.1.0" + }, + "references": [], + "type": "cases", + "updated_at": "2022-03-29T10:33:09.754Z", + "version": "WzE2OTYyNCwxNF0=" +}