-
Notifications
You must be signed in to change notification settings - Fork 8.6k
[Scout] Move Home / Sample Data API from FTR to Scout #271695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
paulinashakirova
wants to merge
8
commits into
elastic:main
Choose a base branch
from
paulinashakirova:scout/home-sample-data-api-migration-v3
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
302ee70
Add Playwright configuration and sample data tests for flights dataset
paulinashakirova e613a8d
Update sample data test comment for clarity
paulinashakirova 6e0cc8e
Merge branch 'main' into scout/home-sample-data-api-migration-v3
paulinashakirova 505ea8e
Enhance Playwright configuration and update sample data test comments
paulinashakirova 6ba9d66
clean up
paulinashakirova a3a6488
improve constants
paulinashakirova 526c6cb
remove comment
paulinashakirova 0798f89
improvements
paulinashakirova File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
src/platform/plugins/shared/home/test/scout/api/fixtures/constants.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import { X_ELASTIC_INTERNAL_ORIGIN_REQUEST } from '@kbn/core-http-common'; | ||
|
|
||
| export const FLIGHTS_DATASET_ID = 'flights'; | ||
|
|
||
| export const FLIGHTS_ES_INDEX = 'kibana_sample_data_flights'; | ||
|
|
||
| // @see src/platform/plugins/shared/home/server/services/sample_data/data_sets/flights/index.ts | ||
| export const FLIGHTS_OVERVIEW_DASHBOARD_ID = '7adfa750-4c81-11e8-b3d7-01146121b73d'; | ||
|
|
||
| export const COMMON_HEADERS = { | ||
| 'kbn-xsrf': 'kibana', | ||
| [X_ELASTIC_INTERNAL_ORIGIN_REQUEST]: 'kibana', | ||
| } as const; | ||
|
|
||
| // Earliest-to-latest timestamp delta in the flights dataset; used to verify timestamps shift relative to install time. | ||
| // @see src/platform/plugins/shared/home/server/services/sample_data/data_sets/flights/flights.json.gz | ||
| export const FLIGHTS_DATA_TIME_SPAN_MS = | ||
| new Date('2018-02-11T14:54:34').getTime() - new Date('2018-01-01T00:00:00').getTime(); | ||
|
paulinashakirova marked this conversation as resolved.
|
||
21 changes: 21 additions & 0 deletions
21
src/platform/plugins/shared/home/test/scout/api/fixtures/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import type { ScoutTestFixtures, ScoutWorkerFixtures } from '@kbn/scout'; | ||
| import { apiTest as baseApiTest } from '@kbn/scout'; | ||
|
|
||
| export const apiTest = baseApiTest.extend<ScoutTestFixtures, ScoutWorkerFixtures>({}); | ||
|
|
||
| export { | ||
| FLIGHTS_DATASET_ID, | ||
| FLIGHTS_ES_INDEX, | ||
| FLIGHTS_OVERVIEW_DASHBOARD_ID, | ||
| COMMON_HEADERS, | ||
| FLIGHTS_DATA_TIME_SPAN_MS, | ||
| } from './constants'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
218 changes: 218 additions & 0 deletions
218
src/platform/plugins/shared/home/test/scout/api/tests/sample_data.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| /* | ||
| * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side | ||
| * Public License v 1"; you may not use this file except in compliance with, at | ||
| * your election, the "Elastic License 2.0", the "GNU Affero General Public | ||
| * License v3.0 only", or the "Server Side Public License, v 1". | ||
| */ | ||
|
|
||
| import { randomUUID } from 'crypto'; | ||
| import type { RoleApiCredentials } from '@kbn/scout'; | ||
| import { tags } from '@kbn/scout'; | ||
| import { expect } from '@kbn/scout/api'; | ||
| import { | ||
| apiTest, | ||
| FLIGHTS_DATASET_ID, | ||
| FLIGHTS_ES_INDEX, | ||
| FLIGHTS_OVERVIEW_DASHBOARD_ID, | ||
| FLIGHTS_DATA_TIME_SPAN_MS, | ||
| COMMON_HEADERS, | ||
| } from '../fixtures'; | ||
|
|
||
| // UUID suffix keeps saved objects isolated to their own space across concurrent CI runs. | ||
| const TEST_SPACE_ID = `scout-sd-${randomUUID().slice(0, 8)}`; | ||
|
|
||
| const sampleDataApiPath = (space: string) => `/s/${space}/api/sample_data`; | ||
|
|
||
| apiTest.describe('sample data API', { tag: tags.stateful.classic }, () => { | ||
| let credentials: RoleApiCredentials; | ||
|
|
||
| apiTest.beforeAll(async ({ requestAuth, kbnClient }) => { | ||
| credentials = await requestAuth.getApiKeyForAdmin(); | ||
|
|
||
| await kbnClient.spaces.create({ id: TEST_SPACE_ID, name: 'Scout sample data test space' }); | ||
| }); | ||
|
|
||
| apiTest.afterAll(async ({ kbnClient }) => { | ||
| await kbnClient.spaces.delete(TEST_SPACE_ID); | ||
| }); | ||
|
paulinashakirova marked this conversation as resolved.
|
||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Negative path | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| apiTest('returns 404 for an unknown dataset ID', async ({ apiClient }) => { | ||
| const response = await apiClient.post('/api/sample_data/xxxx', { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| }); | ||
|
paulinashakirova marked this conversation as resolved.
|
||
|
|
||
| expect(response).toHaveStatusCode(404); | ||
| }); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Default space: full lifecycle | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| apiTest( | ||
| 'default space: list, install, verify timestamps and counts, uninstall', | ||
| async ({ apiClient, esClient }) => { | ||
| const apiPath = sampleDataApiPath('default'); | ||
|
|
||
| await apiTest.step( | ||
| 'install returns 200 with correct ES index and saved object counts', | ||
| async () => { | ||
| const response = await apiClient.post(`${apiPath}/${FLIGHTS_DATASET_ID}`, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| expect(response.body).toStrictEqual({ | ||
| elasticsearchIndicesCreated: { [FLIGHTS_ES_INDEX]: 13014 }, | ||
| kibanaSavedObjectsLoaded: 7, | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| await apiTest.step( | ||
| 'ES index contains timestamps relative to current time (no ?now= param)', | ||
| async () => { | ||
| const result = await esClient.search<{ timestamp: string }>({ | ||
| index: FLIGHTS_ES_INDEX, | ||
| sort: [{ timestamp: { order: 'desc' } }], | ||
| }); | ||
| const latestTimestampMs = Date.parse(result.hits.hits[0]._source!.timestamp); | ||
| const delta = Math.abs(Date.now() - latestTimestampMs); | ||
| expect(delta).toBeLessThan(FLIGHTS_DATA_TIME_SPAN_MS); | ||
| } | ||
| ); | ||
|
|
||
| await apiTest.step( | ||
| 'ES index timestamps shift to match the ?now= parameter on reinstall', | ||
| async () => { | ||
| const nowString = '2000-01-01T00:00:00'; | ||
| const reinstallResponse = await apiClient.post( | ||
| `${apiPath}/${FLIGHTS_DATASET_ID}?now=${encodeURIComponent(nowString)}`, | ||
| { headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader } } | ||
| ); | ||
|
paulinashakirova marked this conversation as resolved.
paulinashakirova marked this conversation as resolved.
|
||
| expect(reinstallResponse).toHaveStatusCode(200); | ||
| const result = await esClient.search<{ timestamp: string }>({ | ||
| index: FLIGHTS_ES_INDEX, | ||
| sort: [{ timestamp: { order: 'desc' } }], | ||
| }); | ||
| const latestTimestampMs = Date.parse(result.hits.hits[0]._source!.timestamp); | ||
| const delta = Math.abs(Date.parse(nowString) - latestTimestampMs); | ||
| expect(delta).toBeLessThan(FLIGHTS_DATA_TIME_SPAN_MS); | ||
| } | ||
| ); | ||
|
|
||
| await apiTest.step('list returns installed status after install', async () => { | ||
| const response = await apiClient.get(apiPath, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| const flights = findFlightsDataset(response.body); | ||
| expect(flights.status).toBe('installed'); | ||
| expect(flights.overviewDashboard).toBe(FLIGHTS_OVERVIEW_DASHBOARD_ID); | ||
| }); | ||
|
|
||
| await apiTest.step('uninstall returns 204', async () => { | ||
| const response = await apiClient.delete(`${apiPath}/${FLIGHTS_DATASET_ID}`, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| }); | ||
| expect(response).toHaveStatusCode(204); | ||
| }); | ||
|
|
||
| await apiTest.step('ES index is removed after uninstall', async () => { | ||
| const indexExists = await esClient.indices.exists({ index: FLIGHTS_ES_INDEX }); | ||
| expect(indexExists).toBe(false); | ||
| }); | ||
|
|
||
| await apiTest.step('list returns not_installed status after uninstall', async () => { | ||
| const response = await apiClient.get(apiPath, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| const flights = findFlightsDataset(response.body); | ||
| expect(flights.status).toBe('not_installed'); | ||
| expect(flights.overviewDashboard).toBe(FLIGHTS_OVERVIEW_DASHBOARD_ID); | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Non-default space: ID preservation + full lifecycle | ||
| // --------------------------------------------------------------------------- | ||
|
paulinashakirova marked this conversation as resolved.
|
||
|
|
||
| apiTest( | ||
| 'non-default space: install and uninstall work correctly; saved object IDs are preserved', | ||
| async ({ apiClient, esClient }) => { | ||
| const apiPath = sampleDataApiPath(TEST_SPACE_ID); | ||
|
paulinashakirova marked this conversation as resolved.
|
||
|
|
||
| await apiTest.step( | ||
| 'install returns 200 with correct ES index and saved object counts', | ||
| async () => { | ||
| const response = await apiClient.post(`${apiPath}/${FLIGHTS_DATASET_ID}`, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| expect(response.body).toStrictEqual({ | ||
| elasticsearchIndicesCreated: { [FLIGHTS_ES_INDEX]: 13014 }, | ||
| kibanaSavedObjectsLoaded: 7, | ||
| }); | ||
| } | ||
| ); | ||
|
|
||
| await apiTest.step( | ||
| 'list shows installed status and the canonical overviewDashboard ID in non-default space', | ||
| async () => { | ||
| const response = await apiClient.get(apiPath, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| const flights = findFlightsDataset(response.body); | ||
| expect(flights.status).toBe('installed'); | ||
| // The installer uses createNewCopies: false, so saved object IDs are identical across all spaces. | ||
| expect(flights.overviewDashboard).toBe(FLIGHTS_OVERVIEW_DASHBOARD_ID); | ||
| } | ||
| ); | ||
|
|
||
| await apiTest.step('uninstall returns 204', async () => { | ||
| const response = await apiClient.delete(`${apiPath}/${FLIGHTS_DATASET_ID}`, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| }); | ||
| expect(response).toHaveStatusCode(204); | ||
| }); | ||
|
|
||
| await apiTest.step('ES index is removed after uninstall', async () => { | ||
| const indexExists = await esClient.indices.exists({ index: FLIGHTS_ES_INDEX }); | ||
| expect(indexExists).toBe(false); | ||
| }); | ||
|
|
||
| await apiTest.step('list returns not_installed status after uninstall', async () => { | ||
| const response = await apiClient.get(apiPath, { | ||
| headers: { ...COMMON_HEADERS, ...credentials.apiKeyHeader }, | ||
| responseType: 'json', | ||
| }); | ||
| expect(response).toHaveStatusCode(200); | ||
| const flights = findFlightsDataset(response.body); | ||
| expect(flights.status).toBe('not_installed'); | ||
| expect(flights.overviewDashboard).toBe(FLIGHTS_OVERVIEW_DASHBOARD_ID); | ||
| }); | ||
| } | ||
| ); | ||
| }); | ||
|
|
||
| function findFlightsDataset( | ||
| body: Array<{ id: string; status: string; overviewDashboard: string }> | ||
| ) { | ||
| const dataset = body.find(({ id }) => id === FLIGHTS_DATASET_ID); | ||
| if (!dataset) { | ||
| throw new Error(`"${FLIGHTS_DATASET_ID}" dataset not found in sample data list response`); | ||
| } | ||
| return dataset; | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.