diff --git a/x-pack/plugins/session_view/common/constants.ts b/x-pack/plugins/session_view/common/constants.ts new file mode 100644 index 0000000000000..2ddf0de1b9abc --- /dev/null +++ b/x-pack/plugins/session_view/common/constants.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ + +export const PLUGIN_ID = 'sessionView'; +export const PLUGIN_NAME = 'Session View'; + +export const BASE_PATH = '/app/sessionView'; + +// Internal APIs are recommended to have the INTERNAL- suffix +export const INTERNAL_TEST_ROUTE = '/internal/session_view/test_route'; + +export const TEST_SAVED_OBJECT = 'test_saved_object'; diff --git a/x-pack/plugins/session_view/common/ecs/rule/index.ts b/x-pack/plugins/session_view/common/ecs/rule/index.ts new file mode 100644 index 0000000000000..ae7e5064a8ece --- /dev/null +++ b/x-pack/plugins/session_view/common/ecs/rule/index.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ + +export interface RuleEcs { + id?: string[]; + rule_id?: string[]; + name?: string[]; + false_positives?: string[]; + saved_id?: string[]; + timeline_id?: string[]; + timeline_title?: string[]; + max_signals?: number[]; + risk_score?: string[]; + output_index?: string[]; + description?: string[]; + from?: string[]; + immutable?: boolean[]; + index?: string[]; + interval?: string[]; + language?: string[]; + query?: string[]; + references?: string[]; + severity?: string[]; + tags?: string[]; + threat?: unknown; + threshold?: unknown; + type?: string[]; + size?: string[]; + to?: string[]; + enabled?: boolean[]; + filters?: unknown; + created_at?: string[]; + updated_at?: string[]; + created_by?: string[]; + updated_by?: string[]; + version?: string[]; + note?: string[]; + building_block_type?: string[]; +} diff --git a/x-pack/plugins/session_view/common/index.ts b/x-pack/plugins/session_view/common/index.ts new file mode 100644 index 0000000000000..63661f0d5a996 --- /dev/null +++ b/x-pack/plugins/session_view/common/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from './constants'; diff --git a/x-pack/plugins/session_view/common/test/index.ts b/x-pack/plugins/session_view/common/test/index.ts new file mode 100644 index 0000000000000..6d5df76b306a3 --- /dev/null +++ b/x-pack/plugins/session_view/common/test/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ + +// For the source of these roles please consult the PR these were introduced https://github.com/elastic/kibana/pull/81866#issue-511165754 +export enum ROLES { + reader = 'reader', + t1_analyst = 't1_analyst', + t2_analyst = 't2_analyst', + hunter = 'hunter', + rule_author = 'rule_author', + soc_manager = 'soc_manager', + platform_engineer = 'platform_engineer', + detections_admin = 'detections_admin', +} diff --git a/x-pack/plugins/session_view/cypress/cypress.json b/x-pack/plugins/session_view/cypress/cypress.json new file mode 100644 index 0000000000000..c2abb2cc23d6b --- /dev/null +++ b/x-pack/plugins/session_view/cypress/cypress.json @@ -0,0 +1,20 @@ +{ + "baseUrl": "http://localhost:5601", + "defaultCommandTimeout": 60000, + "execTimeout": 120000, + "pageLoadTimeout": 120000, + "nodeVersion": "system", + "retries": { + "runMode": 2 + }, + "env": { + "username": "elastic", + "password": "changeme" + }, + "screenshotsFolder": "../../../target/kibana-session-view/cypress/screenshots", + "trashAssetsBeforeRuns": false, + "video": false, + "videosFolder": "../../../target/kibana-session-view/cypress/videos", + "viewportHeight": 900, + "viewportWidth": 1440 +} diff --git a/x-pack/plugins/session_view/cypress/fixtures/example.json b/x-pack/plugins/session_view/cypress/fixtures/example.json new file mode 100644 index 0000000000000..02e4254378e97 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/x-pack/plugins/session_view/cypress/integration/session_view.spec.ts b/x-pack/plugins/session_view/cypress/integration/session_view.spec.ts new file mode 100644 index 0000000000000..a7d3a751cdd8d --- /dev/null +++ b/x-pack/plugins/session_view/cypress/integration/session_view.spec.ts @@ -0,0 +1,29 @@ +/* + * 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 { loginAndWaitForPage } from '../tasks/login'; + +import { SESSION_VIEW_URL } from '../urls/navigation'; + +import { cleanKibana } from '../tasks/common'; +import { TEST } from '../screens/common/page'; + +describe('Display session view test page', () => { + before(() => { + cleanKibana(); + }); + + it('navigates to session view home page', () => { + loginAndWaitForPage(SESSION_VIEW_URL); + cy.get(TEST).should('exist'); + }); + + it('navigates to session view path 1 page', () => { + loginAndWaitForPage(`${SESSION_VIEW_URL}/path_1`); + cy.get(TEST).contains('current path:').should('have.text', 'current path: /path_1'); + }); +}); diff --git a/x-pack/plugins/session_view/cypress/plugins/index.js b/x-pack/plugins/session_view/cypress/plugins/index.js new file mode 100644 index 0000000000000..59b2bab6e4e60 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/plugins/index.js @@ -0,0 +1,22 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/x-pack/plugins/session_view/cypress/screens/common/page.ts b/x-pack/plugins/session_view/cypress/screens/common/page.ts new file mode 100644 index 0000000000000..33bb6a9277b42 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/screens/common/page.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const TEST = '[data-test-subj="sessionViewTestPage"]'; diff --git a/x-pack/plugins/session_view/cypress/support/commands.js b/x-pack/plugins/session_view/cypress/support/commands.js new file mode 100644 index 0000000000000..119ab03f7cda1 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/support/commands.js @@ -0,0 +1,25 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/x-pack/plugins/session_view/cypress/support/index.js b/x-pack/plugins/session_view/cypress/support/index.js new file mode 100644 index 0000000000000..d68db96df2697 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/x-pack/plugins/session_view/cypress/tasks/common.ts b/x-pack/plugins/session_view/cypress/tasks/common.ts new file mode 100644 index 0000000000000..d726d5daa5cbc --- /dev/null +++ b/x-pack/plugins/session_view/cypress/tasks/common.ts @@ -0,0 +1,157 @@ +/* + * 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 { esArchiverResetKibana } from './es_archiver'; +import { RuleEcs } from '../../common/ecs/rule'; + +const primaryButton = 0; + +/** + * To overcome the React Beautiful DND sloppy click detection threshold: + * https://github.com/atlassian/react-beautiful-dnd/blob/67b96c8d04f64af6b63ae1315f74fc02b5db032b/docs/sensors/mouse.md#sloppy-clicks-and-click-prevention- + */ +const dndSloppyClickDetectionThreshold = 5; + +/** Starts dragging the subject */ +export const drag = (subject: JQuery) => { + const subjectLocation = subject[0].getBoundingClientRect(); + + cy.wrap(subject) + .trigger('mousedown', { + button: primaryButton, + clientX: subjectLocation.left, + clientY: subjectLocation.top, + force: true, + }) + .wait(300) + .trigger('mousemove', { + button: primaryButton, + clientX: subjectLocation.left + dndSloppyClickDetectionThreshold, + clientY: subjectLocation.top, + force: true, + }) + .wait(300); +}; + +/** Drags the subject being dragged on the specified drop target, but does not drop it */ +export const dragWithoutDrop = (dropTarget: JQuery) => { + cy.wrap(dropTarget).trigger('mousemove', 'center', { + button: primaryButton, + }); +}; + +/** "Drops" the subject being dragged on the specified drop target */ +export const drop = (dropTarget: JQuery) => { + const targetLocation = dropTarget[0].getBoundingClientRect(); + cy.wrap(dropTarget) + .trigger('mousemove', { + button: primaryButton, + clientX: targetLocation.left, + clientY: targetLocation.top, + force: true, + }) + .wait(300) + .trigger('mouseup', { force: true }) + .wait(300); +}; + +export const reload = () => { + cy.reload(); + cy.contains('a', 'Security'); +}; + +export const cleanKibana = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + + cy.request('GET', '/api/detection_engine/rules/_find').then((response) => { + const rules: RuleEcs[] = response.body.data; + + if (response.body.data.length > 0) { + rules.forEach((rule) => { + const jsonRule = rule; + cy.request({ + method: 'DELETE', + url: `/api/detection_engine/rules?rule_id=${jsonRule.rule_id}`, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + }); + }); + } + }); + + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'alert', + }, + }, + { + match: { + 'alert.alertTypeId': 'siem.signals', + }, + }, + { + match: { + 'alert.consumer': 'siem', + }, + }, + ], + }, + }, + }); + + deleteCases(); + + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'siem-ui-timeline', + }, + }, + ], + }, + }, + }); + + cy.request( + 'POST', + `${Cypress.env( + 'ELASTICSEARCH_URL' + )}/.lists-*,.items-*,.siem-signals-*/_delete_by_query?conflicts=proceed&scroll_size=10000`, + { + query: { + match_all: {}, + }, + } + ); + + esArchiverResetKibana(); +}; + +export const deleteCases = () => { + const kibanaIndexUrl = `${Cypress.env('ELASTICSEARCH_URL')}/.kibana_\*`; + cy.request('POST', `${kibanaIndexUrl}/_delete_by_query?conflicts=proceed`, { + query: { + bool: { + filter: [ + { + match: { + type: 'cases', + }, + }, + ], + }, + }, + }); +}; + +export const scrollToBottom = () => cy.scrollTo('bottom'); diff --git a/x-pack/plugins/session_view/cypress/tasks/es_archiver.ts b/x-pack/plugins/session_view/cypress/tasks/es_archiver.ts new file mode 100644 index 0000000000000..83ec1536baf0f --- /dev/null +++ b/x-pack/plugins/session_view/cypress/tasks/es_archiver.ts @@ -0,0 +1,57 @@ +/* + * 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 Path from 'path'; + +const ES_ARCHIVE_DIR = '../../test/security_solution_cypress/es_archives'; +const CONFIG_PATH = '../../test/functional/config.js'; +const ES_URL = Cypress.env('ELASTICSEARCH_URL'); +const KIBANA_URL = Cypress.config().baseUrl; +const CCS_ES_URL = Cypress.env('CCS_ELASTICSEARCH_URL'); +const CCS_KIBANA_URL = Cypress.env('CCS_KIBANA_URL'); + +// Otherwise cy.exec would inject NODE_TLS_REJECT_UNAUTHORIZED=0 and node would abort if used over https +const NODE_TLS_REJECT_UNAUTHORIZED = '1'; + +export const esArchiverLoad = (folder: string) => { + const path = Path.join(ES_ARCHIVE_DIR, folder); + cy.exec( + `node ../../../scripts/es_archiver load "${path}" --config "${CONFIG_PATH}" --es-url "${ES_URL}" --kibana-url "${KIBANA_URL}"`, + { env: { NODE_TLS_REJECT_UNAUTHORIZED } } + ); +}; + +export const esArchiverUnload = (folder: string) => { + const path = Path.join(ES_ARCHIVE_DIR, folder); + cy.exec( + `node ../../../scripts/es_archiver unload "${path}" --config "${CONFIG_PATH}" --es-url "${ES_URL}" --kibana-url "${KIBANA_URL}"`, + { env: { NODE_TLS_REJECT_UNAUTHORIZED } } + ); +}; + +export const esArchiverResetKibana = () => { + cy.exec( + `node ../../../scripts/es_archiver empty-kibana-index --config "${CONFIG_PATH}" --es-url "${ES_URL}" --kibana-url "${KIBANA_URL}"`, + { env: { NODE_TLS_REJECT_UNAUTHORIZED }, failOnNonZeroExit: false } + ); +}; + +export const esArchiverCCSLoad = (folder: string) => { + const path = Path.join(ES_ARCHIVE_DIR, folder); + cy.exec( + `node ../../../scripts/es_archiver load "${path}" --config "${CONFIG_PATH}" --es-url "${CCS_ES_URL}" --kibana-url "${CCS_KIBANA_URL}"`, + { env: { NODE_TLS_REJECT_UNAUTHORIZED } } + ); +}; + +export const esArchiverCCSUnload = (folder: string) => { + const path = Path.join(ES_ARCHIVE_DIR, folder); + cy.exec( + `node ../../../scripts/es_archiver unload "${path}" --config "${CONFIG_PATH}" --es-url "${CCS_ES_URL}" --kibana-url "${CCS_KIBANA_URL}"`, + { env: { NODE_TLS_REJECT_UNAUTHORIZED } } + ); +}; diff --git a/x-pack/plugins/session_view/cypress/tasks/login.ts b/x-pack/plugins/session_view/cypress/tasks/login.ts new file mode 100644 index 0000000000000..8ce3d227cfa2e --- /dev/null +++ b/x-pack/plugins/session_view/cypress/tasks/login.ts @@ -0,0 +1,312 @@ +/* + * 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 * as yaml from 'js-yaml'; +import Url, { UrlObject } from 'url'; + +import { ROLES } from '../../common/test'; + +/** + * Credentials in the `kibana.dev.yml` config file will be used to authenticate + * with Kibana when credentials are not provided via environment variables + */ +const KIBANA_DEV_YML_PATH = '../../../config/kibana.dev.yml'; + +/** + * The configuration path in `kibana.dev.yml` to the username to be used when + * authenticating with Kibana. + */ +const ELASTICSEARCH_USERNAME_CONFIG_PATH = 'config.elasticsearch.username'; + +/** + * The configuration path in `kibana.dev.yml` to the password to be used when + * authenticating with Kibana. + */ +const ELASTICSEARCH_PASSWORD_CONFIG_PATH = 'config.elasticsearch.password'; + +/** + * The `CYPRESS_ELASTICSEARCH_USERNAME` environment variable specifies the + * username to be used when authenticating with Kibana + */ +const ELASTICSEARCH_USERNAME = 'ELASTICSEARCH_USERNAME'; + +/** + * The `CYPRESS_ELASTICSEARCH_PASSWORD` environment variable specifies the + * username to be used when authenticating with Kibana + */ +const ELASTICSEARCH_PASSWORD = 'ELASTICSEARCH_PASSWORD'; + +/** + * The Kibana server endpoint used for authentication + */ +const LOGIN_API_ENDPOINT = '/internal/security/login'; + +/** + * cy.visit will default to the baseUrl which uses the default kibana test user + * This function will override that functionality in cy.visit by building the baseUrl + * directly from the environment variables set up in x-pack/test/security_solution_cypress/runner.ts + * + * @param role string role/user to log in with + * @param route string route to visit + */ +export const getUrlWithRoute = (role: ROLES, route: string) => { + const url = Cypress.config().baseUrl; + const kibana = new URL(url!); + const theUrl = `${Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: kibana.protocol.replace(':', ''), + hostname: kibana.hostname, + port: kibana.port, + } as UrlObject)}${route.startsWith('/') ? '' : '/'}${route}`; + cy.log(`origin: ${theUrl}`); + return theUrl; +}; + +interface User { + username: string; + password: string; +} + +/** + * Builds a URL with basic auth using the passed in user. + * + * @param user the user information to build the basic auth with + * @param route string route to visit + */ +export const constructUrlWithUser = (user: User, route: string) => { + const url = Cypress.config().baseUrl; + const kibana = new URL(url!); + const hostname = kibana.hostname; + const username = user.username; + const password = user.password; + const protocol = kibana.protocol.replace(':', ''); + const port = kibana.port; + + const path = `${route.startsWith('/') ? '' : '/'}${route}`; + const strUrl = `${protocol}://${username}:${password}@${hostname}:${port}${path}`; + const builtUrl = new URL(strUrl); + + cy.log(`origin: ${builtUrl.href}`); + return builtUrl.href; +}; + +export const getCurlScriptEnvVars = () => ({ + ELASTICSEARCH_URL: Cypress.env('ELASTICSEARCH_URL'), + ELASTICSEARCH_USERNAME: Cypress.env('ELASTICSEARCH_USERNAME'), + ELASTICSEARCH_PASSWORD: Cypress.env('ELASTICSEARCH_PASSWORD'), + KIBANA_URL: Cypress.config().baseUrl, +}); + +export const postRoleAndUser = (role: ROLES) => { + const env = getCurlScriptEnvVars(); + const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`; + const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`; + const detectionsUserScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_user.sh`; + const detectionsUserJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_user.json`; + + // post the role + cy.exec(`bash ${detectionsRoleScriptPath} ${detectionsRoleJsonPath}`, { + env, + }); + + // post the user associated with the role to elasticsearch + cy.exec(`bash ${detectionsUserScriptPath} ${detectionsUserJsonPath}`, { + env, + }); +}; + +export const deleteRoleAndUser = (role: ROLES) => { + const env = getCurlScriptEnvVars(); + const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`; + + // delete the role + cy.exec(`bash ${detectionsUserDeleteScriptPath}`, { + env, + }); +}; + +export const loginWithUser = (user: User) => { + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: user.username, + password: user.password, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: constructUrlWithUser(user, LOGIN_API_ENDPOINT), + }); +}; + +export const loginWithRole = async (role: ROLES) => { + postRoleAndUser(role); + const theUrl = Url.format({ + auth: `${role}:changeme`, + username: role, + password: 'changeme', + protocol: Cypress.env('protocol'), + hostname: Cypress.env('hostname'), + port: Cypress.env('configport'), + } as UrlObject); + cy.log(`origin: ${theUrl}`); + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: role, + password: 'changeme', + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: getUrlWithRoute(role, LOGIN_API_ENDPOINT), + }); +}; + +/** + * Authenticates with Kibana using, if specified, credentials specified by + * environment variables. The credentials in `kibana.dev.yml` will be used + * for authentication when the environment variables are unset. + * + * To speed the execution of tests, prefer this non-interactive authentication, + * which is faster than authentication via Kibana's interactive login page. + */ +export const login = (role?: ROLES) => { + if (role != null) { + loginWithRole(role); + } else if (credentialsProvidedByEnvironment()) { + loginViaEnvironmentCredentials(); + } else { + loginViaConfig(); + } +}; + +/** + * Returns `true` if the credentials used to login to Kibana are provided + * via environment variables + */ +const credentialsProvidedByEnvironment = (): boolean => + Cypress.env(ELASTICSEARCH_USERNAME) != null && Cypress.env(ELASTICSEARCH_PASSWORD) != null; + +/** + * Authenticates with Kibana by reading credentials from the + * `CYPRESS_ELASTICSEARCH_USERNAME` and `CYPRESS_ELASTICSEARCH_PASSWORD` + * environment variables, and POSTing the username and password directly to + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). + */ +const loginViaEnvironmentCredentials = () => { + cy.log( + `Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables` + ); + + // programmatically authenticate without interacting with the Kibana login page + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: Cypress.env(ELASTICSEARCH_USERNAME), + password: Cypress.env(ELASTICSEARCH_PASSWORD), + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-env' }, + method: 'POST', + url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, + }); +}; + +/** + * Authenticates with Kibana by reading credentials from the + * `kibana.dev.yml` file and POSTing the username and password directly to + * Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed). + */ +const loginViaConfig = () => { + cy.log( + `Authenticating via config credentials \`${ELASTICSEARCH_USERNAME_CONFIG_PATH}\` and \`${ELASTICSEARCH_PASSWORD_CONFIG_PATH}\` from \`${KIBANA_DEV_YML_PATH}\`` + ); + + // read the login details from `kibana.dev.yaml` + cy.readFile(KIBANA_DEV_YML_PATH).then((kibanaDevYml) => { + const config = yaml.safeLoad(kibanaDevYml); + + // programmatically authenticate without interacting with the Kibana login page + cy.request({ + body: { + providerType: 'basic', + providerName: 'basic', + currentURL: '/', + params: { + username: config.elasticsearch.username, + password: config.elasticsearch.password, + }, + }, + headers: { 'kbn-xsrf': 'cypress-creds-via-config' }, + method: 'POST', + url: `${Cypress.config().baseUrl}${LOGIN_API_ENDPOINT}`, + }); + }); +}; + +/** + * Get the configured auth details that were used to spawn cypress + * + * @returns the default Elasticsearch username and password for this environment + */ +export const getEnvAuth = (): User => { + if (credentialsProvidedByEnvironment()) { + return { + username: Cypress.env(ELASTICSEARCH_USERNAME), + password: Cypress.env(ELASTICSEARCH_PASSWORD), + }; + } else { + let user: User = { username: '', password: '' }; + cy.readFile(KIBANA_DEV_YML_PATH).then((devYml) => { + const config = yaml.safeLoad(devYml); + user = { username: config.elasticsearch.username, password: config.elasticsearch.password }; + }); + + return user; + } +}; + +/** + * Authenticates with Kibana, visits the specified `url`, and waits for the + * Kibana global nav to be displayed before continuing + */ +export const loginAndWaitForPage = (url: string, role?: ROLES) => { + login(role); + cy.visit( + `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))` + ); + cy.get('[data-test-subj="headerGlobalNav"]'); +}; + +export const loginAndWaitForPageWithoutDateRange = (url: string, role?: ROLES) => { + login(role); + cy.visit(role ? getUrlWithRoute(role, url) : url); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; + +export const loginWithUserAndWaitForPageWithoutDateRange = (url: string, user: User) => { + loginWithUser(user); + cy.visit(constructUrlWithUser(user, url)); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; + +export const waitForPageWithoutDateRange = (url: string, role?: ROLES) => { + cy.visit(role ? getUrlWithRoute(role, url) : url); + cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); +}; diff --git a/x-pack/plugins/session_view/cypress/tsconfig.json b/x-pack/plugins/session_view/cypress/tsconfig.json new file mode 100644 index 0000000000000..4efb4c5c56296 --- /dev/null +++ b/x-pack/plugins/session_view/cypress/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../../tsconfig.base.json", + "include": [ + "**/*", + "fixtures/**/*.json" + ], + "exclude": [ + "target/**/*" + ], + "compilerOptions": { + "target": "es5", + "lib": ["es5", "dom"], + "outDir": "target/types", + "types": [ + "cypress", + "cypress-pipe", + "node" + ], + "resolveJsonModule": true, + }, + "references": [ + { "path": "../tsconfig.json" } + ] +} diff --git a/x-pack/plugins/session_view/cypress/urls/navigation.ts b/x-pack/plugins/session_view/cypress/urls/navigation.ts new file mode 100644 index 0000000000000..fc632b9646dfa --- /dev/null +++ b/x-pack/plugins/session_view/cypress/urls/navigation.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export const SESSION_VIEW_URL = 'app/sessionView'; diff --git a/x-pack/plugins/session_view/jest.config.js b/x-pack/plugins/session_view/jest.config.js new file mode 100644 index 0000000000000..768dea8ee0182 --- /dev/null +++ b/x-pack/plugins/session_view/jest.config.js @@ -0,0 +1,17 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/session_view'], + coverageDirectory: '/target/kibana-coverage/jest/x-pack/plugins/session_view', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/plugins/session_view/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/x-pack/plugins/session_view/kibana.json b/x-pack/plugins/session_view/kibana.json new file mode 100644 index 0000000000000..4bb9471fe85cc --- /dev/null +++ b/x-pack/plugins/session_view/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "sessionView", + "version": "8.0.0", + "kibanaVersion": "kibana", + "owner": { + "name": "Security Team", + "githubTeam": "security-team" + }, + "requiredPlugins": ["data"], + "requiredBundles": ["kibanaReact"], + "server": true, + "ui": true +} diff --git a/x-pack/plugins/session_view/package.json b/x-pack/plugins/session_view/package.json new file mode 100644 index 0000000000000..5724d85fed060 --- /dev/null +++ b/x-pack/plugins/session_view/package.json @@ -0,0 +1,12 @@ +{ + "author": "Elastic", + "name": "session_view", + "version": "8.0.0", + "private": true, + "license": "Elastic-License", + "scripts": { + "cypress": "../../../node_modules/.bin/cypress", + "cypress:open": "yarn cypress open --config-file ./cypress/cypress.json", + "cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/session_view_cypress/visual_config.ts" + } +} diff --git a/x-pack/plugins/session_view/public/application.tsx b/x-pack/plugins/session_view/public/application.tsx new file mode 100644 index 0000000000000..08c9edebc5f98 --- /dev/null +++ b/x-pack/plugins/session_view/public/application.tsx @@ -0,0 +1,72 @@ +/* + * 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 React from 'react'; +import ReactDOM from 'react-dom'; +import { Router } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { ReactQueryDevtools } from 'react-query/devtools'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiErrorBoundary } from '@elastic/eui'; +import type { AppMountParameters, CoreStart } from 'kibana/public'; +import { KibanaContextProvider } from '../../../../src/plugins/kibana_react/public'; +import { EuiThemeProvider } from '../../../../src/plugins/kibana_react/common'; + +import type { SessionViewConfigType } from './types'; +import { Application } from './components/Application'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface DependencyServices {} + +interface RenderAppProps { + coreStart: CoreStart; + depsServices: DependencyServices; + appMountParams: AppMountParameters; + version: string; + config: SessionViewConfigType; +} + +// Initializing react-query +const queryClient = new QueryClient(); + +export const renderApp = (props: RenderAppProps) => { + const { coreStart, depsServices, appMountParams, version, config } = props; + + const { element, history } = appMountParams; + + const renderSessionViewApp = () => { + const services = { + ...coreStart, + ...depsServices, + version, + }; + + return ( + + + + + + + + + + + + + + + ); + }; + + // Mount the application + ReactDOM.render(renderSessionViewApp(), element); + + return () => { + ReactDOM.unmountComponentAtNode(element); + }; +}; diff --git a/x-pack/plugins/session_view/public/components/Application/index.tsx b/x-pack/plugins/session_view/public/components/Application/index.tsx new file mode 100644 index 0000000000000..b411792807557 --- /dev/null +++ b/x-pack/plugins/session_view/public/components/Application/index.tsx @@ -0,0 +1,55 @@ +/* + * 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. + */ +/** @jsx jsx */ + +import { useEuiTheme } from '@elastic/eui'; +import { Link } from 'react-router-dom'; +import { css, jsx } from '@emotion/react'; +import { Routes } from '../../routes'; + +/** + * Top level application component + */ +const listItemCss = ` + margin-right: 30px; +`; + +const Links = () => { + return ( + + + Home + + + Path 1 + + + Path 2 + + + ); +}; + +export const Application = () => { + const { euiTheme } = useEuiTheme(); + return ( + + + + + ); +}; diff --git a/x-pack/plugins/session_view/public/components/TestPage/index.tsx b/x-pack/plugins/session_view/public/components/TestPage/index.tsx new file mode 100644 index 0000000000000..e38e582a96bea --- /dev/null +++ b/x-pack/plugins/session_view/public/components/TestPage/index.tsx @@ -0,0 +1,103 @@ +/* + * 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. + */ +/** @jsx jsx */ + +import { useState } from 'react'; +import { useQuery, useMutation } from 'react-query'; +import { jsx } from '@emotion/react'; +import { + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiPage, + EuiPageContent, +} from '@elastic/eui'; +import { RouteComponentProps } from 'react-router-dom'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { CoreStart } from '../../../../../../src/core/public'; +import { BASE_PATH, INTERNAL_TEST_ROUTE } from '../../../common/constants'; + +export const TestPage = (props: RouteComponentProps) => { + // An example of setting using and setting the core services + // in the client plugin + const { chrome, http } = useKibana().services; + chrome.setBreadcrumbs([ + { + text: props.match.path, + href: http.basePath.prepend(`${BASE_PATH}${props.match.path.split('/')[1]}`), + }, + ]); + chrome.docTitle.change(props.match.path); + + const [indexName, setIndexName] = useState('test'); + const [searchIndex, setSearchIndex] = useState(indexName); + + // An example of using useQuery to hit an internal endpoint via fetch (GET) + const { + isFetching, + data: getData, + refetch, + } = useQuery(['test-data', searchIndex], () => + http.get(INTERNAL_TEST_ROUTE, { + query: { + index: indexName, + }, + }) + ); + + // An example of using useQuery to hit an internal endpoint via mutation (PUT) + const { + mutate, + isLoading, + data: putData, + } = useMutation(() => { + return http.put(INTERNAL_TEST_ROUTE, { body: JSON.stringify({ index: indexName }) }); + }); + + const handleInsertData = () => { + mutate(); + }; + const handleFetchData = () => { + setSearchIndex(indexName); + }; + const handleRefetch = () => { + refetch(); + }; + + const handleOnChange = (e: React.ChangeEvent) => { + setIndexName(e.target.value); + }; + + return ( + + + + current path: {props.match.path} + + Index Name: + + + + Put Data + + put network data: + {isLoading ? Loading! : {JSON.stringify(putData, null, 2)}} + + + Fetch Data + Refetch Data + + + get network data: + {isFetching ? Loading! : {JSON.stringify(getData, null, 2)}} + + + + + ); +}; diff --git a/x-pack/plugins/session_view/public/index.ts b/x-pack/plugins/session_view/public/index.ts new file mode 100644 index 0000000000000..9dd7c4b7ec946 --- /dev/null +++ b/x-pack/plugins/session_view/public/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { PluginInitializerContext } from 'src/core/public'; +import { SessionViewPlugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new SessionViewPlugin(initializerContext); +} diff --git a/x-pack/plugins/session_view/public/plugin.ts b/x-pack/plugins/session_view/public/plugin.ts new file mode 100644 index 0000000000000..27a44fb3512b3 --- /dev/null +++ b/x-pack/plugins/session_view/public/plugin.ts @@ -0,0 +1,57 @@ +/* + * 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 { + AppMountParameters, + CoreSetup, + CoreStart, + Plugin, + PluginInitializerContext, +} from '../../../../src/core/public'; +import { PLUGIN_ID, PLUGIN_NAME } from '../common'; +import { SessionViewConfigType } from './types'; + +export class SessionViewPlugin implements Plugin { + private kibanaVersion: string; + + /** + * Initialize SessionViewPlugin class properties (logger, etc) that is accessible + * through the initializerContext. + */ + constructor(private readonly initializerContext: PluginInitializerContext) { + this.kibanaVersion = initializerContext.env.packageInfo.version; + } + + public setup(core: CoreSetup) { + const kibanaVersion = this.kibanaVersion; + const config = this.initializerContext.config.get(); + + core.application.register({ + id: PLUGIN_ID, + title: PLUGIN_NAME, + async mount(params: AppMountParameters) { + const [coreStart, startDepsServices] = await core.getStartServices(); + const { renderApp } = await import('./application'); + return renderApp({ + coreStart, + config, + depsServices: startDepsServices, + appMountParams: params, + version: kibanaVersion, + }); + }, + }); + } + + public start(core: CoreStart) { + // NO-OP + return {}; + } + + // eslint-disable-next-line @typescript-eslint/no-empty-function + public stop() {} +} diff --git a/x-pack/plugins/session_view/public/routes/index.tsx b/x-pack/plugins/session_view/public/routes/index.tsx new file mode 100644 index 0000000000000..3bbe332bd97af --- /dev/null +++ b/x-pack/plugins/session_view/public/routes/index.tsx @@ -0,0 +1,24 @@ +/* + * 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 React from 'react'; +import { Switch, Route } from 'react-router-dom'; +import { TestPage } from '../components/TestPage'; + +export const Routes = () => { + /** + * Dummy function to render the title, shows an example of setting the chrome's + * breadcrumbs + */ + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/session_view/public/types.ts b/x-pack/plugins/session_view/public/types.ts new file mode 100644 index 0000000000000..9c90c25d17973 --- /dev/null +++ b/x-pack/plugins/session_view/public/types.ts @@ -0,0 +1,10 @@ +/* + * 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. + */ + +export interface SessionViewConfigType { + enabled: boolean; +}; diff --git a/x-pack/plugins/session_view/server/index.ts b/x-pack/plugins/session_view/server/index.ts new file mode 100644 index 0000000000000..989d26f8193e7 --- /dev/null +++ b/x-pack/plugins/session_view/server/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { PluginInitializerContext } from '../../../../src/core/server'; +import { SessionViewPlugin } from './plugin'; + +export function plugin (initializerContext: PluginInitializerContext) { + return new SessionViewPlugin(initializerContext); +} diff --git a/x-pack/plugins/session_view/server/plugin.ts b/x-pack/plugins/session_view/server/plugin.ts new file mode 100644 index 0000000000000..fd56c2eadffe5 --- /dev/null +++ b/x-pack/plugins/session_view/server/plugin.ts @@ -0,0 +1,52 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + Logger, + PluginInitializerContext, +} from '../../../../src/core/server'; +import { + SessionViewSetupPlugins, + SessionViewStartPlugins, +} from './types'; +import { registerRoutes } from './routes'; +import { getTestSavedObject } from './saved_objects'; + +export class SessionViewPlugin implements Plugin { + private logger: Logger; + + /** + * Initialize SessionViewPlugin class properties (logger, etc) that is accessible + * through the initializerContext. + */ + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup, plugins: SessionViewSetupPlugins) { + const { savedObjects } = core; + this.logger.debug('session view: Setup'); + const router = core.http.createRouter(); + + // Register server routes + registerRoutes(router); + + // Register saved objects + savedObjects.registerType(getTestSavedObject()); + } + + public start(core: CoreStart, plugins: SessionViewStartPlugins) { + this.logger.debug('session view: Start'); + } + + public stop() { + this.logger.debug('session view: Stop') + } +} diff --git a/x-pack/plugins/session_view/server/routes/index.ts b/x-pack/plugins/session_view/server/routes/index.ts new file mode 100644 index 0000000000000..55d5f829515eb --- /dev/null +++ b/x-pack/plugins/session_view/server/routes/index.ts @@ -0,0 +1,13 @@ +/* + * 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 { IRouter } from '../../../../../src/core/server'; +import { registerTestRoute } from './test_route'; + +export const registerRoutes = (router: IRouter) => { + registerTestRoute(router); +}; diff --git a/x-pack/plugins/session_view/server/routes/test_route.ts b/x-pack/plugins/session_view/server/routes/test_route.ts new file mode 100644 index 0000000000000..56f0414339255 --- /dev/null +++ b/x-pack/plugins/session_view/server/routes/test_route.ts @@ -0,0 +1,77 @@ +/* + * 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 { schema } from '@kbn/config-schema'; +import uuid from 'uuid'; +import { IRouter } from '../../../../../src/core/server'; +import { INTERNAL_TEST_ROUTE } from '../../common/constants'; + +export const registerTestRoute = (router: IRouter) => { + router.get( + { + path: INTERNAL_TEST_ROUTE, + validate: { + query: schema.object({ + index: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, response) => { + // TODO (Jiawei & Paulo): Evaluate saved objects & ES client + const client = context.core.elasticsearch.client.asCurrentUser; + + const { index } = request.query; + + const search = await client.search({ + index: [`${index}*`], + body: { + size: 20, + timeout: '30s', + query: { + bool: { + filter: [], + }, + }, + }, + }); + + return response.ok({ body: search.body.hits }); + } + ); + + router.put( + { + path: INTERNAL_TEST_ROUTE, + validate: { + body: schema.object({ + index: schema.string(), + }), + }, + }, + async (context, request, response) => { + // TODO (Jiawei & Paulo): Evaluate saved objects & ES client + const { index } = request.body; + + const client = context.core.elasticsearch.client.asCurrentUser; + + client.create({ + index, + id: uuid(), + body: { + message: 'Test Message', + timestamp: new Date().toISOString(), + }, + }); + + return response.ok({ + body: { + message: 'success', + }, + }); + } + ); +}; diff --git a/x-pack/plugins/session_view/server/saved_objects/index.ts b/x-pack/plugins/session_view/server/saved_objects/index.ts new file mode 100644 index 0000000000000..d33c065ac691b --- /dev/null +++ b/x-pack/plugins/session_view/server/saved_objects/index.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export { getTestSavedObject } from './test_saved_object'; diff --git a/x-pack/plugins/session_view/server/saved_objects/test_saved_object.test.ts b/x-pack/plugins/session_view/server/saved_objects/test_saved_object.test.ts new file mode 100644 index 0000000000000..5ded361b254b7 --- /dev/null +++ b/x-pack/plugins/session_view/server/saved_objects/test_saved_object.test.ts @@ -0,0 +1,17 @@ +/* + * 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 { TEST_SAVED_OBJECT } from '../../common/constants'; +import { getTestSavedObject } from './test_saved_object'; + +describe('getTestSavedObject', () => { + it('return test saved object', () => { + const savedObject = getTestSavedObject(); + + expect(savedObject.name).toBe(TEST_SAVED_OBJECT); + }); +}); diff --git a/x-pack/plugins/session_view/server/saved_objects/test_saved_object.ts b/x-pack/plugins/session_view/server/saved_objects/test_saved_object.ts new file mode 100644 index 0000000000000..51cff0c336ab9 --- /dev/null +++ b/x-pack/plugins/session_view/server/saved_objects/test_saved_object.ts @@ -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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { SavedObjectsType } from 'src/core/server'; +import { TEST_SAVED_OBJECT } from '../../common/constants'; + +export const getTestSavedObject = () => ({ + name: TEST_SAVED_OBJECT, + hidden: false, + namespaceType: 'agnostic', + mappings: { + properties: { + name: { type: 'text' }, + value: { type: 'keyword' }, + }, + }, +} as SavedObjectsType); diff --git a/x-pack/plugins/session_view/server/types.ts b/x-pack/plugins/session_view/server/types.ts new file mode 100644 index 0000000000000..c1ef6d9507660 --- /dev/null +++ b/x-pack/plugins/session_view/server/types.ts @@ -0,0 +1,9 @@ +/* + * 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. + */ + +export interface SessionViewSetupPlugins {}; +export interface SessionViewStartPlugins {}; diff --git a/x-pack/plugins/session_view/tsconfig.json b/x-pack/plugins/session_view/tsconfig.json new file mode 100644 index 0000000000000..a9dd66ce503a9 --- /dev/null +++ b/x-pack/plugins/session_view/tsconfig.json @@ -0,0 +1,42 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true, + }, + "include": [ + // add all the folders containg files to be compiled + "common/**/*", + "public/**/*", + "server/**/*", + "server/**/*.json", + "scripts/**/*", + "package.json", + "storybook/**/*", + "../../../typings/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + // add references to other TypeScript projects the plugin depends on + + // requiredPlugins from ./kibana.json + { "path": "../licensing/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../encrypted_saved_objects/tsconfig.json" }, + + // optionalPlugins from ./kibana.json + { "path": "../security/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../cloud/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + + // requiredBundles from ./kibana.json + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../infra/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + ] +} diff --git a/x-pack/test/session_view_cypress/config.ts b/x-pack/test/session_view_cypress/config.ts new file mode 100644 index 0000000000000..18d27a413d68c --- /dev/null +++ b/x-pack/test/session_view_cypress/config.ts @@ -0,0 +1,45 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +import { CA_CERT_PATH } from '@kbn/dev-utils'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonTestsConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const xpackFunctionalTestsConfig = await readConfigFile( + require.resolve('../functional/config.js') + ); + + return { + ...kibanaCommonTestsConfig.getAll(), + + esTestCluster: { + ...xpackFunctionalTestsConfig.get('esTestCluster'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('esTestCluster.serverArgs'), + // define custom es server here + // API Keys is enabled at the top level + 'xpack.security.enabled=true', + ], + }, + + kbnTestServer: { + ...xpackFunctionalTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), + '--csp.strict=false', + // define custom kibana server args here + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + // retrieve rules from the filesystem but not from fleet for Cypress tests + `--home.disableWelcomeScreen=true`, + ], + }, + }; +} diff --git a/x-pack/test/session_view_cypress/ftr_provider_context.d.ts b/x-pack/test/session_view_cypress/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..aa56557c09df8 --- /dev/null +++ b/x-pack/test/session_view_cypress/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * 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 { GenericFtrProviderContext } from '@kbn/test'; + +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/session_view_cypress/runner.ts b/x-pack/test/session_view_cypress/runner.ts new file mode 100644 index 0000000000000..4ce218d16c155 --- /dev/null +++ b/x-pack/test/session_view_cypress/runner.ts @@ -0,0 +1,129 @@ +/* + * 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 { resolve } from 'path'; +import Url from 'url'; + +import { withProcRunner } from '@kbn/dev-utils'; + +import { FtrProviderContext } from './ftr_provider_context'; + +export async function SessionViewCypressCliTestRunner({ getService }: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + + await withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: ['cypress:run'], + cwd: resolve(__dirname, '../../plugins/session_view'), + env: { + FORCE_COLOR: '1', + CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), + CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), + CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + ...process.env, + }, + wait: true, + }); + }); +} + +export async function SessionViewCypressCliFirefoxTestRunner({ + getService, +}: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + + await withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: ['cypress:run:firefox'], + cwd: resolve(__dirname, '../../plugins/session_view'), + env: { + FORCE_COLOR: '1', + CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), + CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), + CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + ...process.env, + }, + wait: true, + }); + }); +} + +export async function SessionViewCypressCcsTestRunner({ getService }: FtrProviderContext) { + const log = getService('log'); + + await withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: ['cypress:run:ccs'], + cwd: resolve(__dirname, '../../plugins/session_view'), + env: { + FORCE_COLOR: '1', + CYPRESS_BASE_URL: process.env.TEST_KIBANA_URL, + CYPRESS_ELASTICSEARCH_URL: process.env.TEST_ES_URL, + CYPRESS_ELASTICSEARCH_USERNAME: process.env.ELASTICSEARCH_USERNAME, + CYPRESS_ELASTICSEARCH_PASSWORD: process.env.ELASTICSEARCH_PASSWORD, + CYPRESS_CCS_KIBANA_URL: process.env.TEST_KIBANA_URLDATA, + CYPRESS_CCS_ELASTICSEARCH_URL: process.env.TEST_ES_URLDATA, + CYPRESS_CCS_REMOTE_NAME: process.env.TEST_CCS_REMOTE_NAME, + ...process.env, + }, + wait: true, + }); + }); +} + +export async function SessionViewCypressVisualTestRunner({ getService }: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + + await withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: ['cypress:open'], + cwd: resolve(__dirname, '../../plugins/session_view'), + env: { + FORCE_COLOR: '1', + CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), + CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), + CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + ...process.env, + }, + wait: true, + }); + }); +} + +export async function SessionViewCypressUpgradeCliTestRunner({ + getService, +}: FtrProviderContext) { + const log = getService('log'); + const config = getService('config'); + + await withProcRunner(log, async (procs) => { + await procs.run('cypress', { + cmd: 'yarn', + args: ['cypress:run:upgrade'], + cwd: resolve(__dirname, '../../plugins/session_view'), + env: { + FORCE_COLOR: '1', + CYPRESS_BASE_URL: Url.format(config.get('servers.kibana')), + CYPRESS_ELASTICSEARCH_URL: Url.format(config.get('servers.elasticsearch')), + CYPRESS_ELASTICSEARCH_USERNAME: config.get('servers.elasticsearch.username'), + CYPRESS_ELASTICSEARCH_PASSWORD: config.get('servers.elasticsearch.password'), + ...process.env, + }, + wait: true, + }); + }); +} diff --git a/x-pack/test/session_view_cypress/services.ts b/x-pack/test/session_view_cypress/services.ts new file mode 100644 index 0000000000000..5e063134081ad --- /dev/null +++ b/x-pack/test/session_view_cypress/services.ts @@ -0,0 +1,8 @@ +/* + * 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. + */ + +export * from '../../../test/common/services'; diff --git a/x-pack/test/session_view_cypress/visual_config.ts b/x-pack/test/session_view_cypress/visual_config.ts new file mode 100644 index 0000000000000..d1800f62becd1 --- /dev/null +++ b/x-pack/test/session_view_cypress/visual_config.ts @@ -0,0 +1,19 @@ +/* + * 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 { FtrConfigProviderContext } from '@kbn/test'; + +import { SessionViewCypressVisualTestRunner } from './runner'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const sessionViewCypressVisualTestRunner = await readConfigFile(require.resolve('./config.ts')); + return { + ...sessionViewCypressVisualTestRunner.getAll(), + + testRunner: SessionViewCypressVisualTestRunner, + }; +}
{JSON.stringify(putData, null, 2)}
{JSON.stringify(getData, null, 2)}