From f8f17116889f5fcb2f8766d0dceafd1d8b7f231c Mon Sep 17 00:00:00 2001 From: Steph Milovic Date: Fri, 18 Dec 2020 09:23:09 -0700 Subject: [PATCH 01/40] [Security Solution] [Cases] Follow up jest tests for #84587 (#86231) --- .../case/common/api/connectors/mappings.ts | 2 + .../client/configure/get_fields.test.ts | 60 ++ .../client/configure/get_mappings.test.ts | 55 ++ .../case/server/client/configure/mock.ts | 625 ++++++++++++++++++ .../server/client/configure/utils.test.ts | 545 +-------------- .../api/__fixtures__/mock_saved_objects.ts | 24 +- .../routes/api/__mocks__/request_responses.ts | 24 +- .../api/cases/configure/get_configure.test.ts | 7 +- .../routes/api/cases/configure/get_fields.ts | 70 -- .../server/routes/api/cases/configure/mock.ts | 35 +- .../configure/post_push_to_service.test.ts | 104 +++ .../cases/configure/post_push_to_service.ts | 2 +- .../routes/api/cases/configure/utils.test.ts | 174 ++++- .../routes/api/cases/configure/utils.ts | 6 +- .../plugins/case/server/routes/api/index.ts | 2 - .../public/cases/containers/api.ts | 19 - .../cases/containers/use_get_fields.tsx | 82 --- 17 files changed, 1087 insertions(+), 749 deletions(-) create mode 100644 x-pack/plugins/case/server/client/configure/get_fields.test.ts create mode 100644 x-pack/plugins/case/server/client/configure/get_mappings.test.ts create mode 100644 x-pack/plugins/case/server/client/configure/mock.ts delete mode 100644 x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts create mode 100644 x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts delete mode 100644 x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/case/common/api/connectors/mappings.ts index 3e8baf0af28343..b91f9d69e85e26 100644 --- a/x-pack/plugins/case/common/api/connectors/mappings.ts +++ b/x-pack/plugins/case/common/api/connectors/mappings.ts @@ -180,6 +180,8 @@ export const PostPushRequestRt = rt.type({ params: ServiceConnectorCaseParamsRt, }); +export type PostPushRequest = rt.TypeOf; + export interface SimpleComment { comment: string; commentId: string; diff --git a/x-pack/plugins/case/server/client/configure/get_fields.test.ts b/x-pack/plugins/case/server/client/configure/get_fields.test.ts new file mode 100644 index 00000000000000..b465d916b2292e --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/get_fields.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConnectorTypes } from '../../../common/api'; + +import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; +import { actionsErrResponse, mappings, mockGetFieldsResponse } from './mock'; +describe('get_fields', () => { + const execute = jest.fn().mockReturnValue(mockGetFieldsResponse); + const actionsMock = { ...actionsClientMock.create(), execute }; + beforeEach(async () => { + jest.clearAllMocks(); + }); + + describe('happy path', () => { + test('it gets fields', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getFields({ + actionsClient: actionsMock, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + expect(res).toEqual({ + fields: [ + { id: 'summary', name: 'Summary', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + defaultMappings: mappings[ConnectorTypes.jira], + }); + }); + }); + + describe('unhappy path', () => { + test('it throws error', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + await caseClient.client + .getFields({ + actionsClient: { ...actionsMock, execute: jest.fn().mockReturnValue(actionsErrResponse) }, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }) + .catch((e) => { + expect(e).not.toBeNull(); + expect(e.isBoom).toBe(true); + expect(e.output.statusCode).toBe(424); + }); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/configure/get_mappings.test.ts b/x-pack/plugins/case/server/client/configure/get_mappings.test.ts new file mode 100644 index 00000000000000..e68db5cde940bb --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/get_mappings.test.ts @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ConnectorTypes } from '../../../common/api'; + +import { createMockSavedObjectsRepository, mockCaseMappings } from '../../routes/api/__fixtures__'; +import { createCaseClientWithMockSavedObjectsClient } from '../mocks'; +import { actionsClientMock } from '../../../../actions/server/actions_client.mock'; +import { mappings, mockGetFieldsResponse } from './mock'; + +describe('get_mappings', () => { + const execute = jest.fn().mockReturnValue(mockGetFieldsResponse); + const actionsMock = { ...actionsClientMock.create(), execute }; + beforeEach(async () => { + jest.restoreAllMocks(); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2019-11-25T21:54:48.952Z'), + })); + }); + + describe('happy path', () => { + test('it gets existing mappings', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getMappings({ + actionsClient: actionsMock, + caseClient: caseClient.client, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + + expect(res).toEqual(mappings[ConnectorTypes.jira]); + }); + test('it creates new mappings', async () => { + const savedObjectsClient = createMockSavedObjectsRepository({ + caseMappingsSavedObject: [], + }); + const caseClient = await createCaseClientWithMockSavedObjectsClient(savedObjectsClient); + const res = await caseClient.client.getMappings({ + actionsClient: actionsMock, + caseClient: caseClient.client, + connectorType: ConnectorTypes.jira, + connectorId: '123', + }); + + expect(res).toEqual(mappings[ConnectorTypes.jira]); + }); + }); +}); diff --git a/x-pack/plugins/case/server/client/configure/mock.ts b/x-pack/plugins/case/server/client/configure/mock.ts new file mode 100644 index 00000000000000..bb57755250ba2c --- /dev/null +++ b/x-pack/plugins/case/server/client/configure/mock.ts @@ -0,0 +1,625 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + ConnectorField, + ConnectorMappingsAttributes, + ConnectorTypes, +} from '../../../common/api/connectors'; +import { + JiraGetFieldsResponse, + ResilientGetFieldsResponse, + ServiceNowGetFieldsResponse, +} from './utils.test'; +interface TestMappings { + [key: string]: ConnectorMappingsAttributes[]; +} +export const mappings: TestMappings = { + [ConnectorTypes.jira]: [ + { + source: 'title', + target: 'summary', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [`${ConnectorTypes.jira}-alt`]: [ + { + source: 'title', + target: 'title', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [ConnectorTypes.resilient]: [ + { + source: 'title', + target: 'name', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], + [ConnectorTypes.servicenow]: [ + { + source: 'title', + target: 'short_description', + action_type: 'overwrite', + }, + { + source: 'description', + target: 'description', + action_type: 'overwrite', + }, + { + source: 'comments', + target: 'comments', + action_type: 'append', + }, + ], +}; + +const jiraFields: JiraGetFieldsResponse = { + summary: { + required: true, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'string', + }, + name: 'Summary', + }, + issuetype: { + required: true, + allowedValues: [ + { + self: 'https://siem-kibana.atlassian.net/rest/api/2/issuetype/10023', + id: '10023', + description: 'A problem or error.', + iconUrl: + 'https://siem-kibana.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype', + name: 'Bug', + subtask: false, + avatarId: 10303, + }, + ], + defaultValue: {}, + schema: { + type: 'issuetype', + }, + name: 'Issue Type', + }, + attachment: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'array', + items: 'attachment', + }, + name: 'Attachment', + }, + duedate: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'date', + }, + name: 'Due date', + }, + description: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'string', + }, + name: 'Description', + }, + project: { + required: true, + allowedValues: [ + { + self: 'https://siem-kibana.atlassian.net/rest/api/2/project/10015', + id: '10015', + key: 'RJ2', + name: 'RJ2', + projectTypeKey: 'business', + simplified: false, + avatarUrls: { + '48x48': + 'https://siem-kibana.atlassian.net/secure/projectavatar?pid=10015&avatarId=10412', + '24x24': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=small&s=small&pid=10015&avatarId=10412', + '16x16': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10015&avatarId=10412', + '32x32': + 'https://siem-kibana.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10015&avatarId=10412', + }, + }, + ], + defaultValue: {}, + schema: { + type: 'project', + }, + name: 'Project', + }, + assignee: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'user', + }, + name: 'Assignee', + }, + labels: { + required: false, + allowedValues: [], + defaultValue: {}, + schema: { + type: 'array', + items: 'string', + }, + name: 'Labels', + }, +}; +const resilientFields: ResilientGetFieldsResponse = [ + { input_type: 'text', name: 'addr', read_only: false, text: 'Address' }, + { + input_type: 'boolean', + name: 'alberta_health_risk_assessment', + read_only: false, + text: 'Alberta Health Risk Assessment', + }, + { input_type: 'number', name: 'hard_liability', read_only: true, text: 'Assessed Liability' }, + { input_type: 'text', name: 'city', read_only: false, text: 'City' }, + { input_type: 'select', name: 'country', read_only: false, text: 'Country/Region' }, + { input_type: 'select_owner', name: 'creator_id', read_only: true, text: 'Created By' }, + { input_type: 'select', name: 'crimestatus_id', read_only: false, text: 'Criminal Activity' }, + { input_type: 'boolean', name: 'data_encrypted', read_only: false, text: 'Data Encrypted' }, + { input_type: 'select', name: 'data_format', read_only: false, text: 'Data Format' }, + { input_type: 'datetimepicker', name: 'end_date', read_only: true, text: 'Date Closed' }, + { input_type: 'datetimepicker', name: 'create_date', read_only: true, text: 'Date Created' }, + { + input_type: 'datetimepicker', + name: 'determined_date', + read_only: false, + text: 'Date Determined', + }, + { + input_type: 'datetimepicker', + name: 'discovered_date', + read_only: false, + required: 'always', + text: 'Date Discovered', + }, + { input_type: 'datetimepicker', name: 'start_date', read_only: false, text: 'Date Occurred' }, + { input_type: 'select', name: 'exposure_dept_id', read_only: false, text: 'Department' }, + { input_type: 'textarea', name: 'description', read_only: false, text: 'Description' }, + { input_type: 'boolean', name: 'employee_involved', read_only: false, text: 'Employee Involved' }, + { input_type: 'boolean', name: 'data_contained', read_only: false, text: 'Exposure Resolved' }, + { input_type: 'select', name: 'exposure_type_id', read_only: false, text: 'Exposure Type' }, + { + input_type: 'multiselect', + name: 'gdpr_breach_circumstances', + read_only: false, + text: 'GDPR Breach Circumstances', + }, + { input_type: 'select', name: 'gdpr_breach_type', read_only: false, text: 'GDPR Breach Type' }, + { + input_type: 'textarea', + name: 'gdpr_breach_type_comment', + read_only: false, + text: 'GDPR Breach Type Comment', + }, + { input_type: 'select', name: 'gdpr_consequences', read_only: false, text: 'GDPR Consequences' }, + { + input_type: 'textarea', + name: 'gdpr_consequences_comment', + read_only: false, + text: 'GDPR Consequences Comment', + }, + { + input_type: 'select', + name: 'gdpr_final_assessment', + read_only: false, + text: 'GDPR Final Assessment', + }, + { + input_type: 'textarea', + name: 'gdpr_final_assessment_comment', + read_only: false, + text: 'GDPR Final Assessment Comment', + }, + { + input_type: 'select', + name: 'gdpr_identification', + read_only: false, + text: 'GDPR Identification', + }, + { + input_type: 'textarea', + name: 'gdpr_identification_comment', + read_only: false, + text: 'GDPR Identification Comment', + }, + { + input_type: 'select', + name: 'gdpr_personal_data', + read_only: false, + text: 'GDPR Personal Data', + }, + { + input_type: 'textarea', + name: 'gdpr_personal_data_comment', + read_only: false, + text: 'GDPR Personal Data Comment', + }, + { + input_type: 'boolean', + name: 'gdpr_subsequent_notification', + read_only: false, + text: 'GDPR Subsequent Notification', + }, + { input_type: 'number', name: 'id', read_only: true, text: 'ID' }, + { input_type: 'boolean', name: 'impact_likely', read_only: false, text: 'Impact Likely' }, + { + input_type: 'boolean', + name: 'ny_impact_likely', + read_only: false, + text: 'Impact Likely for New York', + }, + { + input_type: 'boolean', + name: 'or_impact_likely', + read_only: false, + text: 'Impact Likely for Oregon', + }, + { + input_type: 'boolean', + name: 'wa_impact_likely', + read_only: false, + text: 'Impact Likely for Washington', + }, + { input_type: 'boolean', name: 'confirmed', read_only: false, text: 'Incident Disposition' }, + { input_type: 'multiselect', name: 'incident_type_ids', read_only: false, text: 'Incident Type' }, + { + input_type: 'text', + name: 'exposure_individual_name', + read_only: false, + text: 'Individual Name', + }, + { + input_type: 'select', + name: 'harmstatus_id', + read_only: false, + text: 'Is harm/risk/misuse foreseeable?', + }, + { input_type: 'text', name: 'jurisdiction_name', read_only: false, text: 'Jurisdiction' }, + { + input_type: 'datetimepicker', + name: 'inc_last_modified_date', + read_only: true, + text: 'Last Modified', + }, + { + input_type: 'multiselect', + name: 'gdpr_lawful_data_processing_categories', + read_only: false, + text: 'Lawful Data Processing Categories', + }, + { input_type: 'multiselect_members', name: 'members', read_only: false, text: 'Members' }, + { input_type: 'text', name: 'name', read_only: false, required: 'always', text: 'Name' }, + { input_type: 'boolean', name: 'negative_pr_likely', read_only: false, text: 'Negative PR' }, + { input_type: 'datetimepicker', name: 'due_date', read_only: true, text: 'Next Due Date' }, + { + input_type: 'multiselect', + name: 'nist_attack_vectors', + read_only: false, + text: 'NIST Attack Vectors', + }, + { input_type: 'select', name: 'org_handle', read_only: true, text: 'Organization' }, + { input_type: 'select_owner', name: 'owner_id', read_only: false, text: 'Owner' }, + { input_type: 'select', name: 'phase_id', read_only: true, text: 'Phase' }, + { + input_type: 'select', + name: 'pipeda_other_factors', + read_only: false, + text: 'PIPEDA Other Factors', + }, + { + input_type: 'textarea', + name: 'pipeda_other_factors_comment', + read_only: false, + text: 'PIPEDA Other Factors Comment', + }, + { + input_type: 'select', + name: 'pipeda_overall_assessment', + read_only: false, + text: 'PIPEDA Overall Assessment', + }, + { + input_type: 'textarea', + name: 'pipeda_overall_assessment_comment', + read_only: false, + text: 'PIPEDA Overall Assessment Comment', + }, + { + input_type: 'select', + name: 'pipeda_probability_of_misuse', + read_only: false, + text: 'PIPEDA Probability of Misuse', + }, + { + input_type: 'textarea', + name: 'pipeda_probability_of_misuse_comment', + read_only: false, + text: 'PIPEDA Probability of Misuse Comment', + }, + { + input_type: 'select', + name: 'pipeda_sensitivity_of_pi', + read_only: false, + text: 'PIPEDA Sensitivity of PI', + }, + { + input_type: 'textarea', + name: 'pipeda_sensitivity_of_pi_comment', + read_only: false, + text: 'PIPEDA Sensitivity of PI Comment', + }, + { input_type: 'text', name: 'reporter', read_only: false, text: 'Reporting Individual' }, + { + input_type: 'select', + name: 'resolution_id', + read_only: false, + required: 'close', + text: 'Resolution', + }, + { + input_type: 'textarea', + name: 'resolution_summary', + read_only: false, + required: 'close', + text: 'Resolution Summary', + }, + { input_type: 'select', name: 'gdpr_harm_risk', read_only: false, text: 'Risk of Harm' }, + { input_type: 'select', name: 'severity_code', read_only: false, text: 'Severity' }, + { input_type: 'boolean', name: 'inc_training', read_only: true, text: 'Simulation' }, + { input_type: 'multiselect', name: 'data_source_ids', read_only: false, text: 'Source of Data' }, + { input_type: 'select', name: 'state', read_only: false, text: 'State' }, + { input_type: 'select', name: 'plan_status', read_only: false, text: 'Status' }, + { input_type: 'select', name: 'exposure_vendor_id', read_only: false, text: 'Vendor' }, + { + input_type: 'boolean', + name: 'data_compromised', + read_only: false, + text: 'Was personal information or personal data involved?', + }, + { + input_type: 'select', + name: 'workspace', + read_only: false, + required: 'always', + text: 'Workspace', + }, + { input_type: 'text', name: 'zip', read_only: false, text: 'Zip' }, +]; +const serviceNowFields: ServiceNowGetFieldsResponse = [ + { + column_label: 'Approval', + mandatory: 'false', + max_length: '40', + element: 'approval', + }, + { + column_label: 'Close notes', + mandatory: 'false', + max_length: '4000', + element: 'close_notes', + }, + { + column_label: 'Contact type', + mandatory: 'false', + max_length: '40', + element: 'contact_type', + }, + { + column_label: 'Correlation display', + mandatory: 'false', + max_length: '100', + element: 'correlation_display', + }, + { + column_label: 'Correlation ID', + mandatory: 'false', + max_length: '100', + element: 'correlation_id', + }, + { + column_label: 'Description', + mandatory: 'false', + max_length: '4000', + element: 'description', + }, + { + column_label: 'Number', + mandatory: 'false', + max_length: '40', + element: 'number', + }, + { + column_label: 'Short description', + mandatory: 'false', + max_length: '160', + element: 'short_description', + }, + { + column_label: 'Created by', + mandatory: 'false', + max_length: '40', + element: 'sys_created_by', + }, + { + column_label: 'Updated by', + mandatory: 'false', + max_length: '40', + element: 'sys_updated_by', + }, + { + column_label: 'Upon approval', + mandatory: 'false', + max_length: '40', + element: 'upon_approval', + }, + { + column_label: 'Upon reject', + mandatory: 'false', + max_length: '40', + element: 'upon_reject', + }, +]; +interface FormatFieldsTestData { + expected: ConnectorField[]; + fields: JiraGetFieldsResponse | ResilientGetFieldsResponse | ServiceNowGetFieldsResponse; + type: ConnectorTypes; +} +export const formatFieldsTestData: FormatFieldsTestData[] = [ + { + expected: [ + { id: 'summary', name: 'Summary', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + fields: jiraFields, + type: ConnectorTypes.jira, + }, + { + expected: [ + { id: 'addr', name: 'Address', required: false, type: 'text' }, + { id: 'city', name: 'City', required: false, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'textarea' }, + { + id: 'gdpr_breach_type_comment', + name: 'GDPR Breach Type Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_consequences_comment', + name: 'GDPR Consequences Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_final_assessment_comment', + name: 'GDPR Final Assessment Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_identification_comment', + name: 'GDPR Identification Comment', + required: false, + type: 'textarea', + }, + { + id: 'gdpr_personal_data_comment', + name: 'GDPR Personal Data Comment', + required: false, + type: 'textarea', + }, + { id: 'exposure_individual_name', name: 'Individual Name', required: false, type: 'text' }, + { id: 'jurisdiction_name', name: 'Jurisdiction', required: false, type: 'text' }, + { id: 'name', name: 'Name', required: true, type: 'text' }, + { + id: 'pipeda_other_factors_comment', + name: 'PIPEDA Other Factors Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_overall_assessment_comment', + name: 'PIPEDA Overall Assessment Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_probability_of_misuse_comment', + name: 'PIPEDA Probability of Misuse Comment', + required: false, + type: 'textarea', + }, + { + id: 'pipeda_sensitivity_of_pi_comment', + name: 'PIPEDA Sensitivity of PI Comment', + required: false, + type: 'textarea', + }, + { id: 'reporter', name: 'Reporting Individual', required: false, type: 'text' }, + { id: 'resolution_summary', name: 'Resolution Summary', required: false, type: 'textarea' }, + { id: 'zip', name: 'Zip', required: false, type: 'text' }, + ], + fields: resilientFields, + type: ConnectorTypes.resilient, + }, + { + expected: [ + { id: 'approval', name: 'Approval', required: false, type: 'text' }, + { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, + { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, + { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, + { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'textarea' }, + { id: 'number', name: 'Number', required: false, type: 'text' }, + { id: 'short_description', name: 'Short description', required: false, type: 'text' }, + { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, + { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, + { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, + { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, + ], + fields: serviceNowFields, + type: ConnectorTypes.servicenow, + }, +]; +export const mockGetFieldsResponse = { + status: 'ok', + data: jiraFields, + actionId: '123', +}; + +export const actionsErrResponse = { + status: 'error', + serviceMessage: 'this is an actions error', +}; diff --git a/x-pack/plugins/case/server/client/configure/utils.test.ts b/x-pack/plugins/case/server/client/configure/utils.test.ts index 91c8259cb2c555..f4f0e077425218 100644 --- a/x-pack/plugins/case/server/client/configure/utils.test.ts +++ b/x-pack/plugins/case/server/client/configure/utils.test.ts @@ -4,535 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { +export { JiraGetFieldsResponse, ResilientGetFieldsResponse, ServiceNowGetFieldsResponse, } from '../../../../actions/server/types'; -import { formatFields } from './utils'; +import { createDefaultMapping, formatFields } from './utils'; import { ConnectorTypes } from '../../../common/api/connectors'; +import { mappings, formatFieldsTestData } from './mock'; -const jiraFields: JiraGetFieldsResponse = { - summary: { - required: true, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Summary', - }, - issuetype: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/issuetype/10023', - id: '10023', - description: 'A problem or error.', - iconUrl: - 'https://siem-kibana.atlassian.net/secure/viewavatar?size=medium&avatarId=10303&avatarType=issuetype', - name: 'Bug', - subtask: false, - avatarId: 10303, - }, - ], - defaultValue: {}, - schema: { - type: 'issuetype', - }, - name: 'Issue Type', - }, - attachment: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'attachment', - }, - name: 'Attachment', - }, - duedate: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'date', - }, - name: 'Due date', - }, - description: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'string', - }, - name: 'Description', - }, - project: { - required: true, - allowedValues: [ - { - self: 'https://siem-kibana.atlassian.net/rest/api/2/project/10015', - id: '10015', - key: 'RJ2', - name: 'RJ2', - projectTypeKey: 'business', - simplified: false, - avatarUrls: { - '48x48': - 'https://siem-kibana.atlassian.net/secure/projectavatar?pid=10015&avatarId=10412', - '24x24': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=small&s=small&pid=10015&avatarId=10412', - '16x16': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=xsmall&s=xsmall&pid=10015&avatarId=10412', - '32x32': - 'https://siem-kibana.atlassian.net/secure/projectavatar?size=medium&s=medium&pid=10015&avatarId=10412', - }, - }, - ], - defaultValue: {}, - schema: { - type: 'project', - }, - name: 'Project', - }, - assignee: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'user', - }, - name: 'Assignee', - }, - labels: { - required: false, - allowedValues: [], - defaultValue: {}, - schema: { - type: 'array', - items: 'string', - }, - name: 'Labels', - }, -}; -const resilientFields: ResilientGetFieldsResponse = [ - { input_type: 'text', name: 'addr', read_only: false, text: 'Address' }, - { - input_type: 'boolean', - name: 'alberta_health_risk_assessment', - read_only: false, - text: 'Alberta Health Risk Assessment', - }, - { input_type: 'number', name: 'hard_liability', read_only: true, text: 'Assessed Liability' }, - { input_type: 'text', name: 'city', read_only: false, text: 'City' }, - { input_type: 'select', name: 'country', read_only: false, text: 'Country/Region' }, - { input_type: 'select_owner', name: 'creator_id', read_only: true, text: 'Created By' }, - { input_type: 'select', name: 'crimestatus_id', read_only: false, text: 'Criminal Activity' }, - { input_type: 'boolean', name: 'data_encrypted', read_only: false, text: 'Data Encrypted' }, - { input_type: 'select', name: 'data_format', read_only: false, text: 'Data Format' }, - { input_type: 'datetimepicker', name: 'end_date', read_only: true, text: 'Date Closed' }, - { input_type: 'datetimepicker', name: 'create_date', read_only: true, text: 'Date Created' }, - { - input_type: 'datetimepicker', - name: 'determined_date', - read_only: false, - text: 'Date Determined', - }, - { - input_type: 'datetimepicker', - name: 'discovered_date', - read_only: false, - required: 'always', - text: 'Date Discovered', - }, - { input_type: 'datetimepicker', name: 'start_date', read_only: false, text: 'Date Occurred' }, - { input_type: 'select', name: 'exposure_dept_id', read_only: false, text: 'Department' }, - { input_type: 'textarea', name: 'description', read_only: false, text: 'Description' }, - { input_type: 'boolean', name: 'employee_involved', read_only: false, text: 'Employee Involved' }, - { input_type: 'boolean', name: 'data_contained', read_only: false, text: 'Exposure Resolved' }, - { input_type: 'select', name: 'exposure_type_id', read_only: false, text: 'Exposure Type' }, - { - input_type: 'multiselect', - name: 'gdpr_breach_circumstances', - read_only: false, - text: 'GDPR Breach Circumstances', - }, - { input_type: 'select', name: 'gdpr_breach_type', read_only: false, text: 'GDPR Breach Type' }, - { - input_type: 'textarea', - name: 'gdpr_breach_type_comment', - read_only: false, - text: 'GDPR Breach Type Comment', - }, - { input_type: 'select', name: 'gdpr_consequences', read_only: false, text: 'GDPR Consequences' }, - { - input_type: 'textarea', - name: 'gdpr_consequences_comment', - read_only: false, - text: 'GDPR Consequences Comment', - }, - { - input_type: 'select', - name: 'gdpr_final_assessment', - read_only: false, - text: 'GDPR Final Assessment', - }, - { - input_type: 'textarea', - name: 'gdpr_final_assessment_comment', - read_only: false, - text: 'GDPR Final Assessment Comment', - }, - { - input_type: 'select', - name: 'gdpr_identification', - read_only: false, - text: 'GDPR Identification', - }, - { - input_type: 'textarea', - name: 'gdpr_identification_comment', - read_only: false, - text: 'GDPR Identification Comment', - }, - { - input_type: 'select', - name: 'gdpr_personal_data', - read_only: false, - text: 'GDPR Personal Data', - }, - { - input_type: 'textarea', - name: 'gdpr_personal_data_comment', - read_only: false, - text: 'GDPR Personal Data Comment', - }, - { - input_type: 'boolean', - name: 'gdpr_subsequent_notification', - read_only: false, - text: 'GDPR Subsequent Notification', - }, - { input_type: 'number', name: 'id', read_only: true, text: 'ID' }, - { input_type: 'boolean', name: 'impact_likely', read_only: false, text: 'Impact Likely' }, - { - input_type: 'boolean', - name: 'ny_impact_likely', - read_only: false, - text: 'Impact Likely for New York', - }, - { - input_type: 'boolean', - name: 'or_impact_likely', - read_only: false, - text: 'Impact Likely for Oregon', - }, - { - input_type: 'boolean', - name: 'wa_impact_likely', - read_only: false, - text: 'Impact Likely for Washington', - }, - { input_type: 'boolean', name: 'confirmed', read_only: false, text: 'Incident Disposition' }, - { input_type: 'multiselect', name: 'incident_type_ids', read_only: false, text: 'Incident Type' }, - { - input_type: 'text', - name: 'exposure_individual_name', - read_only: false, - text: 'Individual Name', - }, - { - input_type: 'select', - name: 'harmstatus_id', - read_only: false, - text: 'Is harm/risk/misuse foreseeable?', - }, - { input_type: 'text', name: 'jurisdiction_name', read_only: false, text: 'Jurisdiction' }, - { - input_type: 'datetimepicker', - name: 'inc_last_modified_date', - read_only: true, - text: 'Last Modified', - }, - { - input_type: 'multiselect', - name: 'gdpr_lawful_data_processing_categories', - read_only: false, - text: 'Lawful Data Processing Categories', - }, - { input_type: 'multiselect_members', name: 'members', read_only: false, text: 'Members' }, - { input_type: 'text', name: 'name', read_only: false, required: 'always', text: 'Name' }, - { input_type: 'boolean', name: 'negative_pr_likely', read_only: false, text: 'Negative PR' }, - { input_type: 'datetimepicker', name: 'due_date', read_only: true, text: 'Next Due Date' }, - { - input_type: 'multiselect', - name: 'nist_attack_vectors', - read_only: false, - text: 'NIST Attack Vectors', - }, - { input_type: 'select', name: 'org_handle', read_only: true, text: 'Organization' }, - { input_type: 'select_owner', name: 'owner_id', read_only: false, text: 'Owner' }, - { input_type: 'select', name: 'phase_id', read_only: true, text: 'Phase' }, - { - input_type: 'select', - name: 'pipeda_other_factors', - read_only: false, - text: 'PIPEDA Other Factors', - }, - { - input_type: 'textarea', - name: 'pipeda_other_factors_comment', - read_only: false, - text: 'PIPEDA Other Factors Comment', - }, - { - input_type: 'select', - name: 'pipeda_overall_assessment', - read_only: false, - text: 'PIPEDA Overall Assessment', - }, - { - input_type: 'textarea', - name: 'pipeda_overall_assessment_comment', - read_only: false, - text: 'PIPEDA Overall Assessment Comment', - }, - { - input_type: 'select', - name: 'pipeda_probability_of_misuse', - read_only: false, - text: 'PIPEDA Probability of Misuse', - }, - { - input_type: 'textarea', - name: 'pipeda_probability_of_misuse_comment', - read_only: false, - text: 'PIPEDA Probability of Misuse Comment', - }, - { - input_type: 'select', - name: 'pipeda_sensitivity_of_pi', - read_only: false, - text: 'PIPEDA Sensitivity of PI', - }, - { - input_type: 'textarea', - name: 'pipeda_sensitivity_of_pi_comment', - read_only: false, - text: 'PIPEDA Sensitivity of PI Comment', - }, - { input_type: 'text', name: 'reporter', read_only: false, text: 'Reporting Individual' }, - { - input_type: 'select', - name: 'resolution_id', - read_only: false, - required: 'close', - text: 'Resolution', - }, - { - input_type: 'textarea', - name: 'resolution_summary', - read_only: false, - required: 'close', - text: 'Resolution Summary', - }, - { input_type: 'select', name: 'gdpr_harm_risk', read_only: false, text: 'Risk of Harm' }, - { input_type: 'select', name: 'severity_code', read_only: false, text: 'Severity' }, - { input_type: 'boolean', name: 'inc_training', read_only: true, text: 'Simulation' }, - { input_type: 'multiselect', name: 'data_source_ids', read_only: false, text: 'Source of Data' }, - { input_type: 'select', name: 'state', read_only: false, text: 'State' }, - { input_type: 'select', name: 'plan_status', read_only: false, text: 'Status' }, - { input_type: 'select', name: 'exposure_vendor_id', read_only: false, text: 'Vendor' }, - { - input_type: 'boolean', - name: 'data_compromised', - read_only: false, - text: 'Was personal information or personal data involved?', - }, - { - input_type: 'select', - name: 'workspace', - read_only: false, - required: 'always', - text: 'Workspace', - }, - { input_type: 'text', name: 'zip', read_only: false, text: 'Zip' }, -]; -const serviceNowFields: ServiceNowGetFieldsResponse = [ - { - column_label: 'Approval', - mandatory: 'false', - max_length: '40', - element: 'approval', - }, - { - column_label: 'Close notes', - mandatory: 'false', - max_length: '4000', - element: 'close_notes', - }, - { - column_label: 'Contact type', - mandatory: 'false', - max_length: '40', - element: 'contact_type', - }, - { - column_label: 'Correlation display', - mandatory: 'false', - max_length: '100', - element: 'correlation_display', - }, - { - column_label: 'Correlation ID', - mandatory: 'false', - max_length: '100', - element: 'correlation_id', - }, - { - column_label: 'Description', - mandatory: 'false', - max_length: '4000', - element: 'description', - }, - { - column_label: 'Number', - mandatory: 'false', - max_length: '40', - element: 'number', - }, - { - column_label: 'Short description', - mandatory: 'false', - max_length: '160', - element: 'short_description', - }, - { - column_label: 'Created by', - mandatory: 'false', - max_length: '40', - element: 'sys_created_by', - }, - { - column_label: 'Updated by', - mandatory: 'false', - max_length: '40', - element: 'sys_updated_by', - }, - { - column_label: 'Upon approval', - mandatory: 'false', - max_length: '40', - element: 'upon_approval', - }, - { - column_label: 'Upon reject', - mandatory: 'false', - max_length: '40', - element: 'upon_reject', - }, -]; - -const formatFieldsTestData = [ - { - expected: [ - { id: 'summary', name: 'Summary', required: true, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'text' }, - ], - fields: jiraFields, - type: ConnectorTypes.jira, - }, - { - expected: [ - { id: 'addr', name: 'Address', required: false, type: 'text' }, - { id: 'city', name: 'City', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { - id: 'gdpr_breach_type_comment', - name: 'GDPR Breach Type Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_consequences_comment', - name: 'GDPR Consequences Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_final_assessment_comment', - name: 'GDPR Final Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_identification_comment', - name: 'GDPR Identification Comment', - required: false, - type: 'textarea', - }, - { - id: 'gdpr_personal_data_comment', - name: 'GDPR Personal Data Comment', - required: false, - type: 'textarea', - }, - { id: 'exposure_individual_name', name: 'Individual Name', required: false, type: 'text' }, - { id: 'jurisdiction_name', name: 'Jurisdiction', required: false, type: 'text' }, - { id: 'name', name: 'Name', required: true, type: 'text' }, - { - id: 'pipeda_other_factors_comment', - name: 'PIPEDA Other Factors Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_overall_assessment_comment', - name: 'PIPEDA Overall Assessment Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_probability_of_misuse_comment', - name: 'PIPEDA Probability of Misuse Comment', - required: false, - type: 'textarea', - }, - { - id: 'pipeda_sensitivity_of_pi_comment', - name: 'PIPEDA Sensitivity of PI Comment', - required: false, - type: 'textarea', - }, - { id: 'reporter', name: 'Reporting Individual', required: false, type: 'text' }, - { id: 'resolution_summary', name: 'Resolution Summary', required: false, type: 'textarea' }, - { id: 'zip', name: 'Zip', required: false, type: 'text' }, - ], - fields: resilientFields, - type: ConnectorTypes.resilient, - }, - { - expected: [ - { id: 'approval', name: 'Approval', required: false, type: 'text' }, - { id: 'close_notes', name: 'Close notes', required: false, type: 'textarea' }, - { id: 'contact_type', name: 'Contact type', required: false, type: 'text' }, - { id: 'correlation_display', name: 'Correlation display', required: false, type: 'text' }, - { id: 'correlation_id', name: 'Correlation ID', required: false, type: 'text' }, - { id: 'description', name: 'Description', required: false, type: 'textarea' }, - { id: 'number', name: 'Number', required: false, type: 'text' }, - { id: 'short_description', name: 'Short description', required: false, type: 'text' }, - { id: 'sys_created_by', name: 'Created by', required: false, type: 'text' }, - { id: 'sys_updated_by', name: 'Updated by', required: false, type: 'text' }, - { id: 'upon_approval', name: 'Upon approval', required: false, type: 'text' }, - { id: 'upon_reject', name: 'Upon reject', required: false, type: 'text' }, - ], - fields: serviceNowFields, - type: ConnectorTypes.servicenow, - }, -]; describe('client/configure/utils', () => { describe('formatFields', () => { formatFieldsTestData.forEach(({ expected, fields, type }) => { @@ -542,4 +22,23 @@ describe('client/configure/utils', () => { }); }); }); + describe('createDefaultMapping', () => { + formatFieldsTestData.forEach(({ expected, fields, type }) => { + it(`normalizes ${type} fields to common type ConnectorField`, () => { + const result = createDefaultMapping(expected, type); + expect(result).toEqual(mappings[type]); + }); + }); + it(`if the preferredField is not required and another field is, use the other field`, () => { + const result = createDefaultMapping( + [ + { id: 'summary', name: 'Summary', required: false, type: 'text' }, + { id: 'title', name: 'Title', required: true, type: 'text' }, + { id: 'description', name: 'Description', required: false, type: 'text' }, + ], + ConnectorTypes.jira + ); + expect(result).toEqual(mappings[`${ConnectorTypes.jira}-alt`]); + }); + }); }); diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts index 45ccb4f2c539fa..0d78bceeaf2fa9 100644 --- a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObject, SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObject } from 'kibana/server'; import { CaseStatuses, CommentAttributes, @@ -14,8 +14,8 @@ import { ESCaseAttributes, ESCasesConfigureAttributes, } from '../../../../common/api'; -import { mappings } from '../cases/configure/mock'; import { CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT } from '../../../saved_object_types'; +import { mappings } from '../../../client/configure/mock'; export const mockCases: Array> = [ { @@ -381,31 +381,13 @@ export const mockCaseConfigure: Array> = }, ]; -export const mockCaseConfigureFind: Array> = [ - { - page: 1, - per_page: 5, - total: mockCaseConfigure.length, - saved_objects: [{ ...mockCaseConfigure[0], score: 0 }], - }, -]; - export const mockCaseMappings: Array> = [ { type: CASE_CONNECTOR_MAPPINGS_SAVED_OBJECT, id: 'mock-mappings-1', attributes: { - mappings, + mappings: mappings[ConnectorTypes.jira], }, references: [], }, ]; - -export const mockCaseMappingsFind: Array> = [ - { - page: 1, - per_page: 5, - total: mockCaseConfigure.length, - saved_objects: [{ ...mockCaseMappings[0], score: 0 }], - }, -]; diff --git a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts index b6da21927e342e..efc3b6044a8045 100644 --- a/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts +++ b/x-pack/plugins/case/server/routes/api/__mocks__/request_responses.ts @@ -3,9 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { CasePostRequest, CasesConfigureRequest, ConnectorTypes } from '../../../../common/api'; +import { + CasePostRequest, + CasesConfigureRequest, + ConnectorTypes, + PostPushRequest, +} from '../../../../common/api'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { FindActionResult } from '../../../../../actions/server/types'; +import { params } from '../cases/configure/mock'; export const newCase: CasePostRequest = { title: 'My new case', @@ -76,3 +82,19 @@ export const newConfiguration: CasesConfigureRequest = { }, closure_type: 'close-by-pushing', }; + +export const newPostPushRequest: PostPushRequest = { + params: params[ConnectorTypes.jira], + connector_type: ConnectorTypes.jira, +}; + +export const executePushResponse = { + status: 'ok', + data: { + title: 'RJ2-200', + id: '10663', + pushedDate: '2020-12-17T00:32:40.738Z', + url: 'https://siem-kibana.atlassian.net/browse/RJ2-200', + comments: [], + }, +}; diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts index d75f42f6e486bc..87e165f8e00147 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/get_configure.test.ts @@ -17,7 +17,8 @@ import { import { initGetCaseConfigure } from './get_configure'; import { CASE_CONFIGURE_URL } from '../../../../../common/constants'; -import { mappings } from './mock'; +import { mappings } from '../../../../client/configure/mock'; +import { ConnectorTypes } from '../../../../../common/api/connectors'; describe('GET configuration', () => { let routeHandler: RequestHandler; @@ -42,7 +43,7 @@ describe('GET configuration', () => { expect(res.status).toEqual(200); expect(res.payload).toEqual({ ...mockCaseConfigure[0].attributes, - mappings, + mappings: mappings[ConnectorTypes.jira], version: mockCaseConfigure[0].version, }); }); @@ -76,7 +77,7 @@ describe('GET configuration', () => { email: 'testemail@elastic.co', username: 'elastic', }, - mappings, + mappings: mappings[ConnectorTypes.jira], updated_at: '2020-04-09T09:43:51.778Z', updated_by: { full_name: 'elastic', diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts b/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts deleted file mode 100644 index c9b8e671b7df82..00000000000000 --- a/x-pack/plugins/case/server/routes/api/cases/configure/get_fields.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Boom from '@hapi/boom'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { RouteDeps } from '../../types'; -import { escapeHatch, wrapError } from '../../utils'; - -import { CASE_CONFIGURE_CONNECTOR_DETAILS_URL } from '../../../../../common/constants'; -import { - ConnectorRequestParamsRt, - GetFieldsRequestQueryRt, - throwErrors, -} from '../../../../../common/api'; - -export function initCaseConfigureGetFields({ router }: RouteDeps) { - router.get( - { - path: CASE_CONFIGURE_CONNECTOR_DETAILS_URL, - validate: { - params: escapeHatch, - query: escapeHatch, - }, - }, - async (context, request, response) => { - try { - if (!context.case) { - throw Boom.badRequest('RouteHandlerContext is not registered for cases'); - } - const query = pipe( - GetFieldsRequestQueryRt.decode(request.query), - fold(throwErrors(Boom.badRequest), identity) - ); - const params = pipe( - ConnectorRequestParamsRt.decode(request.params), - fold(throwErrors(Boom.badRequest), identity) - ); - - const caseClient = context.case.getCaseClient(); - - const connectorType = query.connector_type; - if (connectorType == null) { - throw Boom.illegal('no connectorType value provided'); - } - - const actionsClient = await context.actions?.getActionsClient(); - if (actionsClient == null) { - throw Boom.notFound('Action client have not been found'); - } - - const res = await caseClient.getFields({ - actionsClient, - connectorId: params.connector_id, - connectorType, - }); - - return response.ok({ - body: res.fields, - }); - } catch (error) { - return response.customError(wrapError(error)); - } - } - ); -} diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts index ed8b2088646115..771b09cec2a359 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/mock.ts @@ -7,6 +7,7 @@ import { ServiceConnectorCaseParams, ServiceConnectorCommentParams, ConnectorMappingsAttributes, + ConnectorTypes, } from '../../../../../common/api/connectors'; export const updateUser = { updatedAt: '2020-03-13T08:34:53.450Z', @@ -24,16 +25,36 @@ export const comment: ServiceConnectorCommentParams = { ...entity, }; export const defaultPipes = ['informationCreated']; -export const params = { +const basicParams = { comments: [comment], description: 'a description', - impact: '3', - savedObjectId: '1231231231232', - severity: '1', title: 'a title', - urgency: '2', - ...entity, -} as ServiceConnectorCaseParams; + savedObjectId: '1231231231232', + externalId: null, +}; +export const params = { + [ConnectorTypes.jira]: { + ...basicParams, + issueType: '10003', + priority: 'Highest', + parent: '5002', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.resilient]: { + ...basicParams, + incidentTypes: ['10003'], + severityCode: '1', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.servicenow]: { + ...basicParams, + impact: '3', + severity: '1', + urgency: '2', + ...entity, + } as ServiceConnectorCaseParams, + [ConnectorTypes.none]: {}, +}; export const mappings: ConnectorMappingsAttributes[] = [ { source: 'title', diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts new file mode 100644 index 00000000000000..ff0939fdcce1fa --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.test.ts @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kibanaResponseFactory, RequestHandler, RequestHandlerContext } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseMappings, +} from '../../__fixtures__'; + +import { initPostPushToService } from './post_push_to_service'; +import { executePushResponse, newPostPushRequest } from '../../__mocks__/request_responses'; +import { CASE_CONFIGURE_PUSH_URL } from '../../../../../common/constants'; + +describe('Post push to service', () => { + let routeHandler: RequestHandler; + const req = httpServerMock.createKibanaRequest({ + path: `${CASE_CONFIGURE_PUSH_URL}`, + method: 'post', + params: { + connector_id: '666', + }, + body: newPostPushRequest, + }); + let context: RequestHandlerContext; + beforeAll(async () => { + routeHandler = await createRoute(initPostPushToService, 'post'); + const spyOnDate = jest.spyOn(global, 'Date') as jest.SpyInstance<{}, []>; + spyOnDate.mockImplementation(() => ({ + toISOString: jest.fn().mockReturnValue('2020-04-09T09:43:51.778Z'), + })); + context = await createRouteContext( + createMockSavedObjectsRepository({ + caseMappingsSavedObject: mockCaseMappings, + }) + ); + }); + + it('Happy path - posts success', async () => { + const betterContext = ({ + ...context, + actions: { + ...context.actions, + getActionsClient: () => { + const actions = context!.actions!.getActionsClient(); + return { + ...actions, + execute: jest.fn().mockImplementation(({ actionId }) => { + return { + status: 'ok', + data: { + title: 'RJ2-200', + id: '10663', + pushedDate: '2020-12-17T00:32:40.738Z', + url: 'https://siem-kibana.atlassian.net/browse/RJ2-200', + comments: [], + }, + actionId, + }; + }), + }; + }, + }, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + + expect(res.status).toEqual(200); + expect(res.payload).toEqual({ + ...executePushResponse, + actionId: '666', + }); + }); + it('Unhappy path - context case missing', async () => { + const betterContext = ({ + ...context, + case: null, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + expect(res.status).toEqual(400); + expect(res.payload.isBoom).toBeTruthy(); + expect(res.payload.output.payload.message).toEqual( + 'RouteHandlerContext is not registered for cases' + ); + }); + it('Unhappy path - context actions missing', async () => { + const betterContext = ({ + ...context, + actions: null, + } as unknown) as RequestHandlerContext; + + const res = await routeHandler(betterContext, req, kibanaResponseFactory); + expect(res.status).toEqual(404); + expect(res.payload.isBoom).toBeTruthy(); + expect(res.payload.output.payload.message).toEqual('Action client have not been found'); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts index 9c4c06c5f4e186..fb7a91d0463136 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/post_push_to_service.ts @@ -19,7 +19,7 @@ import { } from '../../../../../common/api'; import { mapIncident } from './utils'; -export function initPostPushToService({ router, connectorMappingsService }: RouteDeps) { +export function initPostPushToService({ router }: RouteDeps) { router.post( { path: CASE_CONFIGURE_PUSH_URL, diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts index d2ecdf61c882d7..d1f8391ad082a0 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.test.ts @@ -5,25 +5,31 @@ */ import { + mapIncident, prepareFieldsForTransformation, - transformFields, + serviceFormatter, transformComments, transformers, + transformFields, } from './utils'; -import { comment as commentObj, defaultPipes, mappings, params, updateUser } from './mock'; +import { comment as commentObj, mappings, defaultPipes, params, updateUser } from './mock'; import { - ServiceConnectorCaseParams, + ConnectorTypes, ExternalServiceParams, Incident, + ServiceConnectorCaseParams, } from '../../../../../common/api/connectors'; +import { actionsClientMock } from '../../../../../../actions/server/actions_client.mock'; +import { mappings as mappingsMock } from '../../../../client/configure/mock'; const formatComment = { commentId: commentObj.commentId, comment: commentObj.comment }; +const serviceNowParams = params[ConnectorTypes.servicenow] as ServiceConnectorCaseParams; describe('api/cases/configure/utils', () => { describe('prepareFieldsForTransformation', () => { test('prepare fields with defaults', () => { const res = prepareFieldsForTransformation({ defaultPipes, - params, + params: serviceNowParams, mappings, }); expect(res).toEqual([ @@ -46,7 +52,7 @@ describe('api/cases/configure/utils', () => { const res = prepareFieldsForTransformation({ defaultPipes: ['myTestPipe'], mappings, - params, + params: serviceNowParams, }); expect(res).toEqual([ { @@ -69,11 +75,11 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params, + params: serviceNowParams, }); const res = transformFields({ - params, + params: serviceNowParams, fields, }); @@ -85,14 +91,14 @@ describe('api/cases/configure/utils', () => { test('transform fields for update correctly', () => { const fields = prepareFieldsForTransformation({ - params, + params: serviceNowParams, mappings, defaultPipes: ['informationUpdated'], }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, updatedAt: '2020-03-15T08:34:53.450Z', updatedBy: { username: 'anotherUser', @@ -114,13 +120,13 @@ describe('api/cases/configure/utils', () => { test('add newline character to description', () => { const fields = prepareFieldsForTransformation({ - params, + params: serviceNowParams, mappings, defaultPipes: ['informationUpdated'], }); const res = transformFields({ - params, + params: serviceNowParams, fields, currentIncident: { short_description: 'first title', @@ -134,12 +140,12 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes, mappings, - params, + params: serviceNowParams, }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, createdBy: { fullName: '', username: 'elastic' }, }, fields, @@ -155,12 +161,12 @@ describe('api/cases/configure/utils', () => { const fields = prepareFieldsForTransformation({ defaultPipes: ['informationUpdated'], mappings, - params, + params: serviceNowParams, }); const res = transformFields({ params: { - ...params, + ...serviceNowParams, updatedAt: '2020-03-15T08:34:53.450Z', updatedBy: { username: 'anotherUser', fullName: '' }, }, @@ -382,4 +388,142 @@ describe('api/cases/configure/utils', () => { }); }); }); + describe('mapIncident', () => { + let actionsMock = actionsClientMock.create(); + it('maps an external incident', async () => { + const res = await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + serviceNowParams + ); + expect(res).toEqual({ + incident: { + description: 'a description (created at 2020-03-13T08:34:53.450Z by Elastic User)', + externalId: null, + impact: '3', + severity: '1', + short_description: 'a title (created at 2020-03-13T08:34:53.450Z by Elastic User)', + urgency: '2', + }, + comments: [ + { + comment: 'first comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + commentId: 'b5b4c4d0-574e-11ea-9e2e-21b90f8a9631', + }, + ], + }); + }); + it('throws error if invalid service', async () => { + await mapIncident( + actionsMock, + '123', + 'invalid', + mappingsMock[ConnectorTypes.servicenow], + serviceNowParams + ).catch((e) => { + expect(e).not.toBeNull(); + expect(e).toEqual(new Error(`Invalid service`)); + }); + }); + it('updates an existing incident', async () => { + const existingIncidentData = { + description: 'fun description', + impact: '3', + severity: '3', + short_description: 'fun title', + urgency: '3', + }; + const execute = jest.fn().mockReturnValue(existingIncidentData); + actionsMock = { ...actionsMock, execute }; + const res = await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + { ...serviceNowParams, externalId: '123' } + ); + expect(res).toEqual({ + incident: { + description: 'a description (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + externalId: '123', + impact: '3', + severity: '1', + short_description: 'a title (updated at 2020-03-13T08:34:53.450Z by Elastic User)', + urgency: '2', + }, + comments: [ + { + comment: 'first comment (added at 2020-03-13T08:34:53.450Z by Elastic User)', + commentId: 'b5b4c4d0-574e-11ea-9e2e-21b90f8a9631', + }, + ], + }); + }); + it('throws error when existing incident throws', async () => { + const execute = jest.fn().mockImplementation(() => { + throw new Error('exception'); + }); + actionsMock = { ...actionsMock, execute }; + await mapIncident( + actionsMock, + '123', + ConnectorTypes.servicenow, + mappingsMock[ConnectorTypes.servicenow], + { ...serviceNowParams, externalId: '123' } + ).catch((e) => { + expect(e).not.toBeNull(); + expect(e).toEqual( + new Error( + `Retrieving Incident by id 123 from ServiceNow failed with exception: Error: exception` + ) + ); + }); + }); + }); + + const connectors = [ + { + name: ConnectorTypes.jira, + result: { + incident: { + issueType: '10003', + parent: '5002', + priority: 'Highest', + }, + thirdPartyName: 'Jira', + }, + }, + { + name: ConnectorTypes.resilient, + result: { + incident: { + incidentTypes: ['10003'], + severityCode: '1', + }, + thirdPartyName: 'Resilient', + }, + }, + { + name: ConnectorTypes.servicenow, + result: { + incident: { + impact: '3', + severity: '1', + urgency: '2', + }, + thirdPartyName: 'ServiceNow', + }, + }, + ]; + describe('serviceFormatter', () => { + connectors.forEach((c) => + it(`formats ${c.name}`, () => { + const caseParams = params[c.name] as ServiceConnectorCaseParams; + const res = serviceFormatter(c.name, caseParams); + expect(res).toEqual(c.result); + }) + ); + }); }); diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts index b8a37661fe9f77..89109af4cecb9d 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts @@ -25,9 +25,7 @@ import { TransformerArgs, TransformFieldsArgs, } from '../../../../../common/api'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ActionsClient } from '../../../../../../actions/server/actions_client'; - +import { ActionsClient } from '../../../../../../actions/server'; export const mapIncident = async ( actionsClient: ActionsClient, connectorId: string, @@ -59,13 +57,11 @@ export const mapIncident = async ( ); } } - const fields = prepareFieldsForTransformation({ defaultPipes, mappings, params, }); - const transformedFields = transformFields< ServiceConnectorCaseParams, ExternalServiceParams, diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts index 587e43b218f446..15817b425021e9 100644 --- a/x-pack/plugins/case/server/routes/api/index.ts +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -25,7 +25,6 @@ import { initPostCommentApi } from './cases/comments/post_comment'; import { initCaseConfigureGetActionConnector } from './cases/configure/get_connectors'; import { initGetCaseConfigure } from './cases/configure/get_configure'; -import { initCaseConfigureGetFields } from './cases/configure/get_fields'; import { initPatchCaseConfigure } from './cases/configure/patch_configure'; import { initPostCaseConfigure } from './cases/configure/post_configure'; import { initPostPushToService } from './cases/configure/post_push_to_service'; @@ -54,7 +53,6 @@ export function initCaseApi(deps: RouteDeps) { initGetCaseConfigure(deps); initPatchCaseConfigure(deps); initPostCaseConfigure(deps); - initCaseConfigureGetFields(deps); initPostPushToService(deps); // Reporters initGetReportersApi(deps); diff --git a/x-pack/plugins/security_solution/public/cases/containers/api.ts b/x-pack/plugins/security_solution/public/cases/containers/api.ts index ef1e35b8ceb4b2..07f7391ca94d90 100644 --- a/x-pack/plugins/security_solution/public/cases/containers/api.ts +++ b/x-pack/plugins/security_solution/public/cases/containers/api.ts @@ -16,7 +16,6 @@ import { CaseUserActionsResponse, CommentRequest, CommentType, - ConnectorField, ServiceConnectorCaseParams, ServiceConnectorCaseResponse, User, @@ -24,7 +23,6 @@ import { import { ACTION_TYPES_URL, - CASE_CONFIGURE_CONNECTORS_URL, CASE_REPORTERS_URL, CASE_STATUS_URL, CASE_TAGS_URL, @@ -273,20 +271,3 @@ export const getActionLicense = async (signal: AbortSignal): Promise => { - const response = await KibanaServices.get().http.fetch( - `${CASE_CONFIGURE_CONNECTORS_URL}/${connectorId}`, - { - query: { - connector_type: connectorType, - }, - method: 'GET', - signal, - } - ); - return response; -}; diff --git a/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx b/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx deleted file mode 100644 index 6b594fa60e0c73..00000000000000 --- a/x-pack/plugins/security_solution/public/cases/containers/use_get_fields.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useCallback, useEffect, useState } from 'react'; - -import { errorToToaster, useStateToaster } from '../../common/components/toasters'; -import { getFields } from './api'; -import * as i18n from './translations'; -import { ConnectorField } from '../../../../case/common/api'; - -interface FieldsState { - fields: ConnectorField[]; - isLoading: boolean; - isError: boolean; -} - -const initialData: FieldsState = { - fields: [], - isLoading: false, - isError: false, -}; - -export interface UseGetFields extends FieldsState { - fetchFields: () => void; -} - -export const useGetFields = (connectorId: string, connectorType: string): UseGetFields => { - const [fieldsState, setFieldsState] = useState(initialData); - const [, dispatchToaster] = useStateToaster(); - - const fetchFields = useCallback(() => { - let didCancel = false; - const abortCtrl = new AbortController(); - const fetchData = async () => { - setFieldsState({ - ...fieldsState, - isLoading: true, - }); - try { - const response = await getFields(connectorId, connectorType, abortCtrl.signal); - if (!didCancel) { - setFieldsState({ - fields: response, - isLoading: false, - isError: false, - }); - } - } catch (error) { - if (!didCancel) { - errorToToaster({ - title: i18n.ERROR_TITLE, - error: error.body && error.body.message ? new Error(error.body.message) : error, - dispatchToaster, - }); - setFieldsState({ - fields: [], - isLoading: false, - isError: true, - }); - } - } - }; - fetchData(); - return () => { - didCancel = true; - abortCtrl.abort(); - }; - }, [connectorId, connectorType, dispatchToaster, fieldsState]); - - useEffect(() => { - fetchFields(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return { - ...fieldsState, - fetchFields, - }; -}; From 62833a35df8e6d279113498b51d7ec68865b040f Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Fri, 18 Dec 2020 10:32:13 -0600 Subject: [PATCH 02/40] [Workplace Search] Add AddSourceLogic tests (#86334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove missed action calls These were missed in the PR that split apart add_source_logic from source_logic. These 2 mthods aren’t even in this file. My mistake * Change order of method calls For some reason the test would not pass with the order of the methods the way they were. Changing the order to where the error callback is called first somehow made the test pass * Fix bug where query params never set Uncovered a bug while writing this test. After moving from ent-search to Kibana, it was discovered that the Kibana server did not work well with an empty question mark in the URL. The fix attempted to use lodash’s isEmpty metthod. The problem is that the URLSearchParams class instance created is not an object but has getter/setter functionality so isEmpty always returned true. This fixes it to actually work. * Remove throw blocks In ent-search we had to account for edge cases where the error state wasn’t bubbling up. One of these has been covered with global flash messages. The other will be tested at later date. A tech-debt item has been created. Reference PRs: https://github.com/elastic/ent-search/pull/394 https://github.com/elastic/ent-search/pull/701 * Export interfaces for use in tests * Add tests for add_source_logic * Remove weird import I guess the IDE autocompleted that. Should not have been there * Remove redundant test This test was added before this comit when trying to get coverage for the throw blocks https://github.com/elastic/kibana/pull/86334/commits/7ca9c2b2440eeff22d04bdce7290c136a07f320c * Lint fixes --- .../add_source/add_source_logic.test.ts | 553 ++++++++++++++++++ .../components/add_source/add_source_logic.ts | 18 +- 2 files changed, 560 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts new file mode 100644 index 00000000000000..ba38b46aa05521 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -0,0 +1,553 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resetContext } from 'kea'; +import { mockHttpValues } from '../../../../../__mocks__'; +jest.mock('../../../../../shared/http', () => ({ + HttpLogic: { + values: { http: mockHttpValues.http }, + }, +})); +import { HttpLogic } from '../../../../../shared/http'; + +jest.mock('../../../../../shared/flash_messages', () => ({ + FlashMessagesLogic: { actions: { clearFlashMessages: jest.fn(), setQueuedMessages: jest.fn() } }, + flashAPIErrors: jest.fn(), + setSuccessMessage: jest.fn(), + setQueuedSuccessMessage: jest.fn(), +})); +import { FlashMessagesLogic, flashAPIErrors } from '../../../../../shared/flash_messages'; + +import { AppLogic } from '../../../../app_logic'; +jest.mock('../../../../app_logic', () => ({ + AppLogic: { values: { isOrganization: true } }, +})); + +import { CustomSource } from '../../../../types'; + +import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; + +import { + AddSourceLogic, + SourceConfigData, + SourceConnectData, + OrganizationsMap, +} from './add_source_logic'; + +describe('AddSourceLogic', () => { + const defaultValues = { + dataLoading: true, + sectionLoading: true, + buttonLoading: false, + customSourceNameValue: '', + clientIdValue: '', + clientSecretValue: '', + baseUrlValue: '', + loginValue: '', + passwordValue: '', + subdomainValue: '', + indexPermissionsValue: false, + sourceConfigData: {} as SourceConfigData, + sourceConnectData: {} as SourceConnectData, + newCustomSource: {} as CustomSource, + currentServiceType: '', + githubOrganizations: [], + selectedGithubOrganizationsMap: {} as OrganizationsMap, + selectedGithubOrganizations: [], + }; + + const sourceConnectData = { + oauthUrl: 'http://foo', + serviceType: 'gmail', + }; + + const config = { + id: '123key', + serviceType: 'github', + githubOrganizations: ['foo', 'bar'], + }; + + const clearFlashMessagesSpy = jest.spyOn(FlashMessagesLogic.actions, 'clearFlashMessages'); + + beforeEach(() => { + jest.clearAllMocks(); + resetContext({}); + AddSourceLogic.mount(); + }); + + it('has expected default values', () => { + expect(AddSourceLogic.values).toEqual(defaultValues); + }); + + describe('actions', () => { + it('setSourceConfigData', () => { + AddSourceLogic.actions.setSourceConfigData(sourceConfigData); + + expect(AddSourceLogic.values.sourceConfigData).toEqual(sourceConfigData); + expect(AddSourceLogic.values.dataLoading).toEqual(false); + expect(AddSourceLogic.values.buttonLoading).toEqual(false); + expect(AddSourceLogic.values.clientIdValue).toEqual( + sourceConfigData.configuredFields.clientId + ); + expect(AddSourceLogic.values.clientSecretValue).toEqual( + sourceConfigData.configuredFields.clientSecret + ); + expect(AddSourceLogic.values.baseUrlValue).toEqual(sourceConfigData.configuredFields.baseUrl); + }); + + it('setSourceConnectData', () => { + AddSourceLogic.actions.setSourceConnectData(sourceConnectData); + + expect(AddSourceLogic.values.sourceConnectData).toEqual(sourceConnectData); + expect(AddSourceLogic.values.buttonLoading).toEqual(false); + }); + + it('setClientIdValue', () => { + AddSourceLogic.actions.setClientIdValue('id'); + + expect(AddSourceLogic.values.clientIdValue).toEqual('id'); + }); + + it('setClientSecretValue', () => { + AddSourceLogic.actions.setClientSecretValue('secret'); + + expect(AddSourceLogic.values.clientSecretValue).toEqual('secret'); + }); + + it('setBaseUrlValue', () => { + AddSourceLogic.actions.setBaseUrlValue('secret'); + + expect(AddSourceLogic.values.baseUrlValue).toEqual('secret'); + }); + + it('setCustomSourceNameValue', () => { + AddSourceLogic.actions.setCustomSourceNameValue('name'); + + expect(AddSourceLogic.values.customSourceNameValue).toEqual('name'); + }); + + it('setSourceLoginValue', () => { + AddSourceLogic.actions.setSourceLoginValue('login'); + + expect(AddSourceLogic.values.loginValue).toEqual('login'); + }); + + it('setSourcePasswordValue', () => { + AddSourceLogic.actions.setSourcePasswordValue('password'); + + expect(AddSourceLogic.values.passwordValue).toEqual('password'); + }); + + it('setSourceSubdomainValue', () => { + AddSourceLogic.actions.setSourceSubdomainValue('subdomain'); + + expect(AddSourceLogic.values.subdomainValue).toEqual('subdomain'); + }); + + it('setSourceIndexPermissionsValue', () => { + AddSourceLogic.actions.setSourceIndexPermissionsValue(true); + + expect(AddSourceLogic.values.indexPermissionsValue).toEqual(true); + }); + + it('setCustomSourceData', () => { + const newCustomSource = { + accessToken: 'foo', + key: 'bar', + name: 'source', + id: '123key', + }; + + AddSourceLogic.actions.setCustomSourceData(newCustomSource); + + expect(AddSourceLogic.values.newCustomSource).toEqual(newCustomSource); + }); + + it('setPreContentSourceConfigData', () => { + AddSourceLogic.actions.setPreContentSourceConfigData(config); + + expect(AddSourceLogic.values.dataLoading).toEqual(false); + expect(AddSourceLogic.values.sectionLoading).toEqual(false); + expect(AddSourceLogic.values.currentServiceType).toEqual(config.serviceType); + expect(AddSourceLogic.values.githubOrganizations).toEqual(config.githubOrganizations); + }); + + it('setSelectedGithubOrganizations', () => { + AddSourceLogic.actions.setSelectedGithubOrganizations('foo'); + + expect(AddSourceLogic.values.selectedGithubOrganizationsMap).toEqual({ foo: true }); + }); + + it('setButtonNotLoading', () => { + AddSourceLogic.actions.setButtonNotLoading(); + + expect(AddSourceLogic.values.buttonLoading).toEqual(false); + }); + + it('resetSourceState', () => { + AddSourceLogic.actions.resetSourceState(); + + expect(AddSourceLogic.values.dataLoading).toEqual(false); + expect(AddSourceLogic.values.buttonLoading).toEqual(false); + expect(AddSourceLogic.values.clientIdValue).toEqual(''); + expect(AddSourceLogic.values.clientSecretValue).toEqual(''); + expect(AddSourceLogic.values.baseUrlValue).toEqual(''); + expect(AddSourceLogic.values.loginValue).toEqual(''); + expect(AddSourceLogic.values.passwordValue).toEqual(''); + expect(AddSourceLogic.values.subdomainValue).toEqual(''); + expect(AddSourceLogic.values.indexPermissionsValue).toEqual(false); + expect(AddSourceLogic.values.customSourceNameValue).toEqual(''); + expect(AddSourceLogic.values.newCustomSource).toEqual({}); + expect(AddSourceLogic.values.currentServiceType).toEqual(''); + expect(AddSourceLogic.values.githubOrganizations).toEqual([]); + expect(AddSourceLogic.values.selectedGithubOrganizationsMap).toEqual({}); + }); + + it('handles fallback states', () => { + const { publicKey, privateKey, consumerKey } = sourceConfigData.configuredFields; + AddSourceLogic.actions.setSourceConfigData({ + ...sourceConfigData, + configuredFields: { + publicKey, + privateKey, + consumerKey, + }, + }); + + expect(AddSourceLogic.values.clientIdValue).toEqual(''); + expect(AddSourceLogic.values.clientSecretValue).toEqual(''); + expect(AddSourceLogic.values.baseUrlValue).toEqual(''); + }); + }); + + describe('listeners', () => { + describe('organization context', () => { + describe('getSourceConfigData', () => { + it('calls API and sets values', async () => { + const setSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'setSourceConfigData'); + const promise = Promise.resolve(sourceConfigData); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceConfigData('github'); + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/settings/connectors/github' + ); + await promise; + expect(setSourceConfigDataSpy).toHaveBeenCalledWith(sourceConfigData); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceConfigData('github'); + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('getSourceConnectData', () => { + const successCallback = jest.fn(); + + it('calls API and sets values', async () => { + const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading'); + const setSourceConnectDataSpy = jest.spyOn( + AddSourceLogic.actions, + 'setSourceConnectData' + ); + const promise = Promise.resolve(sourceConnectData); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceConnectData('github', successCallback); + + expect(clearFlashMessagesSpy).toHaveBeenCalled(); + expect(AddSourceLogic.values.buttonLoading).toEqual(true); + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/github/prepare' + ); + await promise; + expect(setSourceConnectDataSpy).toHaveBeenCalledWith(sourceConnectData); + expect(successCallback).toHaveBeenCalledWith(sourceConnectData.oauthUrl); + expect(setButtonNotLoadingSpy).toHaveBeenCalled(); + }); + + it('appends query params', () => { + AddSourceLogic.actions.setSourceSubdomainValue('subdomain'); + AddSourceLogic.actions.setSourceIndexPermissionsValue(true); + AddSourceLogic.actions.getSourceConnectData('github', successCallback); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/github/prepare?subdomain=subdomain&index_permissions=true' + ); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceConnectData('github', successCallback); + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('getSourceReConnectData', () => { + it('calls API and sets values', async () => { + const setSourceConnectDataSpy = jest.spyOn( + AddSourceLogic.actions, + 'setSourceConnectData' + ); + const promise = Promise.resolve(sourceConnectData); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceReConnectData('github'); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/github/reauth_prepare' + ); + await promise; + expect(setSourceConnectDataSpy).toHaveBeenCalledWith(sourceConnectData); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getSourceReConnectData('github'); + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('getPreContentSourceConfigData', () => { + it('calls API and sets values', async () => { + const setPreContentSourceConfigDataSpy = jest.spyOn( + AddSourceLogic.actions, + 'setPreContentSourceConfigData' + ); + const promise = Promise.resolve(config); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getPreContentSourceConfigData('123'); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/pre_sources/123' + ); + await promise; + expect(setPreContentSourceConfigDataSpy).toHaveBeenCalledWith(config); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.get as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.getPreContentSourceConfigData('123'); + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('saveSourceConfig', () => { + let params: any; + + beforeEach(() => { + AddSourceLogic.actions.setSourceConfigData(sourceConfigData); + + params = { + base_url: AddSourceLogic.values.baseUrlValue, + client_id: AddSourceLogic.values.clientIdValue, + client_secret: AddSourceLogic.values.clientSecretValue, + service_type: sourceConfigData.serviceType, + private_key: sourceConfigData.configuredFields?.privateKey, + public_key: sourceConfigData.configuredFields?.publicKey, + consumer_key: sourceConfigData.configuredFields?.consumerKey, + }; + }); + + it('calls API and sets values when updating', async () => { + const successCallback = jest.fn(); + const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading'); + const setSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'setSourceConfigData'); + const promise = Promise.resolve({ sourceConfigData }); + (HttpLogic.values.http.put as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.saveSourceConfig(true, successCallback); + + expect(clearFlashMessagesSpy).toHaveBeenCalled(); + expect(AddSourceLogic.values.buttonLoading).toEqual(true); + expect( + HttpLogic.values.http.put + ).toHaveBeenCalledWith( + `/api/workplace_search/org/settings/connectors/${sourceConfigData.serviceType}`, + { body: JSON.stringify({ params }) } + ); + + await promise; + expect(successCallback).toHaveBeenCalled(); + expect(setSourceConfigDataSpy).toHaveBeenCalledWith({ sourceConfigData }); + expect(setButtonNotLoadingSpy).toHaveBeenCalled(); + }); + + it('calls API when creating with empty attributes', () => { + AddSourceLogic.actions.setClientIdValue(''); + AddSourceLogic.actions.setClientSecretValue(''); + AddSourceLogic.actions.setBaseUrlValue(''); + AddSourceLogic.actions.saveSourceConfig(false); + + const createParams = { + service_type: sourceConfigData.serviceType, + private_key: sourceConfigData.configuredFields?.privateKey, + public_key: sourceConfigData.configuredFields?.publicKey, + consumer_key: sourceConfigData.configuredFields?.consumerKey, + }; + + expect(HttpLogic.values.http.post).toHaveBeenCalledWith( + '/api/workplace_search/org/settings/connectors', + { + body: JSON.stringify({ params: createParams }), + } + ); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.put as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.saveSourceConfig(true); + try { + await promise; + } catch { + // Do nothing + } + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + + describe('createContentSource', () => { + const successCallback = jest.fn(); + const errorCallback = jest.fn(); + + const serviceType = 'zendesk'; + const name = 'name'; + const login = 'login'; + const password = 'password'; + const indexPermissions = false; + + let params: any; + + beforeEach(() => { + AddSourceLogic.actions.setCustomSourceNameValue(name); + AddSourceLogic.actions.setSourceLoginValue(login); + AddSourceLogic.actions.setSourcePasswordValue(password); + AddSourceLogic.actions.setPreContentSourceConfigData(config); + AddSourceLogic.actions.setSourceIndexPermissionsValue(indexPermissions); + AddSourceLogic.actions.setSelectedGithubOrganizations('foo'); + + params = { + service_type: serviceType, + name, + login, + password, + organizations: ['foo'], + }; + }); + + it('calls API and sets values', async () => { + const setButtonNotLoadingSpy = jest.spyOn(AddSourceLogic.actions, 'setButtonNotLoading'); + const setCustomSourceDataSpy = jest.spyOn(AddSourceLogic.actions, 'setCustomSourceData'); + const promise = Promise.resolve({ sourceConfigData }); + (HttpLogic.values.http.post as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.createContentSource(serviceType, successCallback, errorCallback); + + expect(clearFlashMessagesSpy).toHaveBeenCalled(); + expect(AddSourceLogic.values.buttonLoading).toEqual(true); + expect(HttpLogic.values.http.post).toHaveBeenCalledWith( + '/api/workplace_search/org/create_source', + { + body: JSON.stringify({ ...params }), + } + ); + await promise; + expect(setCustomSourceDataSpy).toHaveBeenCalledWith({ sourceConfigData }); + expect(successCallback).toHaveBeenCalled(); + expect(setButtonNotLoadingSpy).toHaveBeenCalled(); + }); + + it('handles error', async () => { + const promise = Promise.reject('this is an error'); + (HttpLogic.values.http.post as jest.Mock).mockReturnValue(promise); + + AddSourceLogic.actions.createContentSource(serviceType, successCallback, errorCallback); + try { + await promise; + } catch { + // Do nothing + } + expect(errorCallback).toHaveBeenCalled(); + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + }); + }); + }); + + describe('account context routes', () => { + beforeEach(() => { + AppLogic.values.isOrganization = false; + }); + + it('getSourceConnectData', () => { + AddSourceLogic.actions.getSourceConnectData('github', jest.fn()); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/github/prepare' + ); + }); + + it('getSourceReConnectData', () => { + AddSourceLogic.actions.getSourceReConnectData('123'); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/sources/123/reauth_prepare' + ); + }); + + it('getPreContentSourceConfigData', () => { + AddSourceLogic.actions.getPreContentSourceConfigData('123'); + + expect(HttpLogic.values.http.get).toHaveBeenCalledWith( + '/api/workplace_search/account/pre_sources/123' + ); + }); + + it('createContentSource', () => { + AddSourceLogic.actions.createContentSource('github', jest.fn()); + + expect(HttpLogic.values.http.post).toHaveBeenCalledWith( + '/api/workplace_search/account/create_source', + { + body: JSON.stringify({ service_type: 'github' }), + } + ); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index bea69a86edebbb..c487b584d8acef 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { keys, pickBy, isEmpty } from 'lodash'; +import { keys, pickBy } from 'lodash'; import { kea, MakeLogicType } from 'kea'; @@ -55,7 +55,7 @@ export interface AddSourceActions { setButtonNotLoading(): void; } -interface SourceConfigData { +export interface SourceConfigData { serviceType: string; name: string; configured: boolean; @@ -73,12 +73,12 @@ interface SourceConfigData { accountContextOnly?: boolean; } -interface SourceConnectData { +export interface SourceConnectData { oauthUrl: string; serviceType: string; } -interface OrganizationsMap { +export interface OrganizationsMap { [key: string]: string | boolean; } @@ -160,7 +160,6 @@ export const AddSourceLogic = kea false, setSourceConfigData: () => false, resetSourceState: () => false, setPreContentSourceConfigData: () => false, @@ -182,7 +181,6 @@ export const AddSourceLogic = kea true, - setSearchResults: () => false, setPreContentSourceConfigData: () => false, }, ], @@ -306,8 +304,8 @@ export const AddSourceLogic = kea Date: Fri, 18 Dec 2020 11:35:15 -0500 Subject: [PATCH 03/40] [Security Solution] Fix Policy-License-Watcher payload (#86185) --- .../endpoint/lib/policy/license_watch.ts | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index cae3b9f33850ab..2f0c3bf8fd5ba9 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -12,7 +12,11 @@ import { SavedObjectsClientContract, SavedObjectsServiceStart, } from 'src/core/server'; -import { PackagePolicy, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../../fleet/common'; +import { + PackagePolicy, + PACKAGE_POLICY_SAVED_OBJECT_TYPE, + UpdatePackagePolicy, +} from '../../../../../fleet/common'; import { PackagePolicyServiceInterface } from '../../../../../fleet/server'; import { ILicense } from '../../../../../licensing/common/types'; import { @@ -91,18 +95,29 @@ export class PolicyWatcher { return; } response.items.forEach(async (policy) => { - const policyConfig = policy.inputs[0].config?.policy.value; + const updatePolicy: UpdatePackagePolicy = { + name: policy.name, + description: policy.description, + namespace: policy.namespace, + enabled: policy.enabled, + policy_id: policy.policy_id, + output_id: policy.output_id, + package: policy.package, + inputs: policy.inputs, + version: policy.version, + }; + const policyConfig = updatePolicy.inputs[0].config?.policy.value; if (!isEndpointPolicyValidForLicense(policyConfig, license)) { - policy.inputs[0].config!.policy.value = unsetPolicyFeaturesAboveLicenseLevel( + updatePolicy.inputs[0].config!.policy.value = unsetPolicyFeaturesAboveLicenseLevel( policyConfig, license ); try { - await this.policyService.update(this.soClient, policy.id, policy); + await this.policyService.update(this.soClient, policy.id, updatePolicy); } catch (e) { // try again for transient issues try { - await this.policyService.update(this.soClient, policy.id, policy); + await this.policyService.update(this.soClient, policy.id, updatePolicy); } catch (ee) { this.logger.warn( `Unable to remove platinum features from policy ${policy.id}: ${ee.message}` From 0155974591f9a385134fd916eca76c71705c79c8 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Fri, 18 Dec 2020 10:53:34 -0700 Subject: [PATCH 04/40] Migrates elasticsearch client in the settings usage collector (#86397) --- .../xpack_legacy/server/routes/settings.test.ts | 10 ++++++++-- x-pack/plugins/xpack_legacy/server/routes/settings.ts | 7 +++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts index 5d22e22ee0eb6f..5d3a2c105c4a45 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.test.ts @@ -24,6 +24,12 @@ import { registerSettingsRoute } from './settings'; type HttpService = ReturnType; type HttpSetup = UnwrapPromise>; +export function mockGetClusterInfo(clusterInfo: any) { + const esClient = elasticsearchServiceMock.createScopedClusterClient().asCurrentUser; + // @ts-ignore we only care about the response body + esClient.info.mockResolvedValue({ body: { ...clusterInfo } }); + return esClient; +} describe('/api/settings', () => { let server: HttpService; let httpSetup: HttpSetup; @@ -31,7 +37,7 @@ describe('/api/settings', () => { let mockApiCaller: jest.Mocked; beforeEach(async () => { - mockApiCaller = jest.fn().mockResolvedValue({ cluster_uuid: 'yyy-yyyyy' }); + mockApiCaller = jest.fn(); server = createHttpServer(); httpSetup = await server.setup({ context: contextServiceMock.createSetupContract({ @@ -43,7 +49,7 @@ describe('/api/settings', () => { }, }, client: { - asCurrentUser: elasticsearchServiceMock.createScopedClusterClient().asCurrentUser, + asCurrentUser: mockGetClusterInfo({ cluster_uuid: 'yyy-yyyyy' }), }, }, savedObjects: { diff --git a/x-pack/plugins/xpack_legacy/server/routes/settings.ts b/x-pack/plugins/xpack_legacy/server/routes/settings.ts index 9a30ca30616b75..93dc6898f0c2e7 100644 --- a/x-pack/plugins/xpack_legacy/server/routes/settings.ts +++ b/x-pack/plugins/xpack_legacy/server/routes/settings.ts @@ -58,9 +58,9 @@ export function registerSettingsRoute({ const settings = (await settingsCollector.fetch(collectorFetchContext)) ?? settingsCollector.getEmailValueStructure(null); - const { cluster_uuid: uuid } = await callAsCurrentUser('info', { - filterPath: 'cluster_uuid', - }); + + const { body } = await collectorFetchContext.esClient.info({ filter_path: 'cluster_uuid' }); + const uuid: string = body.cluster_uuid; const overallStatus = await overallStatus$.pipe(first()).toPromise(); @@ -76,7 +76,6 @@ export function registerSettingsRoute({ snapshot: SNAPSHOT_REGEX.test(config.kibanaVersion), status: ServiceStatusToLegacyState[overallStatus.level.toString()], }; - return res.ok({ body: { cluster_uuid: uuid, From 6a517411ebcdba1499e210111868e5aa925b07d9 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Fri, 18 Dec 2020 10:55:46 -0700 Subject: [PATCH 05/40] Migrates elasticsearch client in the kibana_usage_collector (#86406) --- .../kibana/get_saved_object_counts.test.ts | 23 ++++++++++++++----- .../kibana/get_saved_object_counts.ts | 8 +++---- .../server/collectors/kibana/index.test.ts | 7 ++++-- .../kibana/kibana_usage_collector.ts | 4 ++-- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts index a7681e17664272..64f1088dc33923 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.test.ts @@ -16,14 +16,25 @@ * specific language governing permissions and limitations * under the License. */ - +import { elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; import { getSavedObjectsCounts } from './get_saved_object_counts'; +export function mockGetSavedObjectsCounts(params: any) { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search.mockResolvedValue( + // @ts-ignore we only care about the response body + { + body: { ...params }, + } + ); + return esClient; +} + describe('getSavedObjectsCounts', () => { test('Get all the saved objects equal to 0 because no results were found', async () => { - const callCluster = jest.fn(() => ({})); + const esClient = mockGetSavedObjectsCounts({}); - const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + const results = await getSavedObjectsCounts(esClient, '.kibana'); expect(results).toStrictEqual({ dashboard: { total: 0 }, visualization: { total: 0 }, @@ -35,7 +46,7 @@ describe('getSavedObjectsCounts', () => { }); test('Merge the zeros with the results', async () => { - const callCluster = jest.fn(() => ({ + const esClient = mockGetSavedObjectsCounts({ aggregations: { types: { buckets: [ @@ -46,9 +57,9 @@ describe('getSavedObjectsCounts', () => { ], }, }, - })); + }); - const results = await getSavedObjectsCounts(callCluster as any, '.kibana'); + const results = await getSavedObjectsCounts(esClient, '.kibana'); expect(results).toStrictEqual({ dashboard: { total: 1 }, visualization: { total: 0 }, diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts index e88d90fe5b24ba..65cc3643a88cb6 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/get_saved_object_counts.ts @@ -27,7 +27,7 @@ */ import { snakeCase } from 'lodash'; -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'src/core/server'; const TYPES = [ 'dashboard', @@ -48,7 +48,7 @@ export interface KibanaSavedObjectCounts { } export async function getSavedObjectsCounts( - callCluster: LegacyAPICaller, + esClient: ElasticsearchClient, kibanaIndex: string // Typically '.kibana'. We might need a way to obtain it from the SavedObjects client (or the SavedObjects client to provide a way to run aggregations?) ): Promise { const savedObjectCountSearchParams = { @@ -67,9 +67,9 @@ export async function getSavedObjectsCounts( }, }, }; - const resp = await callCluster('search', savedObjectCountSearchParams); + const { body } = await esClient.search(savedObjectCountSearchParams); const buckets: Array<{ key: string; doc_count: number }> = - resp.aggregations?.types?.buckets || []; + body.aggregations?.types?.buckets || []; // Initialise the object with all zeros for all the types const allZeros: KibanaSavedObjectCounts = TYPES.reduce( diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts index 83cac1d456a3ae..dee9ca4d32c5fb 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/index.test.ts @@ -20,12 +20,13 @@ import { loggingSystemMock, pluginInitializerContextConfigMock, + elasticsearchServiceMock, } from '../../../../../core/server/mocks'; import { Collector, + createCollectorFetchContextMock, createUsageCollectionSetupMock, } from '../../../../usage_collection/server/usage_collection.mock'; -import { createCollectorFetchContextMock } from '../../../../usage_collection/server/mocks'; import { registerKibanaUsageCollector } from './'; const logger = loggingSystemMock.createLogger(); @@ -43,7 +44,9 @@ describe('telemetry_kibana', () => { const getMockFetchClients = (hits?: unknown[]) => { const fetchParamsMock = createCollectorFetchContextMock(); - fetchParamsMock.callCluster.mockResolvedValue({ hits: { hits } }); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + esClient.search.mockResolvedValue({ body: { hits: { hits } } } as any); + fetchParamsMock.esClient = esClient; return fetchParamsMock; }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts index 6c2e0a2c926ad0..5dd39d172e1c2c 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/kibana/kibana_usage_collector.ts @@ -43,13 +43,13 @@ export function getKibanaUsageCollector( graph_workspace: { total: { type: 'long' } }, timelion_sheet: { total: { type: 'long' } }, }, - async fetch({ callCluster }) { + async fetch({ esClient }) { const { kibana: { index }, } = await legacyConfig$.pipe(take(1)).toPromise(); return { index, - ...(await getSavedObjectsCounts(callCluster, index)), + ...(await getSavedObjectsCounts(esClient, index)), }; }, }); From fc7ae0e1a682748c67bbe81fd8de76b148742dc2 Mon Sep 17 00:00:00 2001 From: Angela Chuang <6295984+angorayc@users.noreply.github.com> Date: Fri, 18 Dec 2020 18:02:19 +0000 Subject: [PATCH 06/40] [Security Solution] Fix 'disable usage data here.' link (#86452) * fix 'disable usage data here.' link * update snapshot * update link * update mocks Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- src/plugins/telemetry/common/constants.ts | 2 +- .../opted_in_notice_banner.test.tsx.snap | 2 +- .../components/opted_in_notice_banner.test.tsx | 12 ++++++++++-- .../public/components/opted_in_notice_banner.tsx | 8 ++++++-- src/plugins/telemetry/public/mocks.ts | 1 + src/plugins/telemetry/public/plugin.ts | 1 + .../render_opted_in_notice_banner.test.ts | 4 ++++ .../render_opted_in_notice_banner.tsx | 5 +++-- .../telemetry_notifications.ts | 6 +++++- 9 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index fc77332c18fc90..2fa8b32f682913 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -49,7 +49,7 @@ export const LOCALSTORAGE_KEY = 'telemetry.data'; /** * Link to Advanced Settings. */ -export const PATH_TO_ADVANCED_SETTINGS = 'management/kibana/settings'; +export const PATH_TO_ADVANCED_SETTINGS = '/app/management/kibana/settings'; /** * Link to the Elastic Telemetry privacy statement. diff --git a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap index 62998da73d6f93..897e3b2761c744 100644 --- a/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap +++ b/src/plugins/telemetry/public/components/__snapshots__/opted_in_notice_banner.test.tsx.snap @@ -10,7 +10,7 @@ exports[`OptInDetailsComponent renders as expected 1`] = ` values={ Object { "disableLink": { it('renders as expected', () => { - expect(shallowWithIntl( {}} />)).toMatchSnapshot(); + expect( + shallowWithIntl( {}} http={mockHttp} />) + ).toMatchSnapshot(); }); it('fires the "onSeenBanner" prop when a link is clicked', () => { const onLinkClick = jest.fn(); - const component = shallowWithIntl(); + const component = shallowWithIntl( + + ); const button = component.findWhere((n) => n.type() === EuiButton); diff --git a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx b/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx index 090893964c8810..46ae17171203ce 100644 --- a/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx +++ b/src/plugins/telemetry/public/components/opted_in_notice_banner.tsx @@ -24,14 +24,18 @@ import { EuiButton, EuiLink, EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { PATH_TO_ADVANCED_SETTINGS, PRIVACY_STATEMENT_URL } from '../../common/constants'; +import { HttpSetup } from '../../../../core/public'; interface Props { + http: HttpSetup; onSeenBanner: () => any; } export class OptedInNoticeBanner extends React.PureComponent { render() { - const { onSeenBanner } = this.props; + const { onSeenBanner, http } = this.props; + const basePath = http.basePath.get(); + const bannerTitle = i18n.translate('telemetry.telemetryOptedInNoticeTitle', { defaultMessage: 'Help us improve the Elastic Stack', }); @@ -56,7 +60,7 @@ export class OptedInNoticeBanner extends React.PureComponent { ), disableLink: ( - + { it('adds a banner to banners with priority of 10000', () => { const bannerID = 'brucer-wayne'; const overlays = overlayServiceMock.createStartContract(); + const mockHttp = httpServiceMock.createStartContract(); overlays.banners.add.mockReturnValue(bannerID); const returnedBannerId = renderOptedInNoticeBanner({ + http: mockHttp, onSeen: jest.fn(), overlays, }); diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx b/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx index e63e46af6e8ca3..e1feea4b6cbe11 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx +++ b/src/plugins/telemetry/public/services/telemetry_notifications/render_opted_in_notice_banner.tsx @@ -23,11 +23,12 @@ import { OptedInNoticeBanner } from '../../components/opted_in_notice_banner'; import { toMountPoint } from '../../../../kibana_react/public'; interface RenderBannerConfig { + http: CoreStart['http']; overlays: CoreStart['overlays']; onSeen: () => void; } -export function renderOptedInNoticeBanner({ onSeen, overlays }: RenderBannerConfig) { - const mount = toMountPoint(); +export function renderOptedInNoticeBanner({ onSeen, overlays, http }: RenderBannerConfig) { + const mount = toMountPoint(); const bannerId = overlays.banners.add(mount, 10000); return bannerId; diff --git a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts index fc44a4db7cf5e8..6ebbfcfb913368 100644 --- a/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts +++ b/src/plugins/telemetry/public/services/telemetry_notifications/telemetry_notifications.ts @@ -23,18 +23,21 @@ import { renderOptInBanner } from './render_opt_in_banner'; import { TelemetryService } from '../telemetry_service'; interface TelemetryNotificationsConstructor { + http: CoreStart['http']; overlays: CoreStart['overlays']; telemetryService: TelemetryService; } export class TelemetryNotifications { + private readonly http: CoreStart['http']; private readonly overlays: CoreStart['overlays']; private readonly telemetryService: TelemetryService; private optedInNoticeBannerId?: string; private optInBannerId?: string; - constructor({ overlays, telemetryService }: TelemetryNotificationsConstructor) { + constructor({ http, overlays, telemetryService }: TelemetryNotificationsConstructor) { this.telemetryService = telemetryService; + this.http = http; this.overlays = overlays; } @@ -46,6 +49,7 @@ export class TelemetryNotifications { public renderOptedInNoticeBanner = (): void => { const bannerId = renderOptedInNoticeBanner({ + http: this.http, onSeen: this.setOptedInNoticeSeen, overlays: this.overlays, }); From d73af3282f6cf9c9f8520999982c5085db4a7cb4 Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 18 Dec 2020 10:16:27 -0800 Subject: [PATCH 07/40] [Enterprise Search] Basic DocumentCreation creation mode modal views (#86056) * Add ApiCodeExample modal component - Previously lived in EngineOverview / Onboarding * Add basic PasteJsonText component * Add basic UploadJsonFile component * [Refactor] Have all modal components manage their own ModalHeader & ModalFooters - Per feedback from Casey + Update DocumentCreationModal to use switch * Set basic empty/disabled validation on ModalFooter continue buttons * Update x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx Co-authored-by: Jason Stoltzfus * [PR feedback] Typescript improvements * [PR feedback] Remove need for hasFile reducer - by storing either 1 file or null - which gets around the stored FileList reference not triggering a rerender/change Co-authored-by: Jason Stoltzfus Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../document_creation/constants.tsx | 46 ++++++- .../api_code_example.test.tsx | 75 ++++++++++ .../api_code_example.tsx | 128 ++++++++++++++++++ .../creation_mode_components/index.ts | 10 ++ .../paste_json_text.scss | 9 ++ .../paste_json_text.test.tsx | 80 +++++++++++ .../paste_json_text.tsx | 101 ++++++++++++++ .../show_creation_modes.test.tsx | 37 +++++ .../show_creation_modes.tsx | 45 ++++++ .../upload_json_file.test.tsx | 85 ++++++++++++ .../upload_json_file.tsx | 98 ++++++++++++++ .../document_creation_logic.test.ts | 33 +++++ .../document_creation_logic.ts | 20 +++ .../document_creation_modal.test.tsx | 45 +++--- .../document_creation_modal.tsx | 77 ++++++----- 15 files changed, 830 insertions(+), 59 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx index 5406a90a75a35d..c4237da0d0e804 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx @@ -4,4 +4,48 @@ * you may not use this file except in compliance with the Elastic License. */ -// TODO: This will be used shortly in an upcoming PR +import { i18n } from '@kbn/i18n'; + +export const MODAL_CANCEL_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.modalCancel', + { defaultMessage: 'Cancel' } +); +export const MODAL_CONTINUE_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.modalContinue', + { defaultMessage: 'Continue' } +); + +// This is indented the way it is to work with ApiCodeExample. +// Use dedent() when calling this alone +export const DOCUMENTS_API_JSON_EXAMPLE = `[ + { + "id": "park_rocky-mountain", + "title": "Rocky Mountain", + "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).", + "nps_link": "https://www.nps.gov/romo/index.htm", + "states": [ + "Colorado" + ], + "visitors": 4517585, + "world_heritage_site": false, + "location": "40.4,-105.58", + "acres": 265795.2, + "square_km": 1075.6, + "date_established": "1915-01-26T06:00:00Z" + }, + { + "id": "park_saguaro", + "title": "Saguaro", + "description": "Split into the separate Rincon Mountain and Tucson Mountain districts, this park is evidence that the dry Sonoran Desert is still home to a great variety of life spanning six biotic communities. Beyond the namesake giant saguaro cacti, there are barrel cacti, chollas, and prickly pears, as well as lesser long-nosed bats, spotted owls, and javelinas.", + "nps_link": "https://www.nps.gov/sagu/index.htm", + "states": [ + "Arizona" + ], + "visitors": 820426, + "world_heritage_site": false, + "location": "32.25,-110.5", + "acres": 91715.72, + "square_km": 371.2, + "date_established": "1994-10-14T05:00:00Z" + } + ]`; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx new file mode 100644 index 00000000000000..2dd46419528c1b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../__mocks__/enterprise_search_url.mock'; +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiCode, EuiCodeBlock, EuiButtonEmpty } from '@elastic/eui'; + +import { ApiCodeExample, ModalHeader, ModalBody, ModalFooter } from './api_code_example'; + +describe('ApiCodeExample', () => { + const values = { + engineName: 'test-engine', + engine: { apiKey: 'test-key' }, + }; + const actions = { + closeDocumentCreation: jest.fn(), + }; + + beforeAll(() => { + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(ModalHeader)).toHaveLength(1); + expect(wrapper.find(ModalBody)).toHaveLength(1); + expect(wrapper.find(ModalFooter)).toHaveLength(1); + }); + + describe('ModalHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Indexing by API'); + }); + }); + + describe('ModalBody', () => { + let wrapper: ShallowWrapper; + + beforeAll(() => { + wrapper = shallow(); + }); + + it('renders with the full remote Enterprise Search API URL', () => { + expect(wrapper.find(EuiCode).dive().dive().text()).toEqual( + 'http://localhost:3002/api/as/v1/engines/test-engine/documents' + ); + expect(wrapper.find(EuiCodeBlock).dive().dive().text()).toEqual( + expect.stringContaining('http://localhost:3002/api/as/v1/engines/test-engine/documents') + ); + }); + + it('renders with the API key', () => { + expect(wrapper.find(EuiCodeBlock).dive().dive().text()).toEqual( + expect.stringContaining('test-key') + ); + }); + }); + + describe('ModalFooter', () => { + it('closes the modal', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx new file mode 100644 index 00000000000000..1dd57ffe8bc014 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -0,0 +1,128 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import dedent from 'dedent'; +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButtonEmpty, + EuiText, + EuiLink, + EuiSpacer, + EuiPanel, + EuiBadge, + EuiCode, + EuiCodeBlock, +} from '@elastic/eui'; + +import { getEnterpriseSearchUrl } from '../../../../shared/enterprise_search_url'; +import { EngineLogic } from '../../engine'; +import { EngineDetails } from '../../engine/types'; + +import { DOCS_PREFIX } from '../../../routes'; +import { DOCUMENTS_API_JSON_EXAMPLE, MODAL_CANCEL_BUTTON } from '../constants'; +import { DocumentCreationLogic } from '../'; + +export const ApiCodeExample: React.FC = () => ( + <> + + + + +); + +export const ModalHeader: React.FC = () => { + return ( + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.title', { + defaultMessage: 'Indexing by API', + })} +

+
+
+ ); +}; + +export const ModalBody: React.FC = () => { + const { engineName, engine } = useValues(EngineLogic); + const { apiKey } = engine as EngineDetails; + + const documentsApiUrl = getEnterpriseSearchUrl(`/api/as/v1/engines/${engineName}/documents`); + + return ( + + +

+ + documents API + + ), + clientLibrariesLink: ( + + client libraries + + ), + }} + /> +

+

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.example', { + defaultMessage: + 'To see the API in action, you can experiment with the example request below using a command line or a client library.', + })} +

+
+ + + POST + {documentsApiUrl} + + + {dedent(` + curl -X POST '${documentsApiUrl}' + -H 'Content-Type: application/json' + -H 'Authorization: Bearer ${apiKey}' + -d '${DOCUMENTS_API_JSON_EXAMPLE}' + # Returns + # [ + # { + # "id": "park_rocky-mountain", + # "errors": [] + # }, + # { + # "id": "park_saguaro", + # "errors": [] + # } + # ] + `)} + +
+ ); +}; + +export const ModalFooter: React.FC = () => { + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + {MODAL_CANCEL_BUTTON} + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.ts new file mode 100644 index 00000000000000..b9a6f2b3e750f6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/index.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; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ShowCreationModes } from './show_creation_modes'; +export { ApiCodeExample } from './api_code_example'; +export { PasteJsonText } from './paste_json_text'; +export { UploadJsonFile } from './upload_json_file'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss new file mode 100644 index 00000000000000..cca179e8c06080 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.scss @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +.pasteJsonTextArea { + font-family: $euiCodeFontFamily; +} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx new file mode 100644 index 00000000000000..ede1529c049d70 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; +import { rerender } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiTextArea, EuiButtonEmpty, EuiButton } from '@elastic/eui'; + +import { PasteJsonText, ModalHeader, ModalBody, ModalFooter } from './paste_json_text'; + +describe('PasteJsonText', () => { + const values = { + textInput: 'hello world', + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + }, + }, + }; + const actions = { + setTextInput: jest.fn(), + closeDocumentCreation: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(ModalHeader)).toHaveLength(1); + expect(wrapper.find(ModalBody)).toHaveLength(1); + expect(wrapper.find(ModalFooter)).toHaveLength(1); + }); + + describe('ModalHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Create documents'); + }); + }); + + describe('ModalBody', () => { + it('renders and updates the textarea value', () => { + setMockValues({ ...values, textInput: 'lorem ipsum' }); + const wrapper = shallow(); + const textarea = wrapper.find(EuiTextArea); + + expect(textarea.prop('value')).toEqual('lorem ipsum'); + + textarea.simulate('change', { target: { value: 'dolor sit amet' } }); + expect(actions.setTextInput).toHaveBeenCalledWith('dolor sit amet'); + }); + }); + + describe('ModalFooter', () => { + it('closes the modal', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + + it('disables/enables the Continue button based on whether text has been entered', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(false); + + setMockValues({ ...values, textInput: '' }); + rerender(wrapper); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx new file mode 100644 index 00000000000000..614704701222b0 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButton, + EuiButtonEmpty, + EuiTextArea, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { AppLogic } from '../../../app_logic'; + +import { MODAL_CANCEL_BUTTON, MODAL_CONTINUE_BUTTON } from '../constants'; +import { DocumentCreationLogic } from '../'; + +import './paste_json_text.scss'; + +export const PasteJsonText: React.FC = () => ( + <> + + + + +); + +export const ModalHeader: React.FC = () => { + return ( + + +

+ {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.title', { + defaultMessage: 'Create documents', + })} +

+
+
+ ); +}; + +export const ModalBody: React.FC = () => { + const { configuredLimits } = useValues(AppLogic); + const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; + + const { textInput } = useValues(DocumentCreationLogic); + const { setTextInput } = useActions(DocumentCreationLogic); + + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.description', + { + defaultMessage: + 'Paste an array of JSON documents. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', + values: { maxDocumentByteSize }, + } + )} +

+
+ + setTextInput(e.target.value)} + aria-label={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.label', + { defaultMessage: 'Paste JSON here' } + )} + className="pasteJsonTextArea" + fullWidth + rows={12} + /> +
+ ); +}; + +export const ModalFooter: React.FC = () => { + const { textInput } = useValues(DocumentCreationLogic); + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + {MODAL_CANCEL_BUTTON} + + {MODAL_CONTINUE_BUTTON} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx new file mode 100644 index 00000000000000..eadcf6df473e5e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockActions } from '../../../../__mocks__/kea.mock'; + +import React from 'react'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { EuiButtonEmpty } from '@elastic/eui'; + +import { DocumentCreationButtons } from '../'; +import { ShowCreationModes } from './'; + +describe('ShowCreationModes', () => { + const actions = { + closeDocumentCreation: jest.fn(), + }; + let wrapper: ShallowWrapper; + + beforeAll(() => { + jest.clearAllMocks(); + setMockActions(actions); + wrapper = shallow(); + }); + + it('renders', () => { + expect(wrapper.find('h2').text()).toEqual('Add new documents'); + expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); + }); + + it('closes the modal', () => { + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx new file mode 100644 index 00000000000000..1f7c4db83ab06c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButtonEmpty, +} from '@elastic/eui'; + +import { MODAL_CANCEL_BUTTON } from '../constants'; +import { DocumentCreationLogic, DocumentCreationButtons } from '../'; + +export const ShowCreationModes: React.FC = () => { + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + <> + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title', + { defaultMessage: 'Add new documents' } + )} +

+
+
+ + + + + {MODAL_CANCEL_BUTTON} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx new file mode 100644 index 00000000000000..dae085617cad8d --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { setMockValues, setMockActions } from '../../../../__mocks__/kea.mock'; +import { rerender } from '../../../../__mocks__'; + +import React from 'react'; +import { shallow } from 'enzyme'; +import { EuiFilePicker, EuiButtonEmpty, EuiButton } from '@elastic/eui'; + +import { UploadJsonFile, ModalHeader, ModalBody, ModalFooter } from './upload_json_file'; + +describe('UploadJsonFile', () => { + const mockFile = new File(['mock'], 'mock.json', { type: 'application/json' }); + const values = { + fileInput: null, + configuredLimits: { + engine: { + maxDocumentByteSize: 102400, + }, + }, + }; + const actions = { + setFileInput: jest.fn(), + closeDocumentCreation: jest.fn(), + }; + + beforeEach(() => { + jest.clearAllMocks(); + setMockValues(values); + setMockActions(actions); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find(ModalHeader)).toHaveLength(1); + expect(wrapper.find(ModalBody)).toHaveLength(1); + expect(wrapper.find(ModalFooter)).toHaveLength(1); + }); + + describe('ModalHeader', () => { + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.find('h2').text()).toEqual('Drag and drop .json'); + }); + }); + + describe('ModalBody', () => { + it('updates fileInput when files are added & removed', () => { + const wrapper = shallow(); + + wrapper.find(EuiFilePicker).simulate('change', [mockFile]); + expect(actions.setFileInput).toHaveBeenCalledWith(mockFile); + + wrapper.find(EuiFilePicker).simulate('change', []); + expect(actions.setFileInput).toHaveBeenCalledWith(null); + }); + }); + + describe('ModalFooter', () => { + it('closes the modal', () => { + const wrapper = shallow(); + + wrapper.find(EuiButtonEmpty).simulate('click'); + expect(actions.closeDocumentCreation).toHaveBeenCalled(); + }); + + it('disables/enables the Continue button based on whether files have been uploaded', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + + setMockValues({ ...values, fineInput: mockFile }); + rerender(wrapper); + expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx new file mode 100644 index 00000000000000..d4c005d5cfa2b1 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { useValues, useActions } from 'kea'; + +import { i18n } from '@kbn/i18n'; +import { + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, + EuiButton, + EuiButtonEmpty, + EuiFilePicker, + EuiSpacer, + EuiText, +} from '@elastic/eui'; + +import { AppLogic } from '../../../app_logic'; + +import { MODAL_CANCEL_BUTTON, MODAL_CONTINUE_BUTTON } from '../constants'; +import { DocumentCreationLogic } from '../'; + +export const UploadJsonFile: React.FC = () => ( + <> + + + + +); + +export const ModalHeader: React.FC = () => { + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.title', + { defaultMessage: 'Drag and drop .json' } + )} +

+
+
+ ); +}; + +export const ModalBody: React.FC = () => { + const { configuredLimits } = useValues(AppLogic); + const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; + + const { setFileInput } = useActions(DocumentCreationLogic); + + return ( + + +

+ {i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.label', + { + defaultMessage: + 'If you have a .json file, drag and drop or upload it. Ensure the JSON is valid and that each document object is less than {maxDocumentByteSize} bytes.', + values: { maxDocumentByteSize }, + } + )} +

+
+ + setFileInput(files?.length ? files[0] : null)} + accept="application/json" + fullWidth + /> +
+ ); +}; + +export const ModalFooter: React.FC = () => { + const { fileInput } = useValues(DocumentCreationLogic); + const { closeDocumentCreation } = useActions(DocumentCreationLogic); + + return ( + + {MODAL_CANCEL_BUTTON} + + {MODAL_CONTINUE_BUTTON} + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts index ff38ab5add3672..1145d7853cb1a1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.test.ts @@ -5,7 +5,9 @@ */ import { resetContext } from 'kea'; +import dedent from 'dedent'; +import { DOCUMENTS_API_JSON_EXAMPLE } from './constants'; import { DocumentCreationStep } from './types'; import { DocumentCreationLogic } from './'; @@ -14,7 +16,10 @@ describe('DocumentCreationLogic', () => { isDocumentCreationOpen: false, creationMode: 'text', creationStep: DocumentCreationStep.AddDocuments, + textInput: dedent(DOCUMENTS_API_JSON_EXAMPLE), + fileInput: null, }; + const mockFile = new File(['mockFile'], 'mockFile.json'); const mount = () => { resetContext({}); @@ -130,5 +135,33 @@ describe('DocumentCreationLogic', () => { }); }); }); + + describe('setTextInput', () => { + describe('textInput', () => { + it('should be set to the provided value', () => { + mount(); + DocumentCreationLogic.actions.setTextInput('hello world'); + + expect(DocumentCreationLogic.values).toEqual({ + ...DEFAULT_VALUES, + textInput: 'hello world', + }); + }); + }); + }); + + describe('setFileInput', () => { + describe('fileInput', () => { + it('should be set to the provided value', () => { + mount(); + DocumentCreationLogic.actions.setFileInput(mockFile); + + expect(DocumentCreationLogic.values).toEqual({ + ...DEFAULT_VALUES, + fileInput: mockFile, + }); + }); + }); + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts index 26f7a1f3d50ece..a5e015391d8fd0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts @@ -5,13 +5,17 @@ */ import { kea, MakeLogicType } from 'kea'; +import dedent from 'dedent'; +import { DOCUMENTS_API_JSON_EXAMPLE } from './constants'; import { DocumentCreationMode, DocumentCreationStep } from './types'; interface DocumentCreationValues { isDocumentCreationOpen: boolean; creationMode: DocumentCreationMode; creationStep: DocumentCreationStep; + textInput: string; + fileInput: File | null; } interface DocumentCreationActions { @@ -19,6 +23,8 @@ interface DocumentCreationActions { openDocumentCreation(creationMode: DocumentCreationMode): { creationMode: DocumentCreationMode }; closeDocumentCreation(): void; setCreationStep(creationStep: DocumentCreationStep): { creationStep: DocumentCreationStep }; + setTextInput(textInput: string): { textInput: string }; + setFileInput(fileInput: File | null): { fileInput: File | null }; } export const DocumentCreationLogic = kea< @@ -30,6 +36,8 @@ export const DocumentCreationLogic = kea< openDocumentCreation: (creationMode) => ({ creationMode }), closeDocumentCreation: () => null, setCreationStep: (creationStep) => ({ creationStep }), + setTextInput: (textInput) => ({ textInput }), + setFileInput: (fileInput) => ({ fileInput }), }), reducers: () => ({ isDocumentCreationOpen: [ @@ -54,5 +62,17 @@ export const DocumentCreationLogic = kea< setCreationStep: (_, { creationStep }) => creationStep, }, ], + textInput: [ + dedent(DOCUMENTS_API_JSON_EXAMPLE), + { + setTextInput: (_, { textInput }) => textInput, + }, + ], + fileInput: [ + null, + { + setFileInput: (_, { fileInput }) => fileInput, + }, + ], }), }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx index a00aed96a6fbcb..a0bca62dc74191 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx @@ -8,10 +8,17 @@ import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; import React from 'react'; import { shallow } from 'enzyme'; -import { EuiModal, EuiModalBody } from '@elastic/eui'; - +import { EuiModal } from '@elastic/eui'; + +import { + ShowCreationModes, + ApiCodeExample, + PasteJsonText, + UploadJsonFile, +} from './creation_mode_components'; import { DocumentCreationStep } from './types'; -import { DocumentCreationModal, DocumentCreationButtons } from './'; + +import { DocumentCreationModal, ModalContent } from './document_creation_modal'; describe('DocumentCreationModal', () => { const values = { @@ -44,58 +51,58 @@ describe('DocumentCreationModal', () => { expect(wrapper.isEmptyRender()).toBe(true); }); - describe('modal content', () => { - it('renders document creation mode buttons', () => { + describe('ModalContent', () => { + it('renders ShowCreationModes', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowCreationModes }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); + expect(wrapper.find(ShowCreationModes)).toHaveLength(1); }); describe('creation modes', () => { it('renders ApiCodeExample', () => { setMockValues({ ...values, creationMode: 'api' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('ApiCodeExample'); // TODO: actual component + expect(wrapper.find(ApiCodeExample)).toHaveLength(1); }); it('renders PasteJsonText', () => { setMockValues({ ...values, creationMode: 'text' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('PasteJsonText'); // TODO: actual component + expect(wrapper.find(PasteJsonText)).toHaveLength(1); }); it('renders UploadJsonFile', () => { setMockValues({ ...values, creationMode: 'file' }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('UploadJsonFile'); // TODO: actual component + expect(wrapper.find(UploadJsonFile)).toHaveLength(1); }); }); describe('creation steps', () => { it('renders an error page', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowError }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationError'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationError'); // TODO: actual component }); it('renders an error summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowErrorSummary }); - const wrapper = shallow(); + const wrapper = shallow(); - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationSummary'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component }); it('renders a success summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowSuccessSummary }); - const wrapper = shallow(); + const wrapper = shallow(); // TODO: Figure out if the error and success summary should remain the same vs different components - expect(wrapper.find(EuiModalBody).dive().text()).toBe('DocumentCreationSummary'); // TODO: actual component + expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx index 95ce5456ef9a83..e6662a7c30407b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx @@ -7,52 +7,51 @@ import React from 'react'; import { useValues, useActions } from 'kea'; -import { i18n } from '@kbn/i18n'; -import { - EuiOverlayMask, - EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, -} from '@elastic/eui'; - -import { DocumentCreationLogic, DocumentCreationButtons } from './'; +import { EuiOverlayMask, EuiModal } from '@elastic/eui'; + +import { DocumentCreationLogic } from './'; import { DocumentCreationStep } from './types'; +import { + ShowCreationModes, + ApiCodeExample, + PasteJsonText, + UploadJsonFile, +} from './creation_mode_components'; + export const DocumentCreationModal: React.FC = () => { const { closeDocumentCreation } = useActions(DocumentCreationLogic); - const { isDocumentCreationOpen, creationMode, creationStep } = useValues(DocumentCreationLogic); + const { isDocumentCreationOpen } = useValues(DocumentCreationLogic); - if (!isDocumentCreationOpen) return null; - - return ( + return isDocumentCreationOpen ? ( - - - {i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.modalTitle', { - defaultMessage: 'Document Import', - })} - - - - {creationStep === DocumentCreationStep.ShowError && <>DocumentCreationError} - {creationStep === DocumentCreationStep.ShowCreationModes && } - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'api' && ( - <>ApiCodeExample - )} - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'text' && ( - <>PasteJsonText - )} - {creationStep === DocumentCreationStep.AddDocuments && creationMode === 'file' && ( - <>UploadJsonFile - )} - {creationStep === DocumentCreationStep.ShowErrorSummary && <>DocumentCreationSummary} - {creationStep === DocumentCreationStep.ShowSuccessSummary && <>DocumentCreationSummary} - - + - ); + ) : null; +}; + +export const ModalContent: React.FC = () => { + const { creationStep, creationMode } = useValues(DocumentCreationLogic); + + switch (creationStep) { + case DocumentCreationStep.ShowCreationModes: + return ; + case DocumentCreationStep.AddDocuments: + switch (creationMode) { + case 'api': + return ; + case 'text': + return ; + case 'file': + return ; + } + case DocumentCreationStep.ShowError: + return <>DocumentCreationError; + case DocumentCreationStep.ShowErrorSummary: + return <>DocumentCreationSummary; + case DocumentCreationStep.ShowSuccessSummary: + return <>DocumentCreationSummary; + } }; From e71610630734d6b55df9731f2ee5a2876eed5729 Mon Sep 17 00:00:00 2001 From: Dan Panzarella Date: Fri, 18 Dec 2020 13:41:05 -0500 Subject: [PATCH 08/40] [Security Solution] Correct Policy Config to current license level on fetch (#85206) --- .../common/license/license.ts | 2 +- .../components/current_license/index.tsx | 29 ++++++++++++ .../policy/store/policy_details/action.ts | 9 +++- .../policy/store/policy_details/index.test.ts | 44 +++++++++++++++++++ .../policy/store/policy_details/middleware.ts | 1 - .../policy/store/policy_details/reducer.ts | 8 ++++ .../policy/store/policy_details/selectors.ts | 24 +++++++++- .../public/management/pages/policy/types.ts | 3 ++ .../with_security_context.tsx | 5 ++- .../public/management/routes.tsx | 11 +++-- 10 files changed, 126 insertions(+), 10 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/common/components/current_license/index.tsx diff --git a/x-pack/plugins/security_solution/common/license/license.ts b/x-pack/plugins/security_solution/common/license/license.ts index 2d424ab9c960ad..9c093016f4a38c 100644 --- a/x-pack/plugins/security_solution/common/license/license.ts +++ b/x-pack/plugins/security_solution/common/license/license.ts @@ -51,5 +51,5 @@ export class LicenseService { } export const isAtLeast = (license: ILicense | null, level: LicenseType): boolean => { - return license !== null && license.isAvailable && license.isActive && license.hasAtLeast(level); + return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); }; diff --git a/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx b/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx new file mode 100644 index 00000000000000..27d34f5cf418f9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/current_license/index.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { FC, memo, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { Dispatch } from 'redux'; +import { licenseService } from '../../hooks/use_license'; +import { AppAction } from '../../store/actions'; +import { ILicense } from '../../../../../licensing/common/types'; + +export const CurrentLicense: FC = memo(({ children }) => { + const dispatch = useDispatch>(); + useEffect(() => { + const subscription = licenseService + .getLicenseInformation$() + ?.subscribe((licenseInformation: ILicense) => { + dispatch({ + type: 'licenseChanged', + payload: licenseInformation, + }); + }); + return () => subscription?.unsubscribe(); + }, [dispatch]); + return <>{children}; +}); + +CurrentLicense.displayName = 'CurrentLicense'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts index bda408cd00e75e..573442de807ae9 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ILicense } from '../../../../../../../licensing/common/types'; import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec'; import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; @@ -62,6 +63,11 @@ interface UserClickedPolicyDetailsSaveButton { type: 'userClickedPolicyDetailsSaveButton'; } +interface LicenseChanged { + type: 'licenseChanged'; + payload: ILicense; +} + export type PolicyDetailsAction = | ServerReturnedPolicyDetailsData | UserClickedPolicyDetailsSaveButton @@ -70,4 +76,5 @@ export type PolicyDetailsAction = | ServerReturnedUpdatedPolicyDetailsData | ServerFailedToReturnPolicyDetailsData | UserChangedPolicyConfig - | UserChangedAntivirusRegistration; + | UserChangedAntivirusRegistration + | LicenseChanged; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index 69c2afbd019607..70ffc1f8a9fc40 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -20,6 +20,7 @@ import { } from '../../../../../common/mock/endpoint'; import { HttpFetchOptions } from 'kibana/public'; import { cloneDeep } from 'lodash'; +import { licenseMock } from '../../../../../../../licensing/common/licensing.mock'; describe('policy details: ', () => { let store: Store; @@ -151,6 +152,49 @@ describe('policy details: ', () => { expect(config!.linux.events.file).toEqual(true); }); }); + + describe('when the policy config has paid features enabled', () => { + const CustomMessage = 'Some Popup message change'; + const Basic = licenseMock.createLicense({ license: { type: 'basic', mode: 'basic' } }); + const Platinum = licenseMock.createLicense({ + license: { type: 'platinum', mode: 'platinum' }, + }); + + beforeEach(() => { + const config = policyConfig(getState()); + if (!config) { + throw new Error(); + } + + // have a paid-policy field existing in the store from a previous time + const newPayload1 = cloneDeep(config); + newPayload1.windows.popup.malware.message = CustomMessage; + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload1 }, + }); + }); + + it('preserves paid fields when license level allows', () => { + dispatch({ + type: 'licenseChanged', + payload: Platinum, + }); + const config = policyConfig(getState()); + + expect(config.windows.popup.malware.message).toEqual(CustomMessage); + }); + + it('reverts paid fields to default when license level does not allow', () => { + dispatch({ + type: 'licenseChanged', + payload: Basic, + }); + const config = policyConfig(getState()); + + expect(config.windows.popup.malware.message).not.toEqual(CustomMessage); + }); + }); }); describe('when saving policy data', () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts index f039324b3af648..2f9f0d67237495 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts @@ -26,7 +26,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory { const http = coreStart.http; - return ({ getState, dispatch }) => (next) => async (action) => { next(action); const state = getState(); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts index bcdc7ba2089c64..a6e94d3715ca35 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts @@ -45,6 +45,7 @@ export const initialPolicyDetailsState: () => Immutable = () total: 0, other: 0, }, + license: undefined, }); export const policyDetailsReducer: ImmutableReducer = ( @@ -93,6 +94,13 @@ export const policyDetailsReducer: ImmutableReducer = { ...state, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts index 77e975a46d37bb..c52bef9a23b25b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts @@ -6,6 +6,8 @@ import { createSelector } from 'reselect'; import { matchPath } from 'react-router-dom'; +import { ILicense } from '../../../../../../../licensing/common/types'; +import { unsetPolicyFeaturesAboveLicenseLevel } from '../../../../../../common/license/policy_config'; import { PolicyDetailsState } from '../../types'; import { Immutable, @@ -20,6 +22,24 @@ import { ManagementRoutePolicyDetailsParams } from '../../../../types'; /** Returns the policy details */ export const policyDetails = (state: Immutable) => state.policyItem; +/** Returns current active license */ +export const licenseState = (state: Immutable) => state.license; + +export const licensedPolicy: ( + state: Immutable +) => Immutable | undefined = createSelector( + policyDetails, + licenseState, + (policyData, license) => { + if (policyData) { + unsetPolicyFeaturesAboveLicenseLevel( + policyData?.inputs[0]?.config.policy.value, + license as ILicense + ); + } + return policyData; + } +); /** * Given a Policy Data (package policy) object, return back a new object with only the field @@ -75,7 +95,7 @@ export const getPolicyDataForUpdate = ( */ export const policyDetailsForUpdate: ( state: Immutable -) => Immutable | undefined = createSelector(policyDetails, (policy) => { +) => Immutable | undefined = createSelector(licensedPolicy, (policy) => { if (policy) { return getPolicyDataForUpdate(policy); } @@ -111,7 +131,7 @@ const defaultFullPolicy: Immutable = policyConfigFactory(); * Note: this will return a default full policy if the `policyItem` is `undefined` */ export const fullPolicy: (s: Immutable) => PolicyConfig = createSelector( - policyDetails, + licensedPolicy, (policyData) => { return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy; } diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index 3926ad2220e35d..889bcc15d8df00 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ILicense } from '../../../../../licensing/common/types'; import { AppLocation, Immutable, @@ -66,6 +67,8 @@ export interface PolicyDetailsState { success: boolean; error?: ServerApiError; }; + /** current license */ + license?: ILicense; } /** diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx index f65dbaf1087d8e..118ebdf56db907 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx @@ -8,6 +8,7 @@ import React, { ComponentType, memo } from 'react'; import { CoreStart } from 'kibana/public'; import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; import { Provider as ReduxStoreProvider } from 'react-redux'; +import { CurrentLicense } from '../../../../../common/components/current_license'; import { StartPlugins } from '../../../../../types'; import { managementReducer } from '../../../../store/reducer'; import { managementMiddlewareFactory } from '../../../../store/middleware'; @@ -57,7 +58,9 @@ export const withSecurityContext =

({ return ( - + + + ); }); diff --git a/x-pack/plugins/security_solution/public/management/routes.tsx b/x-pack/plugins/security_solution/public/management/routes.tsx index 209d7dd6dbcde4..bc24b9ca519800 100644 --- a/x-pack/plugins/security_solution/public/management/routes.tsx +++ b/x-pack/plugins/security_solution/public/management/routes.tsx @@ -8,13 +8,16 @@ import React from 'react'; import { Route, Switch } from 'react-router-dom'; import { ManagementContainer } from './pages'; import { NotFoundPage } from '../app/404'; +import { CurrentLicense } from '../common/components/current_license'; /** * Returns the React Router Routes for the management area */ export const ManagementRoutes = () => ( - - - } /> - + + + + } /> + + ); From 3379763965cec86d6a907bd311111939c831fc15 Mon Sep 17 00:00:00 2001 From: Scotty Bollinger Date: Fri, 18 Dec 2020 12:54:59 -0600 Subject: [PATCH 09/40] [Workplace Search] Refactor AddSource component state and add tests (#86482) * Rename variables This will make reviewing later commits easier * Use internal routing instead of history.push Also updates path to Loading * Move state from component to logic * Add tests --- .../components/add_source/add_source.test.tsx | 164 ++++++++++++++++++ .../components/add_source/add_source.tsx | 89 ++++------ .../add_source/add_source_logic.test.ts | 52 ++++++ .../components/add_source/add_source_logic.ts | 67 +++++++ 4 files changed, 312 insertions(+), 60 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx new file mode 100644 index 00000000000000..a45094ac55ba0a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.test.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import '../../../../../__mocks__/kea.mock'; +import '../../../../../__mocks__/shallow_useeffect.mock'; + +import { setMockActions, setMockValues } from '../../../../../__mocks__'; +import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; + +jest.mock('../../../../../shared/kibana', () => ({ + KibanaLogic: { values: { navigateToUrl: jest.fn() } }, +})); +import { KibanaLogic } from '../../../../../shared/kibana'; + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { Loading } from '../../../../../shared/loading'; + +import { AddSource } from './add_source'; +import { AddSourceSteps } from './add_source_logic'; +import { ConfigCompleted } from './config_completed'; +import { ConfigurationIntro } from './configuration_intro'; +import { ConfigureCustom } from './configure_custom'; +import { ConfigureOauth } from './configure_oauth'; +import { ConnectInstance } from './connect_instance'; +import { ReAuthenticate } from './re_authenticate'; +import { SaveConfig } from './save_config'; +import { SaveCustom } from './save_custom'; + +describe('AddSourceList', () => { + const initializeAddSource = jest.fn(); + const setAddSourceStep = jest.fn(); + const saveSourceConfig = jest.fn((_, setConfigCompletedStep) => { + setConfigCompletedStep(); + }); + const createContentSource = jest.fn((_, formSubmitSuccess) => { + formSubmitSuccess(); + }); + const resetSourcesState = jest.fn(); + + const mockValues = { + addSourceCurrentStep: AddSourceSteps.ConfigIntroStep, + sourceConfigData, + dataLoading: false, + newCustomSource: {}, + isOrganization: true, + }; + + beforeEach(() => { + setMockActions({ + initializeAddSource, + setAddSourceStep, + saveSourceConfig, + createContentSource, + resetSourcesState, + }); + setMockValues(mockValues); + }); + + it('renders default state', () => { + const wrapper = shallow(); + wrapper.find(ConfigurationIntro).prop('advanceStep')(); + + expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.SaveConfigStep); + }); + + it('handles loading state', () => { + setMockValues({ ...mockValues, dataLoading: true }); + const wrapper = shallow(); + + expect(wrapper.find(Loading)).toHaveLength(1); + }); + + it('renders Config Completed step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.ConfigCompletedStep, + }); + const wrapper = shallow(); + wrapper.find(ConfigCompleted).prop('advanceStep')(); + + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith( + '/sources/add/confluence_cloud/connect' + ); + expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep); + }); + + it('renders Save Config step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.SaveConfigStep, + }); + const wrapper = shallow(); + const saveConfig = wrapper.find(SaveConfig); + saveConfig.prop('advanceStep')(); + saveConfig.prop('goBackStep')!(); + + expect(setAddSourceStep).toHaveBeenCalledWith(AddSourceSteps.ConfigIntroStep); + expect(saveSourceConfig).toHaveBeenCalled(); + }); + + it('renders Connect Instance step', () => { + setMockValues({ + ...mockValues, + sourceConfigData, + addSourceCurrentStep: AddSourceSteps.ConnectInstanceStep, + }); + const wrapper = shallow(); + wrapper.find(ConnectInstance).prop('onFormCreated')('foo'); + + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith( + '/sources/add/confluence_cloud/connect' + ); + }); + + it('renders Configure Custom step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.ConfigureCustomStep, + }); + const wrapper = shallow(); + wrapper.find(ConfigureCustom).prop('advanceStep')(); + + expect(createContentSource).toHaveBeenCalled(); + }); + + it('renders Configure Oauth step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.ConfigureOauthStep, + }); + const wrapper = shallow(); + + wrapper.find(ConfigureOauth).prop('onFormCreated')('foo'); + + expect(KibanaLogic.values.navigateToUrl).toHaveBeenCalledWith( + '/sources/add/confluence_cloud/connect' + ); + }); + + it('renders Save Custom step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.SaveCustomStep, + }); + const wrapper = shallow(); + + expect(wrapper.find(SaveCustom)).toHaveLength(1); + }); + + it('renders ReAuthenticate step', () => { + setMockValues({ + ...mockValues, + addSourceCurrentStep: AddSourceSteps.ReAuthenticateStep, + }); + const wrapper = shallow(); + + expect(wrapper.find(ReAuthenticate)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx index eca363da89433d..b3f02c831d977c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source.tsx @@ -4,17 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect } from 'react'; -import { History } from 'history'; import { useActions, useValues } from 'kea'; -import { useHistory } from 'react-router-dom'; import { AppLogic } from '../../../../app_logic'; -import { Loading } from '../../../../../../applications/shared/loading'; +import { KibanaLogic } from '../../../../../shared/kibana'; +import { Loading } from '../../../../../shared/loading'; import { CUSTOM_SERVICE_TYPE } from '../../../../constants'; import { staticSourceData } from '../../source_data'; -import { AddSourceLogic } from './add_source_logic'; +import { AddSourceLogic, AddSourceProps, AddSourceSteps } from './add_source_logic'; import { SourceDataItem } from '../../../../types'; import { SOURCE_ADDED_PATH, getSourcesPath } from '../../../../routes'; @@ -28,38 +27,16 @@ import { ReAuthenticate } from './re_authenticate'; import { SaveConfig } from './save_config'; import { SaveCustom } from './save_custom'; -enum Steps { - ConfigIntroStep = 'Config Intro', - SaveConfigStep = 'Save Config', - ConfigCompletedStep = 'Config Completed', - ConnectInstanceStep = 'Connect Instance', - ConfigureCustomStep = 'Configure Custom', - ConfigureOauthStep = 'Configure Oauth', - SaveCustomStep = 'Save Custom', - ReAuthenticateStep = 'ReAuthenticate', -} - -interface AddSourceProps { - sourceIndex: number; - connect?: boolean; - configure?: boolean; - reAuthenticate?: boolean; -} - -export const AddSource: React.FC = ({ - sourceIndex, - connect, - configure, - reAuthenticate, -}) => { - const history = useHistory() as History; +export const AddSource: React.FC = (props) => { const { - getSourceConfigData, + initializeAddSource, + setAddSourceStep, saveSourceConfig, createContentSource, resetSourceState, } = useActions(AddSourceLogic); const { + addSourceCurrentStep, sourceConfigData: { name, categories, @@ -79,54 +56,44 @@ export const AddSource: React.FC = ({ sourceDescription, connectStepDescription, addPath, - } = staticSourceData[sourceIndex] as SourceDataItem; + } = staticSourceData[props.sourceIndex] as SourceDataItem; const { isOrganization } = useValues(AppLogic); useEffect(() => { - getSourceConfigData(serviceType); + initializeAddSource(props); return resetSourceState; }, []); - const isCustom = serviceType === CUSTOM_SERVICE_TYPE; - - const getFirstStep = () => { - if (isCustom) return Steps.ConfigureCustomStep; - if (connect) return Steps.ConnectInstanceStep; - if (configure) return Steps.ConfigureOauthStep; - if (reAuthenticate) return Steps.ReAuthenticateStep; - return Steps.ConfigIntroStep; - }; - - const [currentStep, setStep] = useState(getFirstStep()); - if (dataLoading) return ; - const goToConfigurationIntro = () => setStep(Steps.ConfigIntroStep); - const goToSaveConfig = () => setStep(Steps.SaveConfigStep); - const setConfigCompletedStep = () => setStep(Steps.ConfigCompletedStep); + const goToConfigurationIntro = () => setAddSourceStep(AddSourceSteps.ConfigIntroStep); + const goToSaveConfig = () => setAddSourceStep(AddSourceSteps.SaveConfigStep); + const setConfigCompletedStep = () => setAddSourceStep(AddSourceSteps.ConfigCompletedStep); const goToConfigCompleted = () => saveSourceConfig(false, setConfigCompletedStep); const goToConnectInstance = () => { - setStep(Steps.ConnectInstanceStep); - history.push(`${getSourcesPath(addPath, isOrganization)}/connect`); + setAddSourceStep(AddSourceSteps.ConnectInstanceStep); + KibanaLogic.values.navigateToUrl(`${getSourcesPath(addPath, isOrganization)}/connect`); }; - const saveCustomSuccess = () => setStep(Steps.SaveCustomStep); + const saveCustomSuccess = () => setAddSourceStep(AddSourceSteps.SaveCustomStep); const goToSaveCustom = () => createContentSource(CUSTOM_SERVICE_TYPE, saveCustomSuccess); const goToFormSourceCreated = (sourceName: string) => { - history.push(`${getSourcesPath(SOURCE_ADDED_PATH, isOrganization)}/?name=${sourceName}`); + KibanaLogic.values.navigateToUrl( + `${getSourcesPath(SOURCE_ADDED_PATH, isOrganization)}/?name=${sourceName}` + ); }; const header = ; return ( <> - {currentStep === Steps.ConfigIntroStep && ( + {addSourceCurrentStep === AddSourceSteps.ConfigIntroStep && ( )} - {currentStep === Steps.SaveConfigStep && ( + {addSourceCurrentStep === AddSourceSteps.SaveConfigStep && ( = ({ header={header} /> )} - {currentStep === Steps.ConfigCompletedStep && ( + {addSourceCurrentStep === AddSourceSteps.ConfigCompletedStep && ( = ({ header={header} /> )} - {currentStep === Steps.ConnectInstanceStep && ( + {addSourceCurrentStep === AddSourceSteps.ConnectInstanceStep && ( = ({ header={header} /> )} - {currentStep === Steps.ConfigureCustomStep && ( + {addSourceCurrentStep === AddSourceSteps.ConfigureCustomStep && ( )} - {currentStep === Steps.ConfigureOauthStep && ( + {addSourceCurrentStep === AddSourceSteps.ConfigureOauthStep && ( )} - {currentStep === Steps.SaveCustomStep && ( + {addSourceCurrentStep === AddSourceSteps.SaveCustomStep && ( = ({ header={header} /> )} - {currentStep === Steps.ReAuthenticateStep && } + {addSourceCurrentStep === AddSourceSteps.ReAuthenticateStep && ( + + )} ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index ba38b46aa05521..084c3d1fb9c4f1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -32,6 +32,7 @@ import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; import { AddSourceLogic, + AddSourceSteps, SourceConfigData, SourceConnectData, OrganizationsMap, @@ -39,6 +40,8 @@ import { describe('AddSourceLogic', () => { const defaultValues = { + addSourceCurrentStep: AddSourceSteps.ConfigIntroStep, + addSourceProps: {}, dataLoading: true, sectionLoading: true, buttonLoading: false, @@ -70,6 +73,8 @@ describe('AddSourceLogic', () => { githubOrganizations: ['foo', 'bar'], }; + const CUSTOM_SERVICE_TYPE_INDEX = 17; + const clearFlashMessagesSpy = jest.spyOn(FlashMessagesLogic.actions, 'clearFlashMessages'); beforeEach(() => { @@ -224,6 +229,53 @@ describe('AddSourceLogic', () => { }); describe('listeners', () => { + it('initializeAddSource', () => { + const addSourceProps = { sourceIndex: 1 }; + const getSourceConfigDataSpy = jest.spyOn(AddSourceLogic.actions, 'getSourceConfigData'); + const setAddSourcePropsSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceProps'); + const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep'); + + AddSourceLogic.actions.initializeAddSource(addSourceProps); + + expect(setAddSourcePropsSpy).toHaveBeenCalledWith({ addSourceProps }); + expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConfigIntroStep); + expect(getSourceConfigDataSpy).toHaveBeenCalledWith('confluence_cloud'); + }); + + describe('getFirstStep', () => { + it('sets custom as first step', () => { + const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep'); + const addSourceProps = { sourceIndex: CUSTOM_SERVICE_TYPE_INDEX }; + AddSourceLogic.actions.initializeAddSource(addSourceProps); + + expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConfigureCustomStep); + }); + + it('sets connect as first step', () => { + const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep'); + const addSourceProps = { sourceIndex: 1, connect: true }; + AddSourceLogic.actions.initializeAddSource(addSourceProps); + + expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConnectInstanceStep); + }); + + it('sets configure as first step', () => { + const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep'); + const addSourceProps = { sourceIndex: 1, configure: true }; + AddSourceLogic.actions.initializeAddSource(addSourceProps); + + expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ConfigureOauthStep); + }); + + it('sets reAuthenticate as first step', () => { + const setAddSourceStepSpy = jest.spyOn(AddSourceLogic.actions, 'setAddSourceStep'); + const addSourceProps = { sourceIndex: 1, reAuthenticate: true }; + AddSourceLogic.actions.initializeAddSource(addSourceProps); + + expect(setAddSourceStepSpy).toHaveBeenCalledWith(AddSourceSteps.ReAuthenticateStep); + }); + }); + describe('organization context', () => { describe('getSourceConfigData', () => { it('calls API and sets values', async () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index c487b584d8acef..ec5cf541c2316e 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -18,10 +18,40 @@ import { FlashMessagesLogic, } from '../../../../../shared/flash_messages'; +import { staticSourceData } from '../../source_data'; +import { CUSTOM_SERVICE_TYPE } from '../../../../constants'; + import { AppLogic } from '../../../../app_logic'; import { CustomSource } from '../../../../types'; +export interface AddSourceProps { + sourceIndex: number; + connect?: boolean; + configure?: boolean; + reAuthenticate?: boolean; +} + +export enum AddSourceSteps { + ConfigIntroStep = 'Config Intro', + SaveConfigStep = 'Save Config', + ConfigCompletedStep = 'Config Completed', + ConnectInstanceStep = 'Connect Instance', + ConfigureCustomStep = 'Configure Custom', + ConfigureOauthStep = 'Configure Oauth', + SaveCustomStep = 'Save Custom', + ReAuthenticateStep = 'ReAuthenticate', +} + export interface AddSourceActions { + initializeAddSource: (addSourceProps: AddSourceProps) => { addSourceProps: AddSourceProps }; + setAddSourceProps: ({ + addSourceProps, + }: { + addSourceProps: AddSourceProps; + }) => { + addSourceProps: AddSourceProps; + }; + setAddSourceStep(addSourceCurrentStep: AddSourceSteps): AddSourceSteps; setSourceConfigData(sourceConfigData: SourceConfigData): SourceConfigData; setSourceConnectData(sourceConnectData: SourceConnectData): SourceConnectData; setClientIdValue(clientIdValue: string): string; @@ -83,6 +113,8 @@ export interface OrganizationsMap { } interface AddSourceValues { + addSourceProps: AddSourceProps; + addSourceCurrentStep: AddSourceSteps; dataLoading: boolean; sectionLoading: boolean; buttonLoading: boolean; @@ -112,6 +144,11 @@ interface PreContentSourceResponse { export const AddSourceLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'add_source_logic'], actions: { + initializeAddSource: (addSourceProps: AddSourceProps) => ({ addSourceProps }), + setAddSourceProps: ({ addSourceProps }: { addSourceProps: AddSourceProps }) => ({ + addSourceProps, + }), + setAddSourceStep: (addSourceCurrentStep: AddSourceSteps) => addSourceCurrentStep, setSourceConfigData: (sourceConfigData: SourceConfigData) => sourceConfigData, setSourceConnectData: (sourceConnectData: SourceConnectData) => sourceConnectData, setClientIdValue: (clientIdValue: string) => clientIdValue, @@ -145,6 +182,18 @@ export const AddSourceLogic = kea false, }, reducers: { + addSourceProps: [ + {} as AddSourceProps, + { + setAddSourceProps: (_, { addSourceProps }) => addSourceProps, + }, + ], + addSourceCurrentStep: [ + AddSourceSteps.ConfigIntroStep, + { + setAddSourceStep: (_, addSourceCurrentStep) => addSourceCurrentStep, + }, + ], sourceConfigData: [ {} as SourceConfigData, { @@ -282,6 +331,12 @@ export const AddSourceLogic = kea ({ + initializeAddSource: ({ addSourceProps }) => { + const { serviceType } = staticSourceData[addSourceProps.sourceIndex]; + actions.setAddSourceProps({ addSourceProps }); + actions.setAddSourceStep(getFirstStep(addSourceProps)); + actions.getSourceConfigData(serviceType); + }, getSourceConfigData: async ({ serviceType }) => { const route = `/api/workplace_search/org/settings/connectors/${serviceType}`; @@ -435,3 +490,15 @@ export const AddSourceLogic = kea { + const { sourceIndex, connect, configure, reAuthenticate } = props; + const { serviceType } = staticSourceData[sourceIndex]; + const isCustom = serviceType === CUSTOM_SERVICE_TYPE; + + if (isCustom) return AddSourceSteps.ConfigureCustomStep; + if (connect) return AddSourceSteps.ConnectInstanceStep; + if (configure) return AddSourceSteps.ConfigureOauthStep; + if (reAuthenticate) return AddSourceSteps.ReAuthenticateStep; + return AddSourceSteps.ConfigIntroStep; +}; From b484071096f6e45de3efbbf1b31738a3182091a9 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Fri, 18 Dec 2020 11:07:26 -0800 Subject: [PATCH 10/40] [Security Solution][Endpoint][Admin] Disables malware checkbox when switch is off and can now save in detect mode (#86402) --- .../view/policy_forms/protections/malware.tsx | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx index d611c4102e8f8c..8e631e497e57bd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/malware.tsx @@ -55,16 +55,19 @@ const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: const radioButtonId = useMemo(() => htmlIdGenerator()(), []); // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; + const isPlatinumPlus = useLicense().isPlatinumPlus(); const handleRadioChange = useCallback(() => { if (policyDetailsConfig) { const newPayload = cloneDeep(policyDetailsConfig); for (const os of OSes) { newPayload[os][protection].mode = id; - if (id === ProtectionModes.prevent) { - newPayload[os].popup[protection].enabled = true; - } else { - newPayload[os].popup[protection].enabled = false; + if (isPlatinumPlus) { + if (id === ProtectionModes.prevent) { + newPayload[os].popup[protection].enabled = true; + } else { + newPayload[os].popup[protection].enabled = false; + } } } dispatch({ @@ -72,7 +75,7 @@ const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: payload: { policyConfig: newPayload }, }); } - }, [dispatch, id, policyDetailsConfig]); + }, [dispatch, id, policyDetailsConfig, isPlatinumPlus]); /** * Passing an arbitrary id because EuiRadio @@ -158,12 +161,16 @@ export const MalwareProtections = React.memo(() => { if (event.target.checked === false) { for (const os of OSes) { newPayload[os][protection].mode = ProtectionModes.off; - newPayload[os].popup[protection].enabled = event.target.checked; + if (isPlatinumPlus) { + newPayload[os].popup[protection].enabled = event.target.checked; + } } } else { for (const os of OSes) { newPayload[os][protection].mode = ProtectionModes.prevent; - newPayload[os].popup[protection].enabled = event.target.checked; + if (isPlatinumPlus) { + newPayload[os].popup[protection].enabled = event.target.checked; + } } } dispatch({ @@ -172,7 +179,7 @@ export const MalwareProtections = React.memo(() => { }); } }, - [dispatch, policyDetailsConfig] + [dispatch, policyDetailsConfig, isPlatinumPlus] ); const handleUserNotificationCheckbox = useCallback( @@ -243,6 +250,7 @@ export const MalwareProtections = React.memo(() => { id="xpack.securitySolution.endpoint.policyDetail.malware.userNotification" onChange={handleUserNotificationCheckbox} checked={userNotificationSelected} + disabled={selected === ProtectionModes.off} label={i18n.translate( 'xpack.securitySolution.endpoint.policyDetail.malware.notifyUser', { @@ -305,6 +313,7 @@ export const MalwareProtections = React.memo(() => { ); }, [ radios, + selected, isPlatinumPlus, handleUserNotificationCheckbox, userNotificationSelected, From 73068e755c115f7a14101ad2d2b54ad98d072a69 Mon Sep 17 00:00:00 2001 From: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Fri, 18 Dec 2020 11:45:29 -0800 Subject: [PATCH 11/40] [Security Solution][Endpoint][Admin] Remove spaces in custom malware message brackets (#86393) --- .../common/endpoint/models/policy_config.ts | 2 +- .../apps/endpoint/policy_details.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 22037c021701f7..14941b019421b8 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -72,4 +72,4 @@ export const factory = (): PolicyConfig => { /** * Reflects what string the Endpoint will use when message field is default/empty */ -export const DefaultMalwareMessage = 'Elastic Security { action } { filename }'; +export const DefaultMalwareMessage = 'Elastic Security {action} {filename}'; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index 1a5c99294c281a..e344d4c3c27e49 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -222,7 +222,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, }, @@ -241,7 +241,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, antivirus_registration: { @@ -366,7 +366,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, }, @@ -385,7 +385,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, antivirus_registration: { @@ -503,7 +503,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, }, @@ -522,7 +522,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { popup: { malware: { enabled: true, - message: 'Elastic Security { action } { filename }', + message: 'Elastic Security {action} {filename}', }, }, antivirus_registration: { From c33835e87cab3f1ad9f66a32520b9080541334ca Mon Sep 17 00:00:00 2001 From: Jonathan Budzenski Date: Fri, 18 Dec 2020 14:02:21 -0600 Subject: [PATCH 12/40] [docs] Add kibana-encryption-keys (#84577) Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../security/encryption-keys/index.asciidoc | 44 +++++++++++++++++++ docs/user/security/index.asciidoc | 1 + 2 files changed, 45 insertions(+) create mode 100644 docs/user/security/encryption-keys/index.asciidoc diff --git a/docs/user/security/encryption-keys/index.asciidoc b/docs/user/security/encryption-keys/index.asciidoc new file mode 100644 index 00000000000000..58c0c0bb775caa --- /dev/null +++ b/docs/user/security/encryption-keys/index.asciidoc @@ -0,0 +1,44 @@ +[[kibana-encryption-keys]] +=== Set up encryptions keys to protect sensitive information + +The `kibana-encryption-keys` command helps you set up encryption keys that {kib} uses +to protect sensitive information. + +[discrete] +=== Synopsis + +[source,shell] +-------------------------------------------------- +bin/kibana-encryption-keys generate +[-i, --interactive] [-q, --quiet] +[-f, --force] [-h, --help] +-------------------------------------------------- + +[discrete] +=== Description + +{kib} uses encryption keys in several areas, ranging from encrypting data +in {kib} associated indices to storing session information. By defining these +encryption keys in your configuration, you'll ensure consistent operations +across restarts. + +[discrete] +[[encryption-key-parameters]] +=== Parameters + +`generate`:: Randomly generates passwords to the console. + +`-i, --interactive`:: Prompts you for which encryption keys to set and optionally +where to save a sample configuration file. + +`-q, --quiet`:: Outputs the encryption keys without helper information. + +`-f, --force`:: Shows help information. + +[discrete] +=== Examples + +[source,shell] +-------------------------------------------------- +bin/kibana-encryption-keys generate +-------------------------------------------------- diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index f84e9de87c734a..6a5c4a83aa3ad5 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -45,5 +45,6 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] +include::encryption-keys/index.asciidoc[] include::role-mappings/index.asciidoc[] include::rbac_tutorial.asciidoc[] From 8ce9b474d671dab83591254f973e3cb506aca511 Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 18 Dec 2020 15:07:36 -0500 Subject: [PATCH 13/40] [Time to Visualize] Fix Dashboard OnAppLeave (#86193) Added isTransferInProgress to embeddable_state_transfer for apps to determine whether or not to show onAppLeave confirm --- ...c.embeddablestatetransfer._constructor_.md | 3 +- ...dablestatetransfer.istransferinprogress.md | 11 +++++++ ...beddable-public.embeddablestatetransfer.md | 3 +- .../public/application/dashboard_app.tsx | 17 +++++++--- .../embeddable_state_transfer.test.ts | 31 +++++++++++++++++-- .../embeddable_state_transfer.ts | 8 +++++ src/plugins/embeddable/public/plugin.tsx | 8 ++++- src/plugins/embeddable/public/public.api.md | 4 ++- test/common/services/security/test_user.ts | 6 ++-- .../dashboard/create_and_add_embeddables.ts | 2 +- .../apps/dashboard/dashboard_time_picker.ts | 6 ++++ .../apps/dashboard/panel_context_menu.ts | 2 +- .../apps/dashboard/panel_replacing.ts | 2 ++ test/functional/page_objects/header_page.ts | 17 ++++++++-- .../services/dashboard/visualizations.ts | 3 +- 15 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md index 276499b435e1f8..77e9c2d00b2dd8 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md @@ -9,7 +9,7 @@ Constructs a new instance of the `EmbeddableStateTransfer` class Signature: ```typescript -constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap | undefined, customStorage?: Storage); +constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap | undefined, customStorage?: Storage); ``` ## Parameters @@ -17,6 +17,7 @@ constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: Readonly | Parameter | Type | Description | | --- | --- | --- | | navigateToApp | ApplicationStart['navigateToApp'] | | +| currentAppId$ | ApplicationStart['currentAppId$'] | | | appList | ReadonlyMap<string, PublicAppInfo> | undefined | | | customStorage | Storage | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md new file mode 100644 index 00000000000000..f00d015f316d2a --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStateTransfer](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md) > [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md) + +## EmbeddableStateTransfer.isTransferInProgress property + +Signature: + +```typescript +isTransferInProgress: boolean; +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md index 3676b744b8cc9c..76b6708b93bd12 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.md @@ -16,13 +16,14 @@ export declare class EmbeddableStateTransfer | Constructor | Modifiers | Description | | --- | --- | --- | -| [(constructor)(navigateToApp, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | +| [(constructor)(navigateToApp, currentAppId$, appList, customStorage)](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer._constructor_.md) | | Constructs a new instance of the EmbeddableStateTransfer class | ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [getAppNameFromId](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.getappnamefromid.md) | | (appId: string) => string | undefined | Fetches an internationalized app title when given an appId. | +| [isTransferInProgress](./kibana-plugin-plugins-embeddable-public.embeddablestatetransfer.istransferinprogress.md) | | boolean | | ## Methods diff --git a/src/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index 8eff48251b371e..845d64c16500df 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -45,6 +45,7 @@ import { removeQueryParam } from '../services/kibana_utils'; import { IndexPattern } from '../services/data'; import { EmbeddableRenderer } from '../services/embeddable'; import { DashboardContainerInput } from '.'; +import { leaveConfirmStrings } from '../dashboard_strings'; export interface DashboardAppProps { history: History; @@ -64,8 +65,9 @@ export function DashboardApp({ core, onAppLeave, uiSettings, - indexPatterns: indexPatternService, + embeddable, dashboardCapabilities, + indexPatterns: indexPatternService, } = useKibana().services; const [lastReloadTime, setLastReloadTime] = useState(0); @@ -196,9 +198,14 @@ export function DashboardApp({ return; } onAppLeave((actions) => { - if (dashboardStateManager?.getIsDirty()) { - // TODO: Finish App leave handler with overrides when redirecting to an editor. - // return actions.confirm(leaveConfirmStrings.leaveSubtitle, leaveConfirmStrings.leaveTitle); + if ( + dashboardStateManager?.getIsDirty() && + !embeddable.getStateTransfer().isTransferInProgress + ) { + return actions.confirm( + leaveConfirmStrings.getLeaveSubtitle(), + leaveConfirmStrings.getLeaveTitle() + ); } return actions.default(); }); @@ -206,7 +213,7 @@ export function DashboardApp({ // reset on app leave handler so leaving from the listing page doesn't trigger a confirmation onAppLeave((actions) => actions.default()); }; - }, [dashboardStateManager, dashboardContainer, onAppLeave]); + }, [dashboardStateManager, dashboardContainer, onAppLeave, embeddable]); // Refresh the dashboard container when lastReloadTime changes useEffect(() => { diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts index cbaeddf472d52c..be034d125dceef 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.test.ts @@ -23,6 +23,7 @@ import { EmbeddableStateTransfer } from '.'; import { ApplicationStart, PublicAppInfo } from '../../../../../core/public'; import { EMBEDDABLE_EDITOR_STATE_KEY, EMBEDDABLE_PACKAGE_STATE_KEY } from './types'; import { EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY } from './embeddable_state_transfer'; +import { Subject } from 'rxjs'; const createStorage = (): Storage => { const createMockStore = () => { @@ -46,16 +47,24 @@ const createStorage = (): Storage => { describe('embeddable state transfer', () => { let application: jest.Mocked; let stateTransfer: EmbeddableStateTransfer; + let currentAppId$: Subject; let store: Storage; const destinationApp = 'superUltraVisualize'; const originatingApp = 'superUltraTestDashboard'; beforeEach(() => { + currentAppId$ = new Subject(); + currentAppId$.next(originatingApp); const core = coreMock.createStart(); application = core.application; store = createStorage(); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, undefined, store); + stateTransfer = new EmbeddableStateTransfer( + application.navigateToApp, + currentAppId$, + undefined, + store + ); }); it('cannot fetch app name when given no app list', async () => { @@ -67,7 +76,7 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList); expect(stateTransfer.getAppNameFromId('kibanana')).toBeUndefined(); }); @@ -76,7 +85,7 @@ describe('embeddable state transfer', () => { ['testId', { title: 'State Transfer Test App Hello' } as PublicAppInfo], ['testId2', { title: 'State Transfer Test App Goodbye' } as PublicAppInfo], ]); - stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, appsList); + stateTransfer = new EmbeddableStateTransfer(application.navigateToApp, currentAppId$, appsList); expect(stateTransfer.getAppNameFromId('testId')).toBe('State Transfer Test App Hello'); expect(stateTransfer.getAppNameFromId('testId2')).toBe('State Transfer Test App Goodbye'); }); @@ -107,6 +116,13 @@ describe('embeddable state transfer', () => { }); }); + it('sets isTransferInProgress to true when sending an outgoing editor state', async () => { + await stateTransfer.navigateToEditor(destinationApp, { state: { originatingApp } }); + expect(stateTransfer.isTransferInProgress).toEqual(true); + currentAppId$.next(destinationApp); + expect(stateTransfer.isTransferInProgress).toEqual(false); + }); + it('can send an outgoing embeddable package state', async () => { await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { state: { type: 'coolestType', input: { savedObjectId: '150' } }, @@ -135,6 +151,15 @@ describe('embeddable state transfer', () => { }); }); + it('sets isTransferInProgress to true when sending an outgoing embeddable package state', async () => { + await stateTransfer.navigateToWithEmbeddablePackage(destinationApp, { + state: { type: 'coolestType', input: { savedObjectId: '150' } }, + }); + expect(stateTransfer.isTransferInProgress).toEqual(true); + currentAppId$.next(destinationApp); + expect(stateTransfer.isTransferInProgress).toEqual(false); + }); + it('can fetch an incoming editor state', async () => { store.set(EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY, { [EMBEDDABLE_EDITOR_STATE_KEY]: { originatingApp: 'superUltraTestDashboard' }, diff --git a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts index 0b34bea8105205..92900059668db6 100644 --- a/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts +++ b/src/plugins/embeddable/public/lib/state_transfer/embeddable_state_transfer.ts @@ -38,14 +38,20 @@ export const EMBEDDABLE_STATE_TRANSFER_STORAGE_KEY = 'EMBEDDABLE_STATE_TRANSFER' * @public */ export class EmbeddableStateTransfer { + public isTransferInProgress: boolean; private storage: Storage; constructor( private navigateToApp: ApplicationStart['navigateToApp'], + currentAppId$: ApplicationStart['currentAppId$'], private appList?: ReadonlyMap | undefined, customStorage?: Storage ) { this.storage = customStorage ? customStorage : new Storage(sessionStorage); + this.isTransferInProgress = false; + currentAppId$.subscribe(() => { + this.isTransferInProgress = false; + }); } /** @@ -105,6 +111,7 @@ export class EmbeddableStateTransfer { state: EmbeddableEditorState; } ): Promise { + this.isTransferInProgress = true; await this.navigateToWithState(appId, EMBEDDABLE_EDITOR_STATE_KEY, { ...options, appendToExistingState: true, @@ -119,6 +126,7 @@ export class EmbeddableStateTransfer { appId: string, options?: { path?: string; state: EmbeddablePackageState } ): Promise { + this.isTransferInProgress = true; await this.navigateToWithState(appId, EMBEDDABLE_PACKAGE_STATE_KEY, { ...options, appendToExistingState: true, diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 5118a1a8818c0e..6f43d87bdcd53c 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -161,6 +161,7 @@ export class EmbeddablePublicPlugin implements Plugin storage - ? new EmbeddableStateTransfer(core.application.navigateToApp, this.appList, storage) + ? new EmbeddableStateTransfer( + core.application.navigateToApp, + core.application.currentAppId$, + this.appList, + storage + ) : this.stateTransferService, EmbeddablePanel: getEmbeddablePanelHoc(), telemetry: getTelemetryFunction(commonContract), diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 03818fccda0bc4..7563b66e58ae9a 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -628,12 +628,14 @@ export interface EmbeddableStartDependencies { export class EmbeddableStateTransfer { // Warning: (ae-forgotten-export) The symbol "ApplicationStart" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "PublicAppInfo" needs to be exported by the entry point index.d.ts - constructor(navigateToApp: ApplicationStart['navigateToApp'], appList?: ReadonlyMap | undefined, customStorage?: Storage); + constructor(navigateToApp: ApplicationStart['navigateToApp'], currentAppId$: ApplicationStart['currentAppId$'], appList?: ReadonlyMap | undefined, customStorage?: Storage); // (undocumented) clearEditorState(): void; getAppNameFromId: (appId: string) => string | undefined; getIncomingEditorState(removeAfterFetch?: boolean): EmbeddableEditorState | undefined; getIncomingEmbeddablePackage(removeAfterFetch?: boolean): EmbeddablePackageState | undefined; + // (undocumented) + isTransferInProgress: boolean; // Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "ApplicationStart" navigateToEditor(appId: string, options?: { path?: string; diff --git a/test/common/services/security/test_user.ts b/test/common/services/security/test_user.ts index 7183943591c884..25a36fed9c9c5e 100644 --- a/test/common/services/security/test_user.ts +++ b/test/common/services/security/test_user.ts @@ -87,11 +87,11 @@ export async function createTestUserService( }); if (browser && testSubjects && shouldRefreshBrowser) { - // accept alert if it pops up - const alert = await browser.getAlert(); - await alert?.accept(); if (await testSubjects.exists('kibanaChrome', { allowHidden: true })) { await browser.refresh(); + // accept alert if it pops up + const alert = await browser.getAlert(); + await alert?.accept(); await testSubjects.find('kibanaChrome', config.get('timeouts.find') * 10); } } diff --git a/test/functional/apps/dashboard/create_and_add_embeddables.ts b/test/functional/apps/dashboard/create_and_add_embeddables.ts index d8b8a6f91fe317..605ea26b76c6f7 100644 --- a/test/functional/apps/dashboard/create_and_add_embeddables.ts +++ b/test/functional/apps/dashboard/create_and_add_embeddables.ts @@ -81,7 +81,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('saves the saved visualization url to the app link', async () => { - await PageObjects.header.clickVisualize(); + await PageObjects.header.clickVisualize(true); const currentUrl = await browser.getCurrentUrl(); expect(currentUrl).to.contain(VisualizeConstants.EDIT_PATH); }); diff --git a/test/functional/apps/dashboard/dashboard_time_picker.ts b/test/functional/apps/dashboard/dashboard_time_picker.ts index 274a4355e26e23..c759edd638260a 100644 --- a/test/functional/apps/dashboard/dashboard_time_picker.ts +++ b/test/functional/apps/dashboard/dashboard_time_picker.ts @@ -40,6 +40,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await kibanaServer.uiSettings.replace({}); await browser.refresh(); + const alert = await browser.getAlert(); + await alert?.accept(); }); it('Visualization updated when time picker changes', async () => { @@ -88,6 +90,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `)`; log.debug('go to url' + `${kibanaBaseUrl}#${urlQuery}`); await browser.get(`${kibanaBaseUrl}#${urlQuery}`, true); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.header.waitUntilLoadingHasFinished(); const time = await PageObjects.timePicker.getTimeConfig(); const refresh = await PageObjects.timePicker.getRefreshConfig(); @@ -99,6 +103,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('Timepicker respects dateFormat from UI settings', async () => { await kibanaServer.uiSettings.replace({ dateFormat: 'YYYY-MM-DD HH:mm:ss.SSS' }); await browser.refresh(); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.clickNewDashboard(); await PageObjects.dashboard.addVisualizations([PIE_CHART_VIS_NAME]); diff --git a/test/functional/apps/dashboard/panel_context_menu.ts b/test/functional/apps/dashboard/panel_context_menu.ts index 0b9e873f46151b..bd6756835af319 100644 --- a/test/functional/apps/dashboard/panel_context_menu.ts +++ b/test/functional/apps/dashboard/panel_context_menu.ts @@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchName = 'my search'; before(async () => { - await PageObjects.header.clickDiscover(); + await PageObjects.header.clickDiscover(true); await PageObjects.discover.clickNewSearchButton(); await dashboardVisualizations.createSavedSearch({ name: searchName, fields: ['bytes'] }); await PageObjects.header.waitUntilLoadingHasFinished(); diff --git a/test/functional/apps/dashboard/panel_replacing.ts b/test/functional/apps/dashboard/panel_replacing.ts index 6bf3dbbe47b1d1..c9ecf42dbc8e8a 100644 --- a/test/functional/apps/dashboard/panel_replacing.ts +++ b/test/functional/apps/dashboard/panel_replacing.ts @@ -70,6 +70,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('replaced panel persisted correctly when dashboard is hard refreshed', async () => { const currentUrl = await browser.getCurrentUrl(); await browser.get(currentUrl, true); + const alert = await browser.getAlert(); + await alert?.accept(); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); const panelTitles = await PageObjects.dashboard.getPanelTitles(); diff --git a/test/functional/page_objects/header_page.ts b/test/functional/page_objects/header_page.ts index d69a01ab6bb264..5a892bb4f6ca3b 100644 --- a/test/functional/page_objects/header_page.ts +++ b/test/functional/page_objects/header_page.ts @@ -31,14 +31,16 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo const defaultFindTimeout = config.get('timeouts.find'); class HeaderPage { - public async clickDiscover() { + public async clickDiscover(ignoreAppLeaveWarning = false) { await appsMenu.clickLink('Discover', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); await PageObjects.common.waitForTopNavToBeVisible(); await this.awaitGlobalLoadingIndicatorHidden(); } - public async clickVisualize() { + public async clickVisualize(ignoreAppLeaveWarning = false) { await appsMenu.clickLink('Visualize', { category: 'kibana' }); + await this.onAppLeaveWarning(ignoreAppLeaveWarning); await this.awaitGlobalLoadingIndicatorHidden(); await retry.waitFor('first breadcrumb to be "Visualize"', async () => { const firstBreadcrumb = await globalNav.getFirstBreadcrumb(); @@ -95,6 +97,17 @@ export function HeaderPageProvider({ getService, getPageObjects }: FtrProviderCo log.debug('awaitKibanaChrome'); await testSubjects.find('kibanaChrome', defaultFindTimeout * 10); } + + public async onAppLeaveWarning(ignoreWarning = false) { + await retry.try(async () => { + const warning = await testSubjects.exists('confirmModalTitleText'); + if (warning) { + await testSubjects.click( + ignoreWarning ? 'confirmModalConfirmButton' : 'confirmModalCancelButton' + ); + } + }); + } } return new HeaderPage(); diff --git a/test/functional/services/dashboard/visualizations.ts b/test/functional/services/dashboard/visualizations.ts index b35ef1e8f2f9a9..22e1769145f884 100644 --- a/test/functional/services/dashboard/visualizations.ts +++ b/test/functional/services/dashboard/visualizations.ts @@ -58,8 +58,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }: F fields?: string[]; }) { log.debug(`createSavedSearch(${name})`); - await PageObjects.header.clickDiscover(); - + await PageObjects.header.clickDiscover(true); await PageObjects.timePicker.setHistoricalDataRange(); if (query) { From b5197a36d47007d78f5bdb223580d341236cc4d4 Mon Sep 17 00:00:00 2001 From: ymao1 Date: Fri, 18 Dec 2020 15:26:24 -0500 Subject: [PATCH 14/40] [Alerting UI] Centered loading spinners (#86186) * Creating shared loading spinner component * Using section loading component where it makes sense * Fixing tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/center_justified_spinner.tsx | 22 ++++++++++++++ .../application/components/health_check.tsx | 13 ++++++-- .../lib/suspended_component_with_props.tsx | 12 ++------ .../action_connector_form.tsx | 15 +++++----- .../action_type_form.tsx | 11 +------ .../action_type_menu.tsx | 21 ++++++++++--- .../components/actions_connectors_list.tsx | 10 ++----- .../components/alert_details_route.test.tsx | 4 +-- .../components/alert_details_route.tsx | 11 ++----- .../components/alert_instances_route.test.tsx | 4 +-- .../components/alert_instances_route.tsx | 11 ++----- .../sections/alert_form/alert_form.tsx | 30 +++++++++++-------- .../alerts_list/components/alerts_list.tsx | 8 ++--- 13 files changed, 90 insertions(+), 82 deletions(-) create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx new file mode 100644 index 00000000000000..2dec2dcb001d70 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/center_justified_spinner.tsx @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; + +interface Props { + size?: EuiLoadingSpinnerSize; +} + +export const CenterJustifiedSpinner: React.FunctionComponent = ({ size }) => ( + + + + + +); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx index 0f20ade8187fdb..66f7c1d36dfb21 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/health_check.tsx @@ -9,7 +9,7 @@ import { Option, none, some, fold } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiLink, EuiLoadingSpinner } from '@elastic/eui'; +import { EuiLink, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; @@ -19,6 +19,7 @@ import { health } from '../lib/alert_api'; import './health_check.scss'; import { useHealthContext } from '../context/health_context'; import { useKibana } from '../../common/lib/kibana'; +import { CenterJustifiedSpinner } from './center_justified_spinner'; interface Props { inFlyout?: boolean; @@ -47,7 +48,15 @@ export const HealthCheck: React.FunctionComponent = ({ return pipe( alertingHealth, fold( - () => (waitForCheck ? : {children}), + () => + waitForCheck ? ( + + + + + ) : ( + {children} + ), (healthCheck) => { return healthCheck?.isSufficientlySecure && healthCheck?.hasPermanentEncryptionKey ? ( {children} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx index 563353793f991f..98c20c5abcc2de 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/suspended_component_with_props.tsx @@ -4,23 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Suspense } from 'react'; -import { EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { EuiLoadingSpinnerSize } from '@elastic/eui/src/components/loading/loading_spinner'; +import { CenterJustifiedSpinner } from '../components/center_justified_spinner'; export function suspendedComponentWithProps( ComponentToSuspend: React.ComponentType, size?: EuiLoadingSpinnerSize ) { return (props: T) => ( - - - - - - } - > + }> ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx index 7d8949421126c9..a83194d67a759b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_connector_form.tsx @@ -12,9 +12,6 @@ import { EuiSpacer, EuiFieldText, EuiFormRow, - EuiLoadingSpinner, - EuiFlexGroup, - EuiFlexItem, EuiErrorBoundary, EuiTitle, } from '@elastic/eui'; @@ -29,6 +26,7 @@ import { } from '../../../types'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { useKibana } from '../../../common/lib/kibana'; +import { SectionLoading } from '../../components/section_loading'; export function validateBaseProperties(actionObject: ActionConnector) { const validationResult = { errors: {} }; @@ -181,11 +179,12 @@ export const ActionConnectorForm = ({ - - - - + + + } > {ParamsFieldsComponent ? ( - - - - - - } - > + void; @@ -31,6 +33,7 @@ export const ActionTypeMenu = ({ http, notifications: { toasts }, } = useKibana().services; + const [loadingActionTypes, setLoadingActionTypes] = useState(false); const [actionTypesIndex, setActionTypesIndex] = useState(undefined); useEffect(() => { @@ -43,11 +46,14 @@ export const ActionTypeMenu = ({ * * TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502. * */ - const availableActionTypes = - actionTypes ?? - (await loadActionTypes({ http })).filter( + let availableActionTypes = actionTypes; + if (!availableActionTypes) { + setLoadingActionTypes(true); + availableActionTypes = (await loadActionTypes({ http })).filter( (actionType) => !DEFAULT_HIDDEN_ACTION_TYPES.includes(actionType.id) ); + setLoadingActionTypes(false); + } const index: ActionTypeIndex = {}; for (const actionTypeItem of availableActionTypes) { index[actionTypeItem.id] = actionTypeItem; @@ -117,7 +123,14 @@ export const ActionTypeMenu = ({ ); }); - return ( + return loadingActionTypes ? ( + + + + ) : (

diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index 2df75436f5f96b..bf6786d0d4e4ca 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -10,7 +10,6 @@ import { EuiSpacer, EuiButton, EuiLink, - EuiLoadingSpinner, EuiIconTip, EuiFlexGroup, EuiFlexItem, @@ -40,6 +39,7 @@ import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../. import { EmptyConnectorsPrompt } from '../../../components/prompts/empty_connectors_prompt'; import { useKibana } from '../../../../common/lib/kibana'; import { DEFAULT_HIDDEN_ACTION_TYPES } from '../../../../'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; export const ActionsConnectorsList: React.FunctionComponent = () => { const { @@ -355,13 +355,7 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { /> {/* Render the view based on if there's data or if they can save */} - {(isLoadingActions || isLoadingActionTypes) && ( - - - - - - )} + {(isLoadingActions || isLoadingActionTypes) && } {actionConnectorTableItems.length !== 0 && table} {actionConnectorTableItems.length === 0 && canSave && diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx index 48360647e24ee5..7a12c43427a913 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.test.tsx @@ -10,7 +10,7 @@ import { createMemoryHistory, createLocation } from 'history'; import { ToastsApi } from 'kibana/public'; import { AlertDetailsRoute, getAlertData } from './alert_details_route'; import { Alert } from '../../../../types'; -import { EuiLoadingSpinner } from '@elastic/eui'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; jest.mock('../../../../common/lib/kibana'); describe('alert_details_route', () => { @@ -20,7 +20,7 @@ describe('alert_details_route', () => { expect( shallow( - ).containsMatchingElement() + ).containsMatchingElement() ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx index fc3e05fbfaed0e..ae729dd4f0095e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details_route.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import React, { useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { EuiLoadingSpinner } from '@elastic/eui'; import { ToastsApi } from 'kibana/public'; import { Alert, AlertType, ActionType } from '../../../../types'; import { AlertDetailsWithApi as AlertDetails } from './alert_details'; @@ -21,6 +20,7 @@ import { withActionOperations, } from '../../common/components/with_actions_api_operations'; import { useKibana } from '../../../../common/lib/kibana'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; type AlertDetailsRouteProps = RouteComponentProps<{ alertId: string; @@ -66,14 +66,7 @@ export const AlertDetailsRoute: React.FunctionComponent requestRefresh={async () => requestRefresh(Date.now())} /> ) : ( -
- -
+ ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx index e3fe9cd86356ae..dfaed32ff72aea 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.test.tsx @@ -9,7 +9,7 @@ import { shallow } from 'enzyme'; import { ToastsApi } from 'kibana/public'; import { AlertInstancesRoute, getAlertInstanceSummary } from './alert_instances_route'; import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; -import { EuiLoadingSpinner } from '@elastic/eui'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; jest.mock('../../../../common/lib/kibana'); const fakeNow = new Date('2020-02-09T23:15:41.941Z'); @@ -23,7 +23,7 @@ describe('alert_instance_summary_route', () => { expect( shallow( - ).containsMatchingElement() + ).containsMatchingElement() ).toBeTruthy(); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx index e1e0866d886a30..a122d59959156f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_instances_route.tsx @@ -7,7 +7,6 @@ import { i18n } from '@kbn/i18n'; import { ToastsApi } from 'kibana/public'; import React, { useState, useEffect } from 'react'; -import { EuiLoadingSpinner } from '@elastic/eui'; import { Alert, AlertInstanceSummary, AlertType } from '../../../../types'; import { ComponentOpts as AlertApis, @@ -15,6 +14,7 @@ import { } from '../../common/components/with_bulk_alert_api_operations'; import { AlertInstancesWithApi as AlertInstances } from './alert_instances'; import { useKibana } from '../../../../common/lib/kibana'; +import { CenterJustifiedSpinner } from '../../../components/center_justified_spinner'; type WithAlertInstanceSummaryProps = { alert: Alert; @@ -52,14 +52,7 @@ export const AlertInstancesRoute: React.FunctionComponent ) : ( -
- -
+ ); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index 3210d538419937..cf3d0bf1544c25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useState, useEffect, Suspense, useCallback } from 'react'; +import React, { Fragment, useState, useEffect, useCallback, Suspense } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -23,7 +23,6 @@ import { EuiIconTip, EuiButtonIcon, EuiHorizontalRule, - EuiLoadingSpinner, EuiEmptyPrompt, EuiListGroupItem, EuiListGroup, @@ -71,6 +70,7 @@ import { AlertNotifyWhen } from './alert_notify_when'; import { checkAlertTypeEnabled } from '../../lib/check_alert_type_enabled'; import { alertTypeCompare, alertTypeGroupCompare } from '../../lib/alert_type_compare'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; +import { SectionLoading } from '../../components/section_loading'; const ENTER_KEY = 13; @@ -535,7 +535,16 @@ export const AlertForm = ({ alert.alertTypeId && selectedAlertType ? ( - }> + + + + } + > ) : ( - + + + )} ); }; -const CenterJustifiedSpinner = () => ( - - - - - -); - const NoAuthorizedAlertTypes = ({ operation }: { operation: string }) => ( { {loadedItems.length || isFilterApplied ? ( table ) : alertTypesState.isLoading || alertsState.isLoading ? ( - - - - - + ) : authorizedToCreateAnyAlerts ? ( setAlertFlyoutVisibility(true)} /> ) : ( From 319a4070d7b913af3894358a65897171884c09df Mon Sep 17 00:00:00 2001 From: Devon Thomson Date: Fri, 18 Dec 2020 15:59:24 -0500 Subject: [PATCH 15/40] Fixed duplication of create new modal (#86489) --- .../public/application/top_nav/dashboard_top_nav.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 937e6737d27168..915f245fbcd191 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -27,6 +27,7 @@ import { useKibana } from '../../services/kibana_react'; import { IndexPattern, SavedQuery, TimefilterContract } from '../../services/data'; import { EmbeddableFactoryNotFoundError, + EmbeddableInput, isErrorEmbeddable, openAddPanelFlyout, ViewMode, @@ -135,10 +136,7 @@ export function DashboardTopNav({ if (!factory) { throw new EmbeddableFactoryNotFoundError(type); } - const explicitInput = await factory.getExplicitInput(); - if (dashboardContainer) { - await dashboardContainer.addNewEmbeddable(type, explicitInput); - } + await factory.create({} as EmbeddableInput, dashboardContainer); }, [dashboardContainer, embeddable]); const onChangeViewMode = useCallback( From 8e717205021677bb3fb4a40a0efc40385a8b8692 Mon Sep 17 00:00:00 2001 From: Constance Date: Fri, 18 Dec 2020 13:25:35 -0800 Subject: [PATCH 16/40] [App Search] Sample Engines should have access to the Crawler (#86502) * Remove logic preventing Crawler from being used on sample engines * Remove check around crawler button in DocumentCreationButtons - primarily a UI thing - ideally we always want to show 4 buttons --- .../document_creation_buttons.test.tsx | 24 +------- .../document_creation_buttons.tsx | 57 ++++++++----------- .../components/engine/engine_nav.test.tsx | 6 -- .../components/engine/engine_nav.tsx | 2 +- 4 files changed, 29 insertions(+), 60 deletions(-) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx index 8bd473c003eb12..c76cc4b45fc19b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx @@ -16,8 +16,6 @@ import { DocumentCreationButtons } from './'; describe('DocumentCreationButtons', () => { const values = { engineName: 'test-engine', - isSampleEngine: false, - myRole: { canViewEngineCrawler: true }, }; const actions = { openDocumentCreation: jest.fn(), @@ -56,25 +54,9 @@ describe('DocumentCreationButtons', () => { expect(actions.openDocumentCreation).toHaveBeenCalledWith('api'); }); - describe('crawler card', () => { - it('renders the crawler button with a link to the crawler page', () => { - const wrapper = shallow(); - - expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/test-engine/crawler'); - }); - - it('does not render the crawler button if the user does not have access', () => { - setMockValues({ ...values, myRole: { canViewEngineCrawler: false } }); - const wrapper = shallow(); - - expect(wrapper.find(EuiCardTo)).toHaveLength(0); - }); - - it('does not render the crawler button for the sample engine', () => { - setMockValues({ ...values, isSampleEngine: true }); - const wrapper = shallow(); + it('renders the crawler button with a link to the crawler page', () => { + const wrapper = shallow(); - expect(wrapper.find(EuiCardTo)).toHaveLength(0); - }); + expect(wrapper.find(EuiCardTo).prop('to')).toEqual('/engines/test-engine/crawler'); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx index edeae5205b6467..ce7cae56783382 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.tsx @@ -22,7 +22,6 @@ import { import { EuiCardTo } from '../../../shared/react_router_helpers'; import { DOCS_PREFIX, getEngineRoute, ENGINE_CRAWLER_PATH } from '../../routes'; -import { AppLogic } from '../../app_logic'; import { EngineLogic } from '../engine'; import { DocumentCreationLogic } from './'; @@ -34,11 +33,7 @@ interface Props { export const DocumentCreationButtons: React.FC = ({ disabled = false }) => { const { openDocumentCreation } = useActions(DocumentCreationLogic); - const { engineName, isSampleEngine } = useValues(EngineLogic); - const { - myRole: { canViewEngineCrawler }, - } = useValues(AppLogic); - const showCrawlerLink = canViewEngineCrawler && !isSampleEngine; + const { engineName } = useValues(EngineLogic); const crawlerLink = getEngineRoute(engineName) + ENGINE_CRAWLER_PATH; return ( @@ -61,7 +56,7 @@ export const DocumentCreationButtons: React.FC = ({ disabled = false }) =

- + = ({ disabled = false }) = isDisabled={disabled} /> - {showCrawlerLink && ( - - } - betaBadgeLabel={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTitle', - { defaultMessage: 'Beta' } - )} - betaBadgeTooltipContent={i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTooltip', - { - defaultMessage: - 'The Elastic Crawler is not GA. Please help us by reporting any bugs.', - } - )} - to={crawlerLink} - isDisabled={disabled} - /> - - )} + + } + betaBadgeLabel={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTitle', + { defaultMessage: 'Beta' } + )} + betaBadgeTooltipContent={i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.buttons.betaTooltip', + { + defaultMessage: + 'The Elastic Crawler is not GA. Please help us by reporting any bugs.', + } + )} + to={crawlerLink} + isDisabled={disabled} + /> + ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx index 0d2ce654d4a0ab..2e419168f2e1bb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.test.tsx @@ -102,12 +102,6 @@ describe('EngineNav', () => { const wrapper = shallow(); expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0); }); - - it('does not render for sample engine', () => { - setMockValues({ ...values, myRole, isSampleEngine: true }); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="EngineCrawlerLink"]')).toHaveLength(0); - }); }); describe('meta engine source engines link', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 35389bbe4b3ba7..0fed7cd0fc8fc6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -153,7 +153,7 @@ export const EngineNav: React.FC = () => { )} - {canViewEngineCrawler && !isMetaEngine && !isSampleEngine && ( + {canViewEngineCrawler && !isMetaEngine && ( Date: Fri, 18 Dec 2020 13:31:21 -0800 Subject: [PATCH 17/40] [App Search] Convert DocumentCreationModal to DocumentCreationFlyout (#86508) * Convert DocumentCreationModal to a Flyout - Per discussion w/ Davey - it handles longer/detailed content better * Update instances referencing DocumentCreationFlyout * Update flyout children - modal->flyout - add hasBorder, set EuiTitle sizes, add flexgroup to footer buttons --- .../document_creation/constants.tsx | 10 ++-- .../api_code_example.test.tsx | 22 ++++---- .../api_code_example.tsx | 46 ++++++++------- .../paste_json_text.test.tsx | 22 ++++---- .../paste_json_text.tsx | 56 +++++++++++-------- .../show_creation_modes.test.tsx | 2 +- .../show_creation_modes.tsx | 30 +++++----- .../upload_json_file.test.tsx | 24 ++++---- .../upload_json_file.tsx | 56 +++++++++++-------- .../document_creation_buttons.test.tsx | 2 +- ....tsx => document_creation_flyout.test.tsx} | 32 +++++------ ...modal.tsx => document_creation_flyout.tsx} | 17 +++--- .../document_creation_logic.ts | 2 +- .../components/document_creation/index.ts | 2 +- .../document_creation_button.test.tsx | 4 +- .../documents/document_creation_button.tsx | 4 +- .../engine_overview_empty.test.tsx | 4 +- .../engine_overview/engine_overview_empty.tsx | 4 +- 18 files changed, 181 insertions(+), 158 deletions(-) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/{document_creation_modal.test.tsx => document_creation_flyout.test.tsx} (77%) rename x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/{document_creation_modal.tsx => document_creation_flyout.tsx} (78%) diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx index c4237da0d0e804..27c3410767d8ad 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx @@ -6,12 +6,14 @@ import { i18n } from '@kbn/i18n'; -export const MODAL_CANCEL_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.modalCancel', +export const FLYOUT_ARIA_LABEL_ID = 'documentCreationFlyoutHeadingId'; + +export const FLYOUT_CANCEL_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.flyoutCancel', { defaultMessage: 'Cancel' } ); -export const MODAL_CONTINUE_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.appSearch.documentCreation.modalContinue', +export const FLYOUT_CONTINUE_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.appSearch.documentCreation.flyoutContinue', { defaultMessage: 'Continue' } ); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx index 2dd46419528c1b..ddce27789b82cb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { EuiCode, EuiCodeBlock, EuiButtonEmpty } from '@elastic/eui'; -import { ApiCodeExample, ModalHeader, ModalBody, ModalFooter } from './api_code_example'; +import { ApiCodeExample, FlyoutHeader, FlyoutBody, FlyoutFooter } from './api_code_example'; describe('ApiCodeExample', () => { const values = { @@ -29,23 +29,23 @@ describe('ApiCodeExample', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(ModalHeader)).toHaveLength(1); - expect(wrapper.find(ModalBody)).toHaveLength(1); - expect(wrapper.find(ModalFooter)).toHaveLength(1); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); }); - describe('ModalHeader', () => { + describe('FlyoutHeader', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('h2').text()).toEqual('Indexing by API'); }); }); - describe('ModalBody', () => { + describe('FlyoutBody', () => { let wrapper: ShallowWrapper; beforeAll(() => { - wrapper = shallow(); + wrapper = shallow(); }); it('renders with the full remote Enterprise Search API URL', () => { @@ -64,9 +64,9 @@ describe('ApiCodeExample', () => { }); }); - describe('ModalFooter', () => { - it('closes the modal', () => { - const wrapper = shallow(); + describe('FlyoutFooter', () => { + it('closes the flyout', () => { + const wrapper = shallow(); wrapper.find(EuiButtonEmpty).simulate('click'); expect(actions.closeDocumentCreation).toHaveBeenCalled(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx index 1dd57ffe8bc014..9ebe404659ca2c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx @@ -11,10 +11,10 @@ import { useValues, useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, EuiButtonEmpty, EuiText, EuiLink, @@ -30,39 +30,43 @@ import { EngineLogic } from '../../engine'; import { EngineDetails } from '../../engine/types'; import { DOCS_PREFIX } from '../../../routes'; -import { DOCUMENTS_API_JSON_EXAMPLE, MODAL_CANCEL_BUTTON } from '../constants'; +import { + DOCUMENTS_API_JSON_EXAMPLE, + FLYOUT_ARIA_LABEL_ID, + FLYOUT_CANCEL_BUTTON, +} from '../constants'; import { DocumentCreationLogic } from '../'; export const ApiCodeExample: React.FC = () => ( <> - - - + + + ); -export const ModalHeader: React.FC = () => { +export const FlyoutHeader: React.FC = () => { return ( - - -

+ + +

{i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.api.title', { defaultMessage: 'Indexing by API', })}

- - +
+
); }; -export const ModalBody: React.FC = () => { +export const FlyoutBody: React.FC = () => { const { engineName, engine } = useValues(EngineLogic); const { apiKey } = engine as EngineDetails; const documentsApiUrl = getEnterpriseSearchUrl(`/api/as/v1/engines/${engineName}/documents`); return ( - +

{ # ] `)} - + ); }; -export const ModalFooter: React.FC = () => { +export const FlyoutFooter: React.FC = () => { const { closeDocumentCreation } = useActions(DocumentCreationLogic); return ( - - {MODAL_CANCEL_BUTTON} - + + {FLYOUT_CANCEL_BUTTON} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx index ede1529c049d70..50e4d473e5f78a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiTextArea, EuiButtonEmpty, EuiButton } from '@elastic/eui'; -import { PasteJsonText, ModalHeader, ModalBody, ModalFooter } from './paste_json_text'; +import { PasteJsonText, FlyoutHeader, FlyoutBody, FlyoutFooter } from './paste_json_text'; describe('PasteJsonText', () => { const values = { @@ -35,22 +35,22 @@ describe('PasteJsonText', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(ModalHeader)).toHaveLength(1); - expect(wrapper.find(ModalBody)).toHaveLength(1); - expect(wrapper.find(ModalFooter)).toHaveLength(1); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); }); - describe('ModalHeader', () => { + describe('FlyoutHeader', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('h2').text()).toEqual('Create documents'); }); }); - describe('ModalBody', () => { + describe('FlyoutBody', () => { it('renders and updates the textarea value', () => { setMockValues({ ...values, textInput: 'lorem ipsum' }); - const wrapper = shallow(); + const wrapper = shallow(); const textarea = wrapper.find(EuiTextArea); expect(textarea.prop('value')).toEqual('lorem ipsum'); @@ -60,16 +60,16 @@ describe('PasteJsonText', () => { }); }); - describe('ModalFooter', () => { + describe('FlyoutFooter', () => { it('closes the modal', () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper.find(EuiButtonEmpty).simulate('click'); expect(actions.closeDocumentCreation).toHaveBeenCalled(); }); it('disables/enables the Continue button based on whether text has been entered', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(false); setMockValues({ ...values, textInput: '' }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx index 614704701222b0..ad83e0eb1a286a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/paste_json_text.tsx @@ -9,10 +9,12 @@ import { useValues, useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, EuiButton, EuiButtonEmpty, EuiTextArea, @@ -22,34 +24,34 @@ import { import { AppLogic } from '../../../app_logic'; -import { MODAL_CANCEL_BUTTON, MODAL_CONTINUE_BUTTON } from '../constants'; +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON, FLYOUT_CONTINUE_BUTTON } from '../constants'; import { DocumentCreationLogic } from '../'; import './paste_json_text.scss'; export const PasteJsonText: React.FC = () => ( <> - - - + + + ); -export const ModalHeader: React.FC = () => { +export const FlyoutHeader: React.FC = () => { return ( - - -

+ + +

{i18n.translate('xpack.enterpriseSearch.appSearch.documentCreation.pasteJsonText.title', { defaultMessage: 'Create documents', })}

- - +
+
); }; -export const ModalBody: React.FC = () => { +export const FlyoutBody: React.FC = () => { const { configuredLimits } = useValues(AppLogic); const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; @@ -57,7 +59,7 @@ export const ModalBody: React.FC = () => { const { setTextInput } = useActions(DocumentCreationLogic); return ( - +

{i18n.translate( @@ -82,20 +84,26 @@ export const ModalBody: React.FC = () => { fullWidth rows={12} /> - + ); }; -export const ModalFooter: React.FC = () => { +export const FlyoutFooter: React.FC = () => { const { textInput } = useValues(DocumentCreationLogic); const { closeDocumentCreation } = useActions(DocumentCreationLogic); return ( - - {MODAL_CANCEL_BUTTON} - - {MODAL_CONTINUE_BUTTON} - - + + + + {FLYOUT_CANCEL_BUTTON} + + + + {FLYOUT_CONTINUE_BUTTON} + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx index eadcf6df473e5e..d02545625345d9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.test.tsx @@ -30,7 +30,7 @@ describe('ShowCreationModes', () => { expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); }); - it('closes the modal', () => { + it('closes the flyout', () => { wrapper.find(EuiButtonEmpty).simulate('click'); expect(actions.closeDocumentCreation).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx index 1f7c4db83ab06c..f923661a57bcc1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/show_creation_modes.tsx @@ -9,14 +9,14 @@ import { useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, EuiButtonEmpty, } from '@elastic/eui'; -import { MODAL_CANCEL_BUTTON } from '../constants'; +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON } from '../constants'; import { DocumentCreationLogic, DocumentCreationButtons } from '../'; export const ShowCreationModes: React.FC = () => { @@ -24,22 +24,22 @@ export const ShowCreationModes: React.FC = () => { return ( <> - - -

+ + +

{i18n.translate( 'xpack.enterpriseSearch.appSearch.documentCreation.showCreationModes.title', { defaultMessage: 'Add new documents' } )}

- - - +
+
+ - - - {MODAL_CANCEL_BUTTON} - + + + {FLYOUT_CANCEL_BUTTON} + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx index dae085617cad8d..72a245df817ba9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { EuiFilePicker, EuiButtonEmpty, EuiButton } from '@elastic/eui'; -import { UploadJsonFile, ModalHeader, ModalBody, ModalFooter } from './upload_json_file'; +import { UploadJsonFile, FlyoutHeader, FlyoutBody, FlyoutFooter } from './upload_json_file'; describe('UploadJsonFile', () => { const mockFile = new File(['mock'], 'mock.json', { type: 'application/json' }); @@ -41,21 +41,21 @@ describe('UploadJsonFile', () => { it('renders', () => { const wrapper = shallow(); - expect(wrapper.find(ModalHeader)).toHaveLength(1); - expect(wrapper.find(ModalBody)).toHaveLength(1); - expect(wrapper.find(ModalFooter)).toHaveLength(1); + expect(wrapper.find(FlyoutHeader)).toHaveLength(1); + expect(wrapper.find(FlyoutBody)).toHaveLength(1); + expect(wrapper.find(FlyoutFooter)).toHaveLength(1); }); - describe('ModalHeader', () => { + describe('FlyoutHeader', () => { it('renders', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find('h2').text()).toEqual('Drag and drop .json'); }); }); - describe('ModalBody', () => { + describe('FlyoutBody', () => { it('updates fileInput when files are added & removed', () => { - const wrapper = shallow(); + const wrapper = shallow(); wrapper.find(EuiFilePicker).simulate('change', [mockFile]); expect(actions.setFileInput).toHaveBeenCalledWith(mockFile); @@ -65,16 +65,16 @@ describe('UploadJsonFile', () => { }); }); - describe('ModalFooter', () => { - it('closes the modal', () => { - const wrapper = shallow(); + describe('FlyoutFooter', () => { + it('closes the flyout', () => { + const wrapper = shallow(); wrapper.find(EuiButtonEmpty).simulate('click'); expect(actions.closeDocumentCreation).toHaveBeenCalled(); }); it('disables/enables the Continue button based on whether files have been uploaded', () => { - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(EuiButton).prop('isDisabled')).toBe(true); setMockValues({ ...values, fineInput: mockFile }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx index d4c005d5cfa2b1..6c5b1de79c3207 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/upload_json_file.tsx @@ -14,10 +14,12 @@ import { useValues, useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { - EuiModalHeader, - EuiModalHeaderTitle, - EuiModalBody, - EuiModalFooter, + EuiFlyoutHeader, + EuiTitle, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, EuiButton, EuiButtonEmpty, EuiFilePicker, @@ -27,40 +29,40 @@ import { import { AppLogic } from '../../../app_logic'; -import { MODAL_CANCEL_BUTTON, MODAL_CONTINUE_BUTTON } from '../constants'; +import { FLYOUT_ARIA_LABEL_ID, FLYOUT_CANCEL_BUTTON, FLYOUT_CONTINUE_BUTTON } from '../constants'; import { DocumentCreationLogic } from '../'; export const UploadJsonFile: React.FC = () => ( <> - - - + + + ); -export const ModalHeader: React.FC = () => { +export const FlyoutHeader: React.FC = () => { return ( - - -

+ + +

{i18n.translate( 'xpack.enterpriseSearch.appSearch.documentCreation.uploadJsonFile.title', { defaultMessage: 'Drag and drop .json' } )}

- - +
+
); }; -export const ModalBody: React.FC = () => { +export const FlyoutBody: React.FC = () => { const { configuredLimits } = useValues(AppLogic); const maxDocumentByteSize = configuredLimits?.engine?.maxDocumentByteSize; const { setFileInput } = useActions(DocumentCreationLogic); return ( - +

{i18n.translate( @@ -79,20 +81,26 @@ export const ModalBody: React.FC = () => { accept="application/json" fullWidth /> - + ); }; -export const ModalFooter: React.FC = () => { +export const FlyoutFooter: React.FC = () => { const { fileInput } = useValues(DocumentCreationLogic); const { closeDocumentCreation } = useActions(DocumentCreationLogic); return ( - - {MODAL_CANCEL_BUTTON} - - {MODAL_CONTINUE_BUTTON} - - + + + + {FLYOUT_CANCEL_BUTTON} + + + + {FLYOUT_CONTINUE_BUTTON} + + + + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx index c76cc4b45fc19b..93aff04b3f7c07 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_buttons.test.tsx @@ -41,7 +41,7 @@ describe('DocumentCreationButtons', () => { expect(wrapper.find(EuiCardTo).prop('isDisabled')).toEqual(true); }); - it('opens the DocumentCreationModal on click', () => { + it('opens the DocumentCreationFlyout on click', () => { const wrapper = shallow(); wrapper.find(EuiCard).at(0).simulate('click'); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx similarity index 77% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx index a0bca62dc74191..f2799cde41e971 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.test.tsx @@ -8,7 +8,7 @@ import { setMockValues, setMockActions } from '../../../__mocks__/kea.mock'; import React from 'react'; import { shallow } from 'enzyme'; -import { EuiModal } from '@elastic/eui'; +import { EuiFlyout } from '@elastic/eui'; import { ShowCreationModes, @@ -18,9 +18,9 @@ import { } from './creation_mode_components'; import { DocumentCreationStep } from './types'; -import { DocumentCreationModal, ModalContent } from './document_creation_modal'; +import { DocumentCreationFlyout, FlyoutContent } from './document_creation_flyout'; -describe('DocumentCreationModal', () => { +describe('DocumentCreationFlyout', () => { const values = { isDocumentCreationOpen: true, creationMode: 'text', @@ -36,25 +36,25 @@ describe('DocumentCreationModal', () => { setMockActions(actions); }); - it('renders a closeable modal', () => { - const wrapper = shallow(); - expect(wrapper.find(EuiModal)).toHaveLength(1); + it('renders a closeable flyout', () => { + const wrapper = shallow(); + expect(wrapper.find(EuiFlyout)).toHaveLength(1); - wrapper.find(EuiModal).prop('onClose')(); + wrapper.find(EuiFlyout).prop('onClose')(); expect(actions.closeDocumentCreation).toHaveBeenCalled(); }); it('does not render if isDocumentCreationOpen is false', () => { setMockValues({ ...values, isDocumentCreationOpen: false }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.isEmptyRender()).toBe(true); }); - describe('ModalContent', () => { + describe('FlyoutContent', () => { it('renders ShowCreationModes', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowCreationModes }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(ShowCreationModes)).toHaveLength(1); }); @@ -62,21 +62,21 @@ describe('DocumentCreationModal', () => { describe('creation modes', () => { it('renders ApiCodeExample', () => { setMockValues({ ...values, creationMode: 'api' }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(ApiCodeExample)).toHaveLength(1); }); it('renders PasteJsonText', () => { setMockValues({ ...values, creationMode: 'text' }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(PasteJsonText)).toHaveLength(1); }); it('renders UploadJsonFile', () => { setMockValues({ ...values, creationMode: 'file' }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.find(UploadJsonFile)).toHaveLength(1); }); @@ -85,21 +85,21 @@ describe('DocumentCreationModal', () => { describe('creation steps', () => { it('renders an error page', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowError }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.text()).toBe('DocumentCreationError'); // TODO: actual component }); it('renders an error summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowErrorSummary }); - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component }); it('renders a success summary', () => { setMockValues({ ...values, creationStep: DocumentCreationStep.ShowSuccessSummary }); - const wrapper = shallow(); + const wrapper = shallow(); // TODO: Figure out if the error and success summary should remain the same vs different components expect(wrapper.text()).toBe('DocumentCreationSummary'); // TODO: actual component diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx similarity index 78% rename from x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx rename to x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx index e6662a7c30407b..ca52d14befb380 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_flyout.tsx @@ -7,10 +7,11 @@ import React from 'react'; import { useValues, useActions } from 'kea'; -import { EuiOverlayMask, EuiModal } from '@elastic/eui'; +import { EuiPortal, EuiFlyout } from '@elastic/eui'; import { DocumentCreationLogic } from './'; import { DocumentCreationStep } from './types'; +import { FLYOUT_ARIA_LABEL_ID } from './constants'; import { ShowCreationModes, @@ -19,20 +20,20 @@ import { UploadJsonFile, } from './creation_mode_components'; -export const DocumentCreationModal: React.FC = () => { +export const DocumentCreationFlyout: React.FC = () => { const { closeDocumentCreation } = useActions(DocumentCreationLogic); const { isDocumentCreationOpen } = useValues(DocumentCreationLogic); return isDocumentCreationOpen ? ( - - - - - + + + + + ) : null; }; -export const ModalContent: React.FC = () => { +export const FlyoutContent: React.FC = () => { const { creationStep, creationMode } = useValues(DocumentCreationLogic); switch (creationStep) { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts index a5e015391d8fd0..5b85e7f2ab54e1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/document_creation_logic.ts @@ -30,7 +30,7 @@ interface DocumentCreationActions { export const DocumentCreationLogic = kea< MakeLogicType >({ - path: ['enterprise_search', 'app_search', 'document_creation_modal_logic'], + path: ['enterprise_search', 'app_search', 'document_creation_logic'], actions: () => ({ showCreationModes: () => null, openDocumentCreation: (creationMode) => ({ creationMode }), diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts index 0f4eaaeda0e1a2..d443b02393c057 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/index.ts @@ -5,5 +5,5 @@ */ export { DocumentCreationButtons } from './document_creation_buttons'; -export { DocumentCreationModal } from './document_creation_modal'; +export { DocumentCreationFlyout } from './document_creation_flyout'; export { DocumentCreationLogic } from './document_creation_logic'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx index 17e6e2538f044b..52fa0d03a9719d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.test.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { EuiButton } from '@elastic/eui'; -import { DocumentCreationModal } from '../document_creation'; +import { DocumentCreationFlyout } from '../document_creation'; import { DocumentCreationButton } from './document_creation_button'; describe('DocumentCreationButton', () => { @@ -24,7 +24,7 @@ describe('DocumentCreationButton', () => { it('renders', () => { expect(wrapper.find(EuiButton).length).toEqual(1); - expect(wrapper.find(DocumentCreationModal).length).toEqual(1); + expect(wrapper.find(DocumentCreationFlyout).length).toEqual(1); }); it('opens the document creation modes modal on click', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx index 6d211cf45ca9f3..3e4039bafcac73 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_creation_button.tsx @@ -10,7 +10,7 @@ import { useActions } from 'kea'; import { i18n } from '@kbn/i18n'; import { EuiButton } from '@elastic/eui'; -import { DocumentCreationLogic, DocumentCreationModal } from '../document_creation'; +import { DocumentCreationLogic, DocumentCreationFlyout } from '../document_creation'; export const DocumentCreationButton: React.FC = () => { const { showCreationModes } = useActions(DocumentCreationLogic); @@ -27,7 +27,7 @@ export const DocumentCreationButton: React.FC = () => { defaultMessage: 'Index documents', })} - + ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx index ad7874c01655bc..6c46c849c79bc0 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.test.tsx @@ -10,7 +10,7 @@ import { EuiButton } from '@elastic/eui'; import { CURRENT_MAJOR_VERSION } from '../../../../../common/version'; -import { DocumentCreationButtons, DocumentCreationModal } from '../document_creation'; +import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; import { EmptyEngineOverview } from './engine_overview_empty'; describe('EmptyEngineOverview', () => { @@ -32,6 +32,6 @@ describe('EmptyEngineOverview', () => { it('renders document creation components', () => { expect(wrapper.find(DocumentCreationButtons)).toHaveLength(1); - expect(wrapper.find(DocumentCreationModal)).toHaveLength(1); + expect(wrapper.find(DocumentCreationFlyout)).toHaveLength(1); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx index d65ca4868d2828..83dd396e5e0801 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine_overview/engine_overview_empty.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { DOCS_PREFIX } from '../../routes'; -import { DocumentCreationButtons, DocumentCreationModal } from '../document_creation'; +import { DocumentCreationButtons, DocumentCreationFlyout } from '../document_creation'; export const EmptyEngineOverview: React.FC = () => { return ( @@ -42,7 +42,7 @@ export const EmptyEngineOverview: React.FC = () => { - + ); From 4bf2de7b0d84b002b103bdbdc0097dedd55d4799 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Fri, 18 Dec 2020 22:59:59 +0100 Subject: [PATCH 18/40] [Discover] Change default sort handling (#85561) --- .../public/application/angular/discover.js | 8 ++- .../doc_table/lib/get_default_sort.test.ts | 43 ++++++++++++++++ .../angular/doc_table/lib/get_default_sort.ts | 7 ++- .../lib/get_sort_for_search_source.test.ts | 51 +++++++++++++++++++ .../lib/get_sort_for_search_source.ts | 12 +++-- .../embeddable/search_embeddable.ts | 17 ++++++- .../helpers/get_sharing_data.test.ts | 14 ++++- .../functional/apps/discover/_shared_links.ts | 2 +- 8 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts create mode 100644 src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 2c3b8fd9606a95..99497d61c716e8 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -65,11 +65,13 @@ import { MODIFY_COLUMNS_ON_SWITCH, SAMPLE_SIZE_SETTING, SEARCH_ON_PAGE_LOAD_SETTING, + SORT_DEFAULT_ORDER_SETTING, } from '../../../common'; import { loadIndexPattern, resolveIndexPattern } from '../helpers/resolve_index_pattern'; import { getTopNavLinks } from '../components/top_nav/get_top_nav_links'; import { updateSearchSource } from '../helpers/update_search_source'; import { calcFieldCounts } from '../helpers/calc_field_counts'; +import { getDefaultSort } from './doc_table/lib/get_default_sort'; const services = getServices(); @@ -410,9 +412,13 @@ function discoverController($element, $route, $scope, $timeout, Promise, uiCapab function getStateDefaults() { const query = $scope.searchSource.getField('query') || data.query.queryString.getDefaultQuery(); + const sort = getSortArray(savedSearch.sort, $scope.indexPattern); + return { query, - sort: getSortArray(savedSearch.sort, $scope.indexPattern), + sort: !sort.length + ? getDefaultSort($scope.indexPattern, config.get(SORT_DEFAULT_ORDER_SETTING, 'desc')) + : sort, columns: savedSearch.columns.length > 0 ? savedSearch.columns diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts new file mode 100644 index 00000000000000..9ad19653a6c12d --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.test.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getDefaultSort } from './get_default_sort'; +// @ts-ignore +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../kibana_services'; + +describe('getDefaultSort function', function () { + let indexPattern: IndexPattern; + beforeEach(() => { + indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; + }); + test('should be a function', function () { + expect(typeof getDefaultSort === 'function').toBeTruthy(); + }); + + test('should return default sort for an index pattern with timeFieldName', function () { + expect(getDefaultSort(indexPattern, 'desc')).toEqual([['time', 'desc']]); + expect(getDefaultSort(indexPattern, 'asc')).toEqual([['time', 'asc']]); + }); + + test('should return default sort for an index pattern without timeFieldName', function () { + delete indexPattern.timeFieldName; + expect(getDefaultSort(indexPattern, 'desc')).toEqual([]); + expect(getDefaultSort(indexPattern, 'asc')).toEqual([]); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts index 634e3cfec3a0bd..c1e4da0bab54d3 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_default_sort.ts @@ -17,7 +17,6 @@ * under the License. */ import { IndexPattern } from '../../../../kibana_services'; -// @ts-ignore import { isSortable } from './get_sort'; import { SortOrder } from '../components/table_header/helpers'; @@ -26,12 +25,12 @@ import { SortOrder } from '../components/table_header/helpers'; * the default sort is returned depending of the index pattern */ export function getDefaultSort( - indexPattern: IndexPattern, + indexPattern: IndexPattern | undefined, defaultSortOrder: string = 'desc' ): SortOrder[] { - if (indexPattern.timeFieldName && isSortable(indexPattern.timeFieldName, indexPattern)) { + if (indexPattern?.timeFieldName && isSortable(indexPattern.timeFieldName, indexPattern)) { return [[indexPattern.timeFieldName, defaultSortOrder]]; } else { - return [['_score', defaultSortOrder]]; + return []; } } diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts new file mode 100644 index 00000000000000..1dbd31897d3077 --- /dev/null +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.test.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { getSortForSearchSource } from './get_sort_for_search_source'; +// @ts-ignore +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../kibana_services'; +import { SortOrder } from '../components/table_header/helpers'; + +describe('getSortForSearchSource function', function () { + let indexPattern: IndexPattern; + beforeEach(() => { + indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; + }); + test('should be a function', function () { + expect(typeof getSortForSearchSource === 'function').toBeTruthy(); + }); + + test('should return an object to use for searchSource when columns are given', function () { + const cols = [['bytes', 'desc']] as SortOrder[]; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ bytes: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ bytes: 'desc' }]); + delete indexPattern.timeFieldName; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ bytes: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ bytes: 'desc' }]); + }); + + test('should return an object to use for searchSource when no columns are given', function () { + const cols = [] as SortOrder[]; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ _doc: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ _doc: 'asc' }]); + delete indexPattern.timeFieldName; + expect(getSortForSearchSource(cols, indexPattern)).toEqual([{ _score: 'desc' }]); + expect(getSortForSearchSource(cols, indexPattern, 'asc')).toEqual([{ _score: 'asc' }]); + }); +}); diff --git a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts index 6721f7a03584cd..1244a0e229cdbd 100644 --- a/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts +++ b/src/plugins/discover/public/application/angular/doc_table/lib/get_sort_for_search_source.ts @@ -19,7 +19,6 @@ import { EsQuerySortValue, IndexPattern } from '../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; import { getSort } from './get_sort'; -import { getDefaultSort } from './get_default_sort'; /** * Prepares sort for search source, that's sending the request to ES @@ -33,10 +32,13 @@ export function getSortForSearchSource( indexPattern?: IndexPattern, defaultDirection: string = 'desc' ): EsQuerySortValue[] { - if (!sort || !indexPattern) { - return []; - } else if (Array.isArray(sort) && sort.length === 0) { - sort = getDefaultSort(indexPattern, defaultDirection); + if (!sort || !indexPattern || (Array.isArray(sort) && sort.length === 0)) { + if (indexPattern?.timeFieldName) { + // sorting by index order + return [{ _doc: defaultDirection } as EsQuerySortValue]; + } else { + return [{ _score: defaultDirection } as EsQuerySortValue]; + } } const { timeFieldName } = indexPattern; return getSort(sort, indexPattern).map((sortPair: Record) => { diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index b143afd1988e68..ff408ba431ed95 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -48,6 +48,7 @@ import { import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearch } from '../..'; import { SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; +import { getDefaultSort } from '../angular/doc_table/lib/get_default_sort'; interface SearchScope extends ng.IScope { columns?: string[]; @@ -200,6 +201,13 @@ export class SearchEmbeddable const { searchSource } = this.savedSearch; const indexPattern = (searchScope.indexPattern = searchSource.getField('index'))!; + if (!this.savedSearch.sort || !this.savedSearch.sort.length) { + this.savedSearch.sort = getDefaultSort( + indexPattern, + getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') + ); + } + const timeRangeSearchSource = searchSource.create(); timeRangeSearchSource.setField('filter', () => { if (!this.searchScope || !this.input.timeRange) return; @@ -341,7 +349,14 @@ export class SearchEmbeddable // If there is column or sort data on the panel, that means the original columns or sort settings have // been overridden in a dashboard. searchScope.columns = this.input.columns || this.savedSearch.columns; - searchScope.sort = this.input.sort || this.savedSearch.sort; + const savedSearchSort = + this.savedSearch.sort && this.savedSearch.sort.length + ? this.savedSearch.sort + : getDefaultSort( + this.searchScope?.indexPattern, + getServices().uiSettings.get(SORT_DEFAULT_ORDER_SETTING, 'desc') + ); + searchScope.sort = this.input.sort || savedSearchSort; searchScope.sharedItemTitle = this.panelTitle; if (forceFetch || isFetchRequired) { diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index b2aa3a05d7eb02..4dec1f75ba322b 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -21,6 +21,7 @@ import { getSharingData } from './get_sharing_data'; import { IUiSettingsClient } from 'kibana/public'; import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks'; import { indexPatternMock } from '../../__mocks__/index_pattern'; +import { SORT_DEFAULT_ORDER_SETTING } from '../../../common'; describe('getSharingData', () => { test('returns valid data for sharing', async () => { @@ -29,7 +30,10 @@ describe('getSharingData', () => { searchSourceMock, { columns: [] }, ({ - get: () => { + get: (key: string) => { + if (key === SORT_DEFAULT_ORDER_SETTING) { + return 'desc'; + } return false; }, } as unknown) as IUiSettingsClient, @@ -57,7 +61,13 @@ describe('getSharingData', () => { }, }, "script_fields": Object {}, - "sort": Array [], + "sort": Array [ + Object { + "_score": Object { + "order": "desc", + }, + }, + ], "stored_fields": undefined, }, "index": "the-index-pattern-title", diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index a676878382865c..51ea5f997e859d 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -90,7 +90,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ":(from:'2015-09-19T06:31:44.000Z',to:'2015-09" + "-23T18:31:44.000Z'))&_a=(columns:!(_source),filters:!(),index:'logstash-" + "*',interval:auto,query:(language:kuery,query:'')" + - ',sort:!())'; + ",sort:!(!('@timestamp',desc)))"; const actualUrl = await PageObjects.share.getSharedUrl(); // strip the timestamp out of each URL expect(actualUrl.replace(/_t=\d{13}/, '_t=TIMESTAMP')).to.be( From 563fa6de22be7f0c00133e17cbe3639e37eb031f Mon Sep 17 00:00:00 2001 From: Jason Stoltzfus Date: Fri, 18 Dec 2020 17:01:05 -0500 Subject: [PATCH 19/40] [App Search] Updates to results on the documents view (#86181) --- .../build_search_ui_config.test.ts | 38 ++++++++++ .../build_search_ui_config.ts | 34 +++++++++ .../search_experience/search_experience.tsx | 11 +-- .../search_experience_content.test.tsx | 11 ++- .../search_experience_content.tsx | 4 +- .../views/result_view.test.tsx | 13 +++- .../search_experience/views/result_view.tsx | 10 ++- .../app_search/components/library/library.tsx | 37 +++++++++- .../app_search/components/result/result.scss | 1 + .../components/result/result.test.tsx | 36 +++++++++ .../app_search/components/result/result.tsx | 73 +++++++++++++------ .../components/result/result_field_value.scss | 5 -- .../result/result_field_value.test.tsx | 4 +- .../components/result/result_field_value.tsx | 5 +- .../public/applications/app_search/routes.ts | 3 + 15 files changed, 235 insertions(+), 50 deletions(-) create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts new file mode 100644 index 00000000000000..dd52f6b8227ba6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SchemaTypes } from '../../../../shared/types'; + +import { buildSearchUIConfig } from './build_search_ui_config'; + +describe('buildSearchUIConfig', () => { + it('builds a configuration object for Search UI', () => { + const connector = {}; + const schema = { + foo: 'text' as SchemaTypes, + bar: 'number' as SchemaTypes, + }; + + const config = buildSearchUIConfig(connector, schema); + expect(config.apiConnector).toEqual(connector); + expect(config.searchQuery.result_fields).toEqual({ + bar: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + foo: { + raw: {}, + snippet: { + fallback: true, + size: 300, + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts new file mode 100644 index 00000000000000..533adbaf5bab93 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/build_search_ui_config.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Schema } from '../../../../shared/types'; + +export const buildSearchUIConfig = (apiConnector: object, schema: Schema) => { + return { + alwaysSearchOnInitialLoad: true, + apiConnector, + trackUrlState: false, + initialState: { + sortDirection: 'desc', + sortField: 'id', + }, + searchQuery: { + result_fields: Object.keys(schema || {}).reduce( + (acc: { [key: string]: object }, key: string) => { + acc[key] = { + snippet: { + size: 300, + fallback: true, + }, + raw: {}, + }; + return acc; + }, + {} + ), + }, + }; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx index 49cc573b686bc2..1501efc589fc08 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience.tsx @@ -20,6 +20,7 @@ import { externalUrl } from '../../../../shared/enterprise_search_url'; import { SearchBoxView, SortingView } from './views'; import { SearchExperienceContent } from './search_experience_content'; +import { buildSearchUIConfig } from './build_search_ui_config'; const DEFAULT_SORT_OPTIONS = [ { @@ -52,15 +53,7 @@ export const SearchExperience: React.FC = () => { searchKey: engine.apiKey, }); - const searchProviderConfig = { - alwaysSearchOnInitialLoad: true, - apiConnector: connector, - trackUrlState: false, - initialState: { - sortDirection: 'desc', - sortField: 'id', - }, - }; + const searchProviderConfig = buildSearchUIConfig(connector, engine.schema || {}); return (

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx index 455e237848a4bc..a46ec560a13e0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.test.tsx @@ -15,6 +15,7 @@ import { Results } from '@elastic/react-search-ui'; import { ResultView } from './views'; import { Pagination } from './pagination'; +import { SchemaTypes } from '../../../../shared/types'; import { SearchExperienceContent } from './search_experience_content'; describe('SearchExperienceContent', () => { @@ -27,6 +28,11 @@ describe('SearchExperienceContent', () => { engineName: 'engine1', isMetaEngine: false, myRole: { canManageEngineDocuments: true }, + engine: { + schema: { + title: 'string' as SchemaTypes, + }, + }, }; beforeEach(() => { @@ -40,7 +46,7 @@ describe('SearchExperienceContent', () => { expect(wrapper.isEmptyRender()).toBe(false); }); - it('passes engineName to the result view', () => { + it('passes engineName and schema to the result view', () => { const props = { result: { id: { @@ -56,6 +62,9 @@ describe('SearchExperienceContent', () => { raw: 'bar', }, }, + schemaForTypeHighlights: { + title: 'string' as SchemaTypes, + }, }; const wrapper = shallow(); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx index 9194a3a1db5e44..55a8377261dd9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/search_experience_content.tsx @@ -25,7 +25,7 @@ export const SearchExperienceContent: React.FC = () => { const { resultSearchTerm, totalResults, wasSearched } = useSearchContextState(); const { myRole } = useValues(AppLogic); - const { isMetaEngine } = useValues(EngineLogic); + const { isMetaEngine, engine } = useValues(EngineLogic); if (!wasSearched) return null; @@ -44,7 +44,7 @@ export const SearchExperienceContent: React.FC = () => { { - return ; + return ; }} /> diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx index 049a3ad1bed66f..91334f312623df 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.test.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ResultView } from '.'; +import { SchemaTypes } from '../../../../../shared/types'; import { Result } from '../../../result/result'; describe('ResultView', () => { @@ -27,8 +28,16 @@ describe('ResultView', () => { }, }; + const schema = { + title: 'string' as SchemaTypes, + }; + it('renders', () => { - const wrapper = shallow(); - expect(wrapper.find(Result).exists()).toBe(true); + const wrapper = shallow(); + expect(wrapper.find(Result).props()).toEqual({ + result, + shouldLinkToDetailPage: true, + schemaForTypeHighlights: schema, + }); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx index 52b845a1aee2d8..543c63b3349407 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/search_experience/views/result_view.tsx @@ -7,16 +7,22 @@ import React from 'react'; import { Result as ResultType } from '../../../result/types'; +import { Schema } from '../../../../../shared/types'; import { Result } from '../../../result/result'; export interface Props { result: ResultType; + schemaForTypeHighlights?: Schema; } -export const ResultView: React.FC = ({ result }) => { +export const ResultView: React.FC = ({ result, schemaForTypeHighlights }) => { return (
  • - +
  • ); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx index 66c0cc165fc056..1b222cfaacf7c1 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/library/library.tsx @@ -15,6 +15,7 @@ import { import React from 'react'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { Schema } from '../../../shared/types'; import { Result } from '../result/result'; export const Library: React.FC = () => { @@ -35,12 +36,18 @@ export const Library: React.FC = () => { description: { raw: 'A description', }, - states: { - raw: ['Pennsylvania', 'Ohio'], + date_established: { + raw: '1968-10-02T05:00:00Z', + }, + location: { + raw: '37.3,-113.05', }, visitors: { raw: 1000, }, + states: { + raw: ['Pennsylvania', 'Ohio'], + }, size: { raw: 200, }, @@ -50,6 +57,17 @@ export const Library: React.FC = () => { }, }; + const schema: Schema = { + title: 'text', + description: 'text', + date_established: 'date', + location: 'geolocation', + states: 'text', + visitors: 'number', + size: 'number', + length: 'number', + }; + return ( <> @@ -170,6 +188,21 @@ export const Library: React.FC = () => { }, }} /> + + + +

    With a link

    +
    + + + + + + +

    With field value type highlights

    +
    + + diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss index ed8ce512a2eb80..8342061ee00c3b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.scss @@ -5,6 +5,7 @@ width: 100%; padding: $euiSize; overflow: hidden; + color: $euiTextColor; } &__hiddenFieldsIndicator { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx index ade26551039faa..5b598a0b8565ec 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.test.tsx @@ -11,6 +11,9 @@ import { EuiPanel } from '@elastic/eui'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { SchemaTypes } from '../../../shared/types'; + import { Result } from './result'; describe('Result', () => { @@ -37,6 +40,12 @@ describe('Result', () => { }, }; + const schema = { + title: 'text' as SchemaTypes, + description: 'text' as SchemaTypes, + length: 'number' as SchemaTypes, + }; + it('renders', () => { const wrapper = shallow(); expect(wrapper.find(EuiPanel).exists()).toBe(true); @@ -62,6 +71,33 @@ describe('Result', () => { }); }); + describe('document detail link', () => { + it('will render a link if shouldLinkToDetailPage is true', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).prop('to')).toEqual('/engines/my-engine/documents/1'); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(false); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(true); + }); + + it('will not render a link if shouldLinkToDetailPage is not set', () => { + const wrapper = shallow(); + expect(wrapper.find(ReactRouterHelper).exists()).toBe(false); + expect(wrapper.find('article.appSearchResult__content').exists()).toBe(true); + expect(wrapper.find('a.appSearchResult__content').exists()).toBe(false); + }); + }); + + it('will render field details with type highlights if schemaForTypeHighlights has been provided', () => { + const wrapper = shallow( + + ); + expect(wrapper.find(ResultField).map((rf) => rf.prop('type'))).toEqual([ + 'text', + 'text', + 'number', + ]); + }); + describe('when there are more than 5 fields', () => { const propsWithMoreFields = { result: { diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index 4f343e64b12ae5..11415f55123804 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -14,15 +14,25 @@ import { i18n } from '@kbn/i18n'; import { FieldValue, Result as ResultType } from './types'; import { ResultField } from './result_field'; import { ResultHeader } from './result_header'; +import { getDocumentDetailRoute } from '../../routes'; +import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { Schema } from '../../../shared/types'; interface Props { result: ResultType; showScore?: boolean; + shouldLinkToDetailPage?: boolean; + schemaForTypeHighlights?: Schema; } const RESULT_CUTOFF = 5; -export const Result: React.FC = ({ result, showScore }) => { +export const Result: React.FC = ({ + result, + showScore = false, + shouldLinkToDetailPage = false, + schemaForTypeHighlights, +}) => { const [isOpen, setIsOpen] = useState(false); const ID = 'id'; @@ -33,6 +43,19 @@ export const Result: React.FC = ({ result, showScore }) => { [result] ); const numResults = resultFields.length; + const typeForField = (fieldName: string) => { + if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; + }; + + const conditionallyLinkedArticle = (children: React.ReactNode) => { + return shouldLinkToDetailPage ? ( + + {children} + + ) : ( +
    {children}
    + ); + }; return ( = ({ result, showScore }) => { defaultMessage: 'View document details', })} > -
    - -
    - {resultFields - .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) - .map(([field, value]: [string, FieldValue]) => ( - - ))} -
    - {numResults > RESULT_CUTOFF && !isOpen && ( -
    - {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { - defaultMessage: '{numberOfAdditionalFields} more fields', - values: { - numberOfAdditionalFields: numResults - RESULT_CUTOFF, - }, - })} -
    - )} -
    + {conditionallyLinkedArticle( + <> + +
    + {resultFields + .slice(0, isOpen ? resultFields.length : RESULT_CUTOFF) + .map(([field, value]: [string, FieldValue]) => ( + + ))} +
    + {numResults > RESULT_CUTOFF && !isOpen && ( +
    + {i18n.translate('xpack.enterpriseSearch.appSearch.result.numberOfAdditionalFields', { + defaultMessage: '{numberOfAdditionalFields} more fields', + values: { + numberOfAdditionalFields: numResults - RESULT_CUTOFF, + }, + })} +
    + )} + + )} {numResults > RESULT_CUTOFF && (
    ); + const { code } = status.state.state.ui.message; const accordion = ( + {code?.length ? ( + + {code} + + ) : null} = (props: Props) => { paddingLeft: `0.5rem`, }} > - {(status.state.state.ui.message.nextSteps || []).map((step: AlertMessage) => { - return {}} label={replaceTokens(step)} />; - })} + {(status.state.state.ui.message.nextSteps || []).map( + (step: AlertMessage, stepIndex: number) => { + return ( + {}} + label={replaceTokens(step)} + key={index + stepIndex} + /> + ); + } + )} } + label={} /> diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx new file mode 100644 index 00000000000000..2dafadf2726082 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Expression, Props } from '../components/duration/expression'; +import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; +import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; + +interface ValidateOptions { + duration: string; +} + +const validate = (inputValues: ValidateOptions): ValidationResult => { + const validationResult = { errors: {} }; + const errors: { [key: string]: string[] } = { + duration: [], + }; + if (!inputValues.duration) { + errors.duration.push( + i18n.translate('xpack.monitoring.alerts.validation.duration', { + defaultMessage: 'A valid duration is required.', + }) + ); + } + validationResult.errors = errors; + return validationResult; +}; + +export function createCCRReadExceptionsAlertType(): AlertTypeModel { + return { + id: ALERT_CCR_READ_EXCEPTIONS, + name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label, + description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description, + iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.ELASTIC_WEBSITE_URL}guide/en/kibana/${docLinks.DOC_LINK_VERSION}/kibana-alerts.html`; + }, + alertParamsExpression: (props: Props) => ( + + ), + validate, + defaultActionMessage: '{{context.internalFullMessage}}', + requiresAppContext: true, + }; +} diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx index 82a1a1f841a22a..bbea32e4d2d04a 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_category.tsx @@ -171,6 +171,7 @@ export function getAlertPanelsByCategory( for (const { alert, states } of category.alerts) { const items = []; for (const alertState of states.filter(({ state }) => stateFilter(state))) { + const { nodeName, itemLabel } = alertState.state; items.push({ name: ( @@ -188,7 +189,7 @@ export function getAlertPanelsByCategory( )}
    - {alertState.state.nodeName} + {nodeName || itemLabel} ), panel: ++tertiaryPanelIndex, diff --git a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx index c48706f4edcb97..735b9c3637cdd9 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/get_alert_panels_by_node.tsx @@ -69,10 +69,11 @@ export function getAlertPanelsByNode( const states = (statesByNodes[nodeUuid] as CommonAlertState[]).filter(({ state }) => stateFilter(state) ); + const { nodeName, itemLabel } = states[0].state; return { name: ( - {states[0].state.nodeName} ({states.length}) + {nodeName || itemLabel} ({states.length}) ), panel: index + 1, @@ -86,7 +87,8 @@ export function getAlertPanelsByNode( let title = ''; for (const { alert, states } of alertsForNode) { for (const alertState of states) { - title = alertState.state.nodeName; + const { nodeName, itemLabel } = alertState.state; + title = nodeName || itemLabel; panelItems.push({ name: ( diff --git a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx index b8ac69cbae68a4..0ddda96a1100d8 100644 --- a/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx +++ b/x-pack/plugins/monitoring/public/alerts/lib/replace_tokens.tsx @@ -77,6 +77,7 @@ export function replaceTokens(alertMessage: AlertMessage): JSX.Element | string } const url = linkToken.partialUrl + .replace('{basePath}', Legacy.shims.getBasePath()) .replace('{elasticWebsiteUrl}', Legacy.shims.docLinks.ELASTIC_WEBSITE_URL) .replace('{docLinkVersion}', Legacy.shims.docLinks.DOC_LINK_VERSION); const index = text.indexOf(linkPart[0]); diff --git a/x-pack/plugins/monitoring/public/alerts/panel.tsx b/x-pack/plugins/monitoring/public/alerts/panel.tsx index 139010a3d24469..2d319a81dd0630 100644 --- a/x-pack/plugins/monitoring/public/alerts/panel.tsx +++ b/x-pack/plugins/monitoring/public/alerts/panel.tsx @@ -10,6 +10,7 @@ import { EuiHorizontalRule, EuiListGroup, EuiListGroupItem, + EuiCodeBlock, } from '@elastic/eui'; import { CommonAlert, CommonAlertState, AlertMessage } from '../../common/types/alerts'; @@ -47,12 +48,24 @@ export const AlertPanel: React.FC = (props: Props) => { ) : null; + const { code } = alertState.state.ui.message; return (
    {replaceTokens(alertState.state.ui.message)}
    + {code?.length ? ( + + {code} + + ) : null} {nextStepsUi ? : null} {nextStepsUi}
    diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index ded309ce64e2ec..8849fb05fcf3c7 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -47,6 +47,7 @@ import { ALERT_NODES_CHANGED, ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA, + ALERT_CCR_READ_EXCEPTIONS, } from '../../../../common/constants'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; @@ -159,7 +160,11 @@ function renderLog(log) { ); } -const OVERVIEW_PANEL_ALERTS = [ALERT_CLUSTER_HEALTH, ALERT_LICENSE_EXPIRATION]; +const OVERVIEW_PANEL_ALERTS = [ + ALERT_CLUSTER_HEALTH, + ALERT_LICENSE_EXPIRATION, + ALERT_CCR_READ_EXCEPTIONS, +]; const NODES_PANEL_ALERTS = [ ALERT_CPU_USAGE, diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap index d54612b6f4f29a..794982a0b6193d 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap @@ -29,6 +29,12 @@ exports[`Ccr that it renders normally 1`] = ` "name": "Follows", "sortable": true, }, + Object { + "field": "alerts", + "name": "Alerts", + "render": [Function], + "sortable": true, + }, Object { "field": "syncLagOps", "name": "Sync Lag (ops)", diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js index ab26b6a9cc0bb6..8b7c386a4dcc6c 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr/ccr.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, Component } from 'react'; +import React, { Fragment, useState } from 'react'; import { EuiInMemoryTable, EuiLink, @@ -20,27 +20,20 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { getSafeForExternalLink } from '../../../lib/get_safe_for_external_link'; +import { AlertsStatus } from '../../../alerts/status'; import './ccr.scss'; function toSeconds(ms) { return Math.floor(ms / 1000) + 's'; } -export class Ccr extends Component { - constructor(props) { - super(props); - this.state = { - itemIdToExpandedRowMap: {}, - }; - } - - toggleShards(index, shards) { - const itemIdToExpandedRowMap = { - ...this.state.itemIdToExpandedRowMap, - }; +export const Ccr = (props) => { + const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState({}); + const toggleShards = (index, shards) => { + const itemIdToExpandedRowMapValues = { ...itemIdToExpandedRowMap }; - if (itemIdToExpandedRowMap[index]) { - delete itemIdToExpandedRowMap[index]; + if (itemIdToExpandedRowMapValues[index]) { + delete itemIdToExpandedRowMapValues[index]; } else { let pagination = { initialPageSize: 5, @@ -51,7 +44,7 @@ export class Ccr extends Component { pagination = false; } - itemIdToExpandedRowMap[index] = ( + itemIdToExpandedRowMapValues[index] = ( null, }, + { + field: 'alerts', + sortable: true, + name: i18n.translate( + 'xpack.monitoring.elasticsearch.ccr.shardsTable.alertsColumnTitle', + { + defaultMessage: 'Alerts', + } + ), + render: (_field, item) => { + return ( + state.meta.shardId === item.shardId} + /> + ); + }, + }, { field: 'syncLagOps', name: i18n.translate( @@ -157,11 +169,11 @@ export class Ccr extends Component { /> ); } - this.setState({ itemIdToExpandedRowMap }); - } + setItemIdToExpandedRowMap(itemIdToExpandedRowMapValues); + }; - renderTable() { - const { data } = this.props; + const renderTable = () => { + const { data, alerts } = props; const items = data; let pagination = { @@ -194,9 +206,9 @@ export class Ccr extends Component { ), sortable: true, render: (index, { shards }) => { - const expanded = !!this.state.itemIdToExpandedRowMap[index]; + const expanded = !!itemIdToExpandedRowMap[index]; return ( - this.toggleShards(index, shards)}> + toggleShards(index, shards)}> {index}   {expanded ? : } @@ -214,6 +226,25 @@ export class Ccr extends Component { } ), }, + { + field: 'alerts', + sortable: true, + name: i18n.translate( + 'xpack.monitoring.elasticsearch.ccr.ccrListingTable.alertsColumnTitle', + { + defaultMessage: 'Alerts', + } + ), + render: (_field, item) => { + return ( + state.meta.followerIndex === item.index} + /> + ); + }, + }, { field: 'syncLagOps', sortable: true, @@ -264,28 +295,26 @@ export class Ccr extends Component { }} sorting={sorting} itemId="id" - itemIdToExpandedRowMap={this.state.itemIdToExpandedRowMap} + itemIdToExpandedRowMap={itemIdToExpandedRowMap} /> ); - } + }; - render() { - return ( - - - -

    - -

    -
    - - {this.renderTable()} - -
    -
    - ); - } -} + return ( + + + +

    + +

    +
    + + {renderTable()} + +
    +
    + ); +}; diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap index e35d2ba6108f52..81398c1d8e8362 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap @@ -2,50 +2,46 @@ exports[`CcrShard that is renders an exception properly 1`] = ` - -

    - - - -

    -
    - -
    `; @@ -59,44 +55,50 @@ exports[`CcrShard that it renders normally 1`] = ` } > - + + + + - - + + + + + + {this.renderErrors()} {this.renderCharts()} diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js index 52de0659ed527b..657301d6e1cb3e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/ccr_shard/status.js @@ -8,8 +8,9 @@ import React from 'react'; import { SummaryStatus } from '../../summary_status'; import { formatMetric } from '../../../lib/format_number'; import { i18n } from '@kbn/i18n'; +import { AlertsStatus } from '../../../alerts/status'; -export function Status({ stat, formattedLeader, oldestStat }) { +export function Status({ stat, formattedLeader, oldestStat, alerts = {} }) { const { follower_index: followerIndex, shard_id: shardId, @@ -23,6 +24,12 @@ export function Status({ stat, formattedLeader, oldestStat }) { } = oldestStat; const metrics = [ + { + label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.alerts', { + defaultMessage: 'Alerts', + }), + value: , + }, { label: i18n.translate('xpack.monitoring.elasticsearch.ccrShard.status.followerIndexLabel', { defaultMessage: 'Follower Index', diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 0439b47569e720..a0de3a7663a129 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -156,6 +156,7 @@ export class MonitoringPlugin './alerts/thread_pool_rejections_alert' ); const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert'); + const { createCCRReadExceptionsAlertType } = await import('./alerts/ccr_read_exceptions_alert'); const { triggersActionsUi: { alertTypeRegistry }, @@ -176,6 +177,7 @@ export class MonitoringPlugin ALERT_DETAILS[ALERT_THREAD_POOL_WRITE_REJECTIONS] ) ); + alertTypeRegistry.register(createCCRReadExceptionsAlertType()); const legacyAlertTypes = createLegacyAlertTypes(); for (const legacyAlertType of legacyAlertTypes) { alertTypeRegistry.register(legacyAlertType); diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js index 65693407857363..9e26d453d76a35 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/index.js @@ -12,7 +12,13 @@ import { routeInitProvider } from '../../../lib/route_init'; import template from './index.html'; import { Ccr } from '../../../components/elasticsearch/ccr'; import { MonitoringViewBaseController } from '../../base_controller'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../common/constants'; +import { SetupModeRenderer } from '../../../components/renderers'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/ccr', { template, @@ -37,6 +43,12 @@ uiRoutes.when('/elasticsearch/ccr', { getPageData, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS], + }, + }, }); $scope.$watch( @@ -45,7 +57,20 @@ uiRoutes.when('/elasticsearch/ccr', { if (!data) { return; } - this.renderReact(); + this.renderReact( + ( + + {flyoutComponent} + + {bottomBarComponent} + + )} + /> + ); } ); } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js index 33a2d27f398565..6c1c4218568e35 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/ccr/shard/index.js @@ -13,7 +13,13 @@ import { routeInitProvider } from '../../../../lib/route_init'; import template from './index.html'; import { MonitoringViewBaseController } from '../../../base_controller'; import { CcrShard } from '../../../../components/elasticsearch/ccr_shard'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../../common/constants'; +import { SetupModeRenderer } from '../../../../components/renderers'; +import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { template, @@ -27,6 +33,7 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { controllerAs: 'elasticsearchCcr', controller: class ElasticsearchCcrController extends MonitoringViewBaseController { constructor($injector, $scope, pageData) { + const $route = $injector.get('$route'); super({ title: i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.routeTitle', { defaultMessage: 'Elasticsearch - Ccr - Shard', @@ -35,6 +42,17 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { getPageData, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_CCR_READ_EXCEPTIONS], + filters: [ + { + shardId: $route.current.pathParams.shardId, + }, + ], + }, + }, }); $scope.instance = i18n.translate('xpack.monitoring.elasticsearch.ccr.shard.instanceTitle', { @@ -62,7 +80,20 @@ uiRoutes.when('/elasticsearch/ccr/:index/shard/:shardId', { }) ); - this.renderReact(); + this.renderReact( + ( + + {flyoutComponent} + + {bottomBarComponent} + + )} + /> + ); } ); } diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index b43a56562a2aab..64b7148d87d9e4 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -5,6 +5,7 @@ */ import { + CCRReadExceptionsAlert, CpuUsageAlert, MissingMonitoringDataAlert, DiskUsageAlert, @@ -32,6 +33,7 @@ import { ALERT_LOGSTASH_VERSION_MISMATCH, ALERT_KIBANA_VERSION_MISMATCH, ALERT_ELASTICSEARCH_VERSION_MISMATCH, + ALERT_CCR_READ_EXCEPTIONS, } from '../../common/constants'; import { AlertsClient } from '../../../alerts/server'; import { Alert } from '../../../alerts/common'; @@ -49,6 +51,7 @@ const BY_TYPE = { [ALERT_LOGSTASH_VERSION_MISMATCH]: LogstashVersionMismatchAlert, [ALERT_KIBANA_VERSION_MISMATCH]: KibanaVersionMismatchAlert, [ALERT_ELASTICSEARCH_VERSION_MISMATCH]: ElasticsearchVersionMismatchAlert, + [ALERT_CCR_READ_EXCEPTIONS]: CCRReadExceptionsAlert, }; export class AlertsFactory { @@ -68,7 +71,6 @@ export class AlertsFactory { if (!alertClientAlerts.total || !alertClientAlerts.data?.length) { return; - // return new alertCls() as BaseAlert; } const [rawAlert] = alertClientAlerts.data as [Alert]; diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index ebff72a255777f..a3bcc310b80847 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -345,7 +345,7 @@ export class BaseAlert { const firingNodeUuids = nodes .filter((node) => node.shouldFire) - .map((node) => node.meta.nodeId) + .map((node) => node.meta.nodeId || node.meta.instanceId) .join(','); const instanceId = `${this.alertOptions.id}:${cluster.clusterUuid}:${firingNodeUuids}`; const instance = services.alertInstanceFactory(instanceId); @@ -355,13 +355,16 @@ export class BaseAlert { if (!node.shouldFire) { continue; } - const stat = node.meta as AlertNodeState; + const { meta } = node; const nodeState = this.getDefaultAlertState(cluster, node) as AlertNodeState; if (key) { - nodeState[key] = stat[key]; + nodeState[key] = meta[key]; } - nodeState.nodeId = stat.nodeId || node.nodeId!; - nodeState.nodeName = stat.nodeName || node.nodeName || nodeState.nodeId; + nodeState.nodeId = meta.nodeId || node.nodeId! || meta.instanceId; + // TODO: make these functions more generic, so it's node/item agnostic + nodeState.nodeName = meta.itemLabel || meta.nodeName || node.nodeName || nodeState.nodeId; + nodeState.itemLabel = meta.itemLabel; + nodeState.meta = meta; nodeState.ui.triggeredMS = currentUTC; nodeState.ui.isFiring = true; nodeState.ui.severity = node.severity; diff --git a/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts new file mode 100644 index 00000000000000..6034f32a8c6598 --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/ccr_read_exceptions_alert.ts @@ -0,0 +1,289 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { BaseAlert } from './base_alert'; +import { + AlertData, + AlertCluster, + AlertState, + AlertMessage, + CCRReadExceptionsUIMeta, + AlertMessageTimeToken, + AlertMessageLinkToken, + AlertInstanceState, + CommonAlertParams, + CommonAlertFilter, + CCRReadExceptionsStats, +} from '../../common/types/alerts'; +import { AlertInstance } from '../../../alerts/server'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_CCR_READ_EXCEPTIONS, + ALERT_DETAILS, +} from '../../common/constants'; +import { fetchCCRReadExceptions } from '../lib/alerts/fetch_ccr_read_exceptions'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; +import { parseDuration } from '../../../alerts/common/parse_duration'; +import { SanitizedAlert, RawAlertInstance } from '../../../alerts/common'; +import { AlertingDefaults, createLink } from './alert_helpers'; +import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; +import { Globals } from '../static_globals'; + +export class CCRReadExceptionsAlert extends BaseAlert { + constructor(public rawAlert?: SanitizedAlert) { + super(rawAlert, { + id: ALERT_CCR_READ_EXCEPTIONS, + name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label, + throttle: '6h', + defaultParams: { + duration: '1h', + }, + actionVariables: [ + { + name: 'remoteClusters', + description: i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.remoteClusters', + { + defaultMessage: 'List of remote clusters that are experiencing CCR read exceptions.', + } + ), + }, + { + name: 'followerIndices', + description: i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.actionVariables.followerIndices', + { + defaultMessage: 'List of follower indices reporting CCR read exceptions.', + } + ), + }, + ...Object.values(AlertingDefaults.ALERT_TYPE.context), + ], + }); + } + + protected async fetchData( + params: CommonAlertParams, + callCluster: any, + clusters: AlertCluster[], + availableCcs: string[] + ): Promise { + let esIndexPattern = appendMetricbeatIndex(Globals.app.config, INDEX_PATTERN_ELASTICSEARCH); + if (availableCcs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + const { duration: durationString } = params; + const duration = parseDuration(durationString); + const endMs = +new Date(); + const startMs = endMs - duration; + const stats = await fetchCCRReadExceptions( + callCluster, + esIndexPattern, + startMs, + endMs, + Globals.app.config.ui.max_bucket_size + ); + + return stats.map((stat) => { + const { + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + clusterUuid, + ccs, + } = stat; + return { + shouldFire: true, + severity: AlertSeverity.Danger, + meta: { + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + instanceId: `${remoteCluster}:${followerIndex}`, + itemLabel: followerIndex, + }, + clusterUuid, + ccs, + }; + }); + } + + protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { + const { + remoteCluster, + followerIndex, + shardId, + lastReadException, + } = item.meta as CCRReadExceptionsUIMeta; + return { + text: i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.firingMessage', { + defaultMessage: `Follower index #start_link{followerIndex}#end_link is reporting CCR read exceptions on remote cluster: {remoteCluster} at #absolute`, + values: { + remoteCluster, + followerIndex, + }, + }), + code: JSON.stringify(lastReadException, null, 2), + nextSteps: [ + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.identifyCCRStats', + { + defaultMessage: '#start_linkIdentify CCR usage/stats#end_link', + } + ), + 'elasticsearch/ccr', + AlertMessageTokenType.Link + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentFollow', + { + defaultMessage: '#start_linkManage CCR follower indices#end_link', + } + ), + `{basePath}management/data/cross_cluster_replication/follower_indices` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.stackManagmentAutoFollow', + { + defaultMessage: '#start_linkCreate auto-follow patterns#end_link', + } + ), + `{basePath}management/data/cross_cluster_replication/auto_follow_patterns` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followerAPIDoc', { + defaultMessage: '#start_linkAdd follower index API (Docs)#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/ccr-put-follow.html` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.ccrDocs', { + defaultMessage: '#start_linkCross-cluster replication (Docs)#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/{docLinkVersion}/xpack-ccr.html` + ), + createLink( + i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.biDirectionalReplication', + { + defaultMessage: '#start_linkBi-directional replication (Blog)#end_link', + } + ), + `{elasticWebsiteUrl}blog/bi-directional-replication-with-elasticsearch-cross-cluster-replication-ccr` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.ui.nextSteps.followTheLeader', { + defaultMessage: '#start_linkFollow the Leader (Blog)#end_link', + }), + `{elasticWebsiteUrl}blog/follow-the-leader-an-introduction-to-cross-cluster-replication-in-elasticsearch` + ), + ], + tokens: [ + { + startToken: '#absolute', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.triggeredMS, + } as AlertMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.Link, + url: `elasticsearch/ccr/${followerIndex}/shard/${shardId}`, + } as AlertMessageLinkToken, + ], + }; + } + + protected filterAlertInstance(alertInstance: RawAlertInstance, filters: CommonAlertFilter[]) { + const alertInstanceStates = alertInstance.state?.alertStates as AlertState[]; + const alertFilter = filters?.find((filter) => filter.shardId); + if (!filters || !filters.length || !alertInstanceStates?.length || !alertFilter?.shardId) { + return alertInstance; + } + const shardIdInt = parseInt(alertFilter.shardId!, 10); + const alertStates = alertInstanceStates.filter( + ({ meta }) => (meta as CCRReadExceptionsStats).shardId === shardIdInt + ); + return { state: { alertStates } }; + } + + protected executeActions( + instance: AlertInstance, + { alertStates }: AlertInstanceState, + item: AlertData | null, + cluster: AlertCluster + ) { + const remoteClustersList = alertStates + .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).remoteCluster) + .join(', '); + const followerIndicesList = alertStates + .map((alertState) => (alertState.meta as CCRReadExceptionsUIMeta).followerIndex) + .join(', '); + + const shortActionText = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.shortAction', + { + defaultMessage: + 'Verify follower and leader index relationships across the affected remote clusters.', + } + ); + const fullActionText = i18n.translate('xpack.monitoring.alerts.ccrReadExceptions.fullAction', { + defaultMessage: 'View CCR stats', + }); + + const ccs = alertStates.find((state) => state.ccs)?.ccs; + const globalStateLink = this.createGlobalStateLink( + 'elasticsearch/ccr', + cluster.clusterUuid, + ccs + ); + + const action = `[${fullActionText}](${globalStateLink})`; + const internalShortMessage = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.firing.internalShortMessage', + { + defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. {shortActionText}`, + values: { + remoteClustersList, + shortActionText, + }, + } + ); + const internalFullMessage = i18n.translate( + 'xpack.monitoring.alerts.ccrReadExceptions.firing.internalFullMessage', + { + defaultMessage: `CCR read exceptions alert is firing for the following remote clusters: {remoteClustersList}. Current 'follower_index' indices are affected: {followerIndicesList}. {action}`, + values: { + action, + remoteClustersList, + followerIndicesList, + }, + } + ); + + instance.scheduleActions('default', { + internalShortMessage, + internalFullMessage, + state: AlertingDefaults.ALERT_STATE.firing, + remoteClusters: remoteClustersList, + followerIndices: followerIndicesList, + clusterName: cluster.clusterName, + action, + actionPlain: shortActionText, + }); + } +} diff --git a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts index 63195621fb9c84..4622f73b9feb0c 100644 --- a/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/cpu_usage_alert.test.ts @@ -125,6 +125,13 @@ describe('CpuUsageAlert', () => { ccs: undefined, cluster: { clusterUuid, clusterName }, cpuUsage, + itemLabel: undefined, + meta: { + clusterUuid, + cpuUsage, + nodeId, + nodeName, + }, nodeId, nodeName, ui: { diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index 5fa718dfb34cda..b58476a01dc14f 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { CCRReadExceptionsAlert } from './ccr_read_exceptions_alert'; export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; export { MissingMonitoringDataAlert } from './missing_monitoring_data_alert'; diff --git a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts index 6ba4333309f00a..65205738f82c36 100644 --- a/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts +++ b/x-pack/plugins/monitoring/server/alerts/missing_monitoring_data_alert.test.ts @@ -131,6 +131,14 @@ describe('MissingMonitoringDataAlert', () => { nodeId, nodeName, gapDuration, + itemLabel: undefined, + meta: { + clusterUuid, + gapDuration, + limit: 86400000, + nodeId, + nodeName, + }, ui: { isFiring: true, message: { diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts new file mode 100644 index 00000000000000..c8933a7cd14a9c --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_ccr_read_exceptions.ts @@ -0,0 +1,131 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { CCRReadExceptionsStats } from '../../../common/types/alerts'; + +export async function fetchCCRReadExceptions( + callCluster: any, + index: string, + startMs: number, + endMs: number, + size: number +): Promise { + const params = { + index, + filterPath: ['aggregations.remote_clusters.buckets'], + body: { + size: 0, + query: { + bool: { + filter: [ + { + nested: { + path: 'ccr_stats.read_exceptions', + query: { + exists: { + field: 'ccr_stats.read_exceptions.exception', + }, + }, + }, + }, + { + term: { + type: 'ccr_stats', + }, + }, + { + range: { + timestamp: { + format: 'epoch_millis', + gte: startMs, + lte: endMs, + }, + }, + }, + ], + }, + }, + aggs: { + remote_clusters: { + terms: { + field: 'ccr_stats.remote_cluster', + size, + }, + aggs: { + follower_indices: { + terms: { + field: 'ccr_stats.follower_index', + size, + }, + aggs: { + hits: { + top_hits: { + sort: [ + { + timestamp: { + order: 'desc', + unmapped_type: 'long', + }, + }, + ], + _source: { + includes: [ + 'cluster_uuid', + 'ccr_stats.read_exceptions', + 'ccr_stats.shard_id', + 'ccr_stats.leader_index', + ], + }, + size: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + const stats: CCRReadExceptionsStats[] = []; + const { buckets: remoteClusterBuckets = [] } = response.aggregations.remote_clusters; + + if (!remoteClusterBuckets.length) { + return stats; + } + + for (const remoteClusterBucket of remoteClusterBuckets) { + const followerIndicesBuckets = remoteClusterBucket.follower_indices.buckets; + const remoteCluster = remoteClusterBucket.key; + + for (const followerIndexBucket of followerIndicesBuckets) { + const followerIndex = followerIndexBucket.key; + const { + _index: monitoringIndexName, + _source: { ccr_stats: ccrStats, cluster_uuid: clusterUuid }, + } = get(followerIndexBucket, 'hits.hits.hits[0]'); + const { + read_exceptions: readExceptions, + leader_index: leaderIndex, + shard_id: shardId, + } = ccrStats; + const { exception: lastReadException } = readExceptions[readExceptions.length - 1]; + + stats.push({ + clusterUuid, + remoteCluster, + followerIndex, + shardId, + leaderIndex, + lastReadException, + ccs: monitoringIndexName.includes(':') ? monitoringIndexName.split(':')[0] : null, + }); + } + } + return stats; +} From f7ace5e16defed0692424a22faa3b703905f3836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Sat, 19 Dec 2020 01:53:09 +0100 Subject: [PATCH 23/40] [Rollup Jobs] Added autofocus to cron editor (#86324) --- .../public/components/cron_editor/cron_editor.tsx | 2 ++ .../crud_app/sections/job_create/steps/step_logistics.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx index 72e2f51c37e4c4..19af93b67aca03 100644 --- a/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx +++ b/src/plugins/es_ui_shared/public/components/cron_editor/cron_editor.tsx @@ -67,6 +67,7 @@ interface Props { fieldToPreferredValueMap: FieldToValueMap; frequency: Frequency; }) => void; + autoFocus?: boolean; } type State = FieldToValueMap; @@ -234,6 +235,7 @@ export class CronEditor extends Component { fullWidth > ) => diff --git a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index 5e9c2f62ceef8e..bb217fbeed3048 100644 --- a/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -45,8 +45,10 @@ export class StepLogistics extends Component { hasMatchingIndices: PropTypes.bool.isRequired, indexPatternAsyncErrors: PropTypes.array, }; + state = { cronFocus: false }; showAdvancedCron = () => { + this.setState({ cronFocus: true }); const { onFieldsChange } = this.props; onFieldsChange({ @@ -55,6 +57,7 @@ export class StepLogistics extends Component { }; hideAdvancedCron = () => { + this.setState({ cronFocus: true }); const { onFieldsChange, fields } = this.props; const { simpleRollupCron } = fields; @@ -156,6 +159,7 @@ export class StepLogistics extends Component { fullWidth > onFieldsChange({ rollupCron: e.target.value })} isInvalid={Boolean(areStepErrorsVisible && errorRollupCron)} @@ -181,6 +185,7 @@ export class StepLogistics extends Component { return ( Date: Fri, 18 Dec 2020 19:53:41 -0500 Subject: [PATCH 24/40] [Maps] Use Json for mvt-tests (#86492) --- package.json | 2 + .../server/mvt/__tests__/pbf/0_0_0_docs.pbf | Bin 155 -> 0 bytes .../mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf | Bin 91 -> 0 bytes .../mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf | Bin 83 -> 0 bytes .../plugins/maps/server/mvt/get_tile.test.ts | 135 +++++++++++++++--- yarn.lock | 4 +- 6 files changed, 122 insertions(+), 19 deletions(-) delete mode 100644 x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf delete mode 100644 x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf delete mode 100644 x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf diff --git a/package.json b/package.json index b4b3cbe22b715d..8e99b5c693ceca 100644 --- a/package.json +++ b/package.json @@ -381,6 +381,7 @@ "@mapbox/geojson-rewind": "^0.5.0", "@mapbox/mapbox-gl-draw": "^1.2.0", "@mapbox/mapbox-gl-rtl-text": "^0.2.3", + "@mapbox/vector-tile": "1.3.1", "@microsoft/api-documenter": "7.7.2", "@microsoft/api-extractor": "7.7.0", "@octokit/rest": "^16.35.0", @@ -750,6 +751,7 @@ "ora": "^4.0.4", "p-limit": "^3.0.1", "parse-link-header": "^1.0.1", + "pbf": "3.2.1", "pirates": "^4.0.1", "pixelmatch": "^5.1.0", "pkg-up": "^2.0.0", diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_docs.pbf deleted file mode 100644 index 9a9296e2ece3f94ac726f6a83f2958ba2e22074b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 155 zcmb1|!C1k>#Z#PLT9lj`pOaXbTBOmSAfzP3#=yYH$iyVUtR%)cfww_Yse%1Hdjp%m z1GW`x?>R5<@Jq49XXd4(R!A|&XQoIA$H!+U<;BORr6!h?7Nr7(;^URrxL6AEb1Id@ lxJ2B|1A=@b0-e$;E2DHXOfw@hld_a#xuikzR@fx13;_42EcXBa diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_asgrid.pbf deleted file mode 100644 index f2289865b80229bd3c96d44cfba54ef8aab2d16b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 91 zcmb1&tYG5eDb6n~N=}W>NvupQ(r8c+5*K1&U|?jFU{>PgY!GgckYaGKXJD}Bm*Pyx uPmWK{FU>2F;!i9~kIzqw5AhFi^oe&2Q)1ERJY diff --git a/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf b/x-pack/plugins/maps/server/mvt/__tests__/pbf/0_0_0_grid_aspoint.pbf deleted file mode 100644 index 54b0791ccd1361b7d26b2c689235c433e7f1c378..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83 zcmb0NtYG5eDb6n~N=}W>NvupQ(r8c+;um6JU|?jFU{qq|d?VB*A;p=JpB$f@Uz%4U m#h+M~9-p5UAL1Y4=o9Z4ro^JrdzMLwQ*x{OUx}m{A&vm0mKV|h diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts index 1e00fd27e3d1b7..3660039f2513c0 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.test.ts @@ -7,9 +7,26 @@ import { getGridTile, getTile } from './get_tile'; import { TILE_GRIDAGGS, TILE_SEARCHES } from './__tests__/tile_es_responses'; import { Logger } from 'src/core/server'; -import * as path from 'path'; -import * as fs from 'fs'; -import { ES_GEO_FIELD_TYPE, RENDER_AS } from '../../common/constants'; +import { ES_GEO_FIELD_TYPE, MVT_SOURCE_LAYER_NAME, RENDER_AS } from '../../common/constants'; + +// @ts-expect-error +import { VectorTile, VectorTileLayer } from '@mapbox/vector-tile'; +// @ts-expect-error +import Protobuf from 'pbf'; + +interface ITileLayerJsonExpectation { + version: number; + name: string; + extent: number; + length: number; + features: Array<{ + id: string | number | undefined; + type: number; + properties: object; + extent: number; + pointArrays: object; + }>; +} describe('getTile', () => { const mockCallElasticsearch = jest.fn(); @@ -39,7 +56,7 @@ describe('getTile', () => { } }); - const tile = await getTile({ + const pbfTile = await getTile({ x: 0, y: 0, z: 0, @@ -53,7 +70,35 @@ describe('getTile', () => { geoFieldType: ES_GEO_FIELD_TYPE.GEO_SHAPE, }); - compareTiles('./__tests__/pbf/0_0_0_docs.pbf', tile); + const jsonTile = new VectorTile(new Protobuf(pbfTile)); + compareJsonTiles(jsonTile, { + version: 2, + name: 'source_layer', + extent: 4096, + length: 1, + features: [ + { + id: undefined, + type: 3, + properties: { + __kbn__feature_id__: 'poly:G7PRMXQBgyyZ-h5iYibj:0', + _id: 'G7PRMXQBgyyZ-h5iYibj', + _index: 'poly', + }, + extent: 4096, + pointArrays: [ + [ + { x: 840, y: 1600 }, + { x: 1288, y: 1096 }, + { x: 1672, y: 1104 }, + { x: 2104, y: 1508 }, + { x: 1472, y: 2316 }, + { x: 840, y: 1600 }, + ], + ], + }, + ], + }); }); }); @@ -115,22 +160,78 @@ describe('getGridTile', () => { }; test('0.0.0 tile (clusters)', async () => { - const tile = await getGridTile(defaultParams); - compareTiles('./__tests__/pbf/0_0_0_grid_aspoint.pbf', tile); + const pbfTile = await getGridTile(defaultParams); + const jsonTile = new VectorTile(new Protobuf(pbfTile)); + compareJsonTiles(jsonTile, { + version: 2, + name: 'source_layer', + extent: 4096, + length: 1, + features: [ + { + id: undefined, + type: 1, + properties: { + ['avg_of_TOTAL_AV']: 5398920.390458991, + doc_count: 42637, + }, + extent: 4096, + pointArrays: [[{ x: 1206, y: 1539 }]], + }, + ], + }); }); test('0.0.0 tile (grids)', async () => { - const tile = await getGridTile({ ...defaultParams, requestType: RENDER_AS.GRID }); - compareTiles('./__tests__/pbf/0_0_0_grid_asgrid.pbf', tile); + const pbfTile = await getGridTile({ ...defaultParams, requestType: RENDER_AS.GRID }); + const jsonTile = new VectorTile(new Protobuf(pbfTile)); + compareJsonTiles(jsonTile, { + version: 2, + name: 'source_layer', + extent: 4096, + length: 1, + features: [ + { + id: undefined, + type: 3, + properties: { + ['avg_of_TOTAL_AV']: 5398920.390458991, + doc_count: 42637, + }, + extent: 4096, + pointArrays: [ + [ + { x: 1216, y: 1536 }, + { x: 1216, y: 1568 }, + { x: 1184, y: 1568 }, + { x: 1184, y: 1536 }, + { x: 1216, y: 1536 }, + ], + ], + }, + ], + }); }); }); -function compareTiles(expectedRelativePath: string, actualTile: Buffer | null) { - if (actualTile === null) { - throw new Error('Tile should be created'); - } - const expectedPath = path.resolve(__dirname, expectedRelativePath); - const expectedBin = fs.readFileSync(expectedPath, 'binary'); - const expectedTile = Buffer.from(expectedBin, 'binary'); - expect(expectedTile.equals(actualTile)).toBe(true); +/** + * Verifies JSON-representation of tile-contents + * @param actualTileJson + * @param expectedLayer + */ +function compareJsonTiles(actualTileJson: VectorTile, expectedLayer: ITileLayerJsonExpectation) { + const actualLayer: VectorTileLayer = actualTileJson.layers[MVT_SOURCE_LAYER_NAME]; + expect(actualLayer.version).toEqual(expectedLayer.version); + expect(actualLayer.extent).toEqual(expectedLayer.extent); + expect(actualLayer.name).toEqual(expectedLayer.name); + expect(actualLayer.length).toEqual(expectedLayer.features.length); + + expectedLayer.features.forEach((expectedFeature, index) => { + const actualFeature = actualLayer.feature(index); + expect(actualFeature.type).toEqual(expectedFeature.type); + expect(actualFeature.extent).toEqual(expectedFeature.extent); + expect(actualFeature.id).toEqual(expectedFeature.id); + expect(actualFeature.properties).toEqual(expectedFeature.properties); + expect(actualFeature.loadGeometry()).toEqual(expectedFeature.pointArrays); + }); } diff --git a/yarn.lock b/yarn.lock index 4dbfa610be6c39..25cbe964c19e15 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2998,7 +2998,7 @@ resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e" integrity sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4= -"@mapbox/vector-tile@^1.3.1": +"@mapbox/vector-tile@1.3.1", "@mapbox/vector-tile@^1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666" integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw== @@ -21931,7 +21931,7 @@ pathval@^1.1.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= -pbf@^3.0.5, pbf@^3.2.1: +pbf@3.2.1, pbf@^3.0.5, pbf@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a" integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ== From 8dc86c5f4b720c1eaa9cee26aaad7e6389f46df1 Mon Sep 17 00:00:00 2001 From: Brian Seeders Date: Fri, 18 Dec 2020 20:00:51 -0500 Subject: [PATCH 25/40] [CI] TeamCity updates (#85843) --- .ci/teamcity/bootstrap.sh | 2 +- .ci/teamcity/checks/bundle_limits.sh | 3 +- .ci/teamcity/checks/commit.sh | 13 +++++++ .ci/teamcity/checks/commit_check_runner.sh | 9 +++++ .ci/teamcity/checks/jest_configs.sh | 8 ++++ .../checks/plugins_with_circular_deps.sh | 8 ++++ .ci/teamcity/oss/plugin_functional.sh | 21 ++++++++-- .ci/teamcity/setup_env.sh | 3 ++ .teamcity/pom.xml | 17 +++++++++ .teamcity/settings.kts | 2 +- .teamcity/src/Agents.kt | 28 ++++++++++++++ .teamcity/src/Extensions.kt | 38 +------------------ .teamcity/src/builds/Checks.kt | 6 ++- .../src/builds/default/DefaultCiGroup.kt | 4 ++ .../src/builds/default/DefaultCiGroups.kt | 2 +- .teamcity/src/builds/es_snapshots/Verify.kt | 4 +- .../OssApiServerIntegration.kt} | 8 +--- .teamcity/src/builds/test/AllTests.kt | 3 +- .teamcity/src/builds/test/QuickTests.kt | 2 +- .teamcity/src/projects/Kibana.kt | 38 +++---------------- .teamcity/src/templates/DefaultTemplate.kt | 7 ++-- .teamcity/src/templates/KibanaTemplate.kt | 9 ++--- .teamcity/tests/projects/KibanaTest.kt | 5 ++- 23 files changed, 143 insertions(+), 97 deletions(-) create mode 100755 .ci/teamcity/checks/commit.sh create mode 100755 .ci/teamcity/checks/commit_check_runner.sh create mode 100755 .ci/teamcity/checks/jest_configs.sh create mode 100755 .ci/teamcity/checks/plugins_with_circular_deps.sh create mode 100644 .teamcity/src/Agents.kt rename .teamcity/src/builds/{test/ApiServerIntegration.kt => oss/OssApiServerIntegration.kt} (62%) diff --git a/.ci/teamcity/bootstrap.sh b/.ci/teamcity/bootstrap.sh index adb884ca064ba5..fc57811bb20771 100755 --- a/.ci/teamcity/bootstrap.sh +++ b/.ci/teamcity/bootstrap.sh @@ -7,7 +7,7 @@ source "$(dirname "${0}")/util.sh" tc_start_block "Bootstrap" tc_start_block "yarn install and kbn bootstrap" -verify_no_git_changes yarn kbn bootstrap --prefer-offline +verify_no_git_changes yarn kbn bootstrap tc_end_block "yarn install and kbn bootstrap" tc_start_block "build kbn-pm" diff --git a/.ci/teamcity/checks/bundle_limits.sh b/.ci/teamcity/checks/bundle_limits.sh index 3f7daef6d04731..751ec5a03ee7bc 100755 --- a/.ci/teamcity/checks/bundle_limits.sh +++ b/.ci/teamcity/checks/bundle_limits.sh @@ -4,4 +4,5 @@ set -euo pipefail source "$(dirname "${0}")/../util.sh" -node scripts/build_kibana_platform_plugins --validate-limits +checks-reporter-with-killswitch "Check Bundle Limits" \ + node scripts/build_kibana_platform_plugins --validate-limits diff --git a/.ci/teamcity/checks/commit.sh b/.ci/teamcity/checks/commit.sh new file mode 100755 index 00000000000000..387ec0c1267858 --- /dev/null +++ b/.ci/teamcity/checks/commit.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +# Runs pre-commit hook script for the files touched in the last commit. +# That way we can ensure a set of quick commit checks earlier as we removed +# the pre-commit hook installation by default. +# If files are more than 200 we will skip it and just use +# the further ci steps that already check linting and file casing for the entire repo. +checks-reporter-with-killswitch "Quick commit checks" \ + "$(dirname "${0}")/commit_check_runner.sh" diff --git a/.ci/teamcity/checks/commit_check_runner.sh b/.ci/teamcity/checks/commit_check_runner.sh new file mode 100755 index 00000000000000..f2a4a205682154 --- /dev/null +++ b/.ci/teamcity/checks/commit_check_runner.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +echo "!!!!!!!! ATTENTION !!!!!!!! +That check is intended to provide earlier CI feedback after we remove the automatic install for the local pre-commit hook. +If you want, you can still manually install the pre-commit hook locally by running 'node scripts/register_git_hook locally' +!!!!!!!!!!!!!!!!!!!!!!!!!!! +" + +node scripts/precommit_hook.js --ref HEAD~1..HEAD --max-files 200 --verbose diff --git a/.ci/teamcity/checks/jest_configs.sh b/.ci/teamcity/checks/jest_configs.sh new file mode 100755 index 00000000000000..6703ffffb56515 --- /dev/null +++ b/.ci/teamcity/checks/jest_configs.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +checks-reporter-with-killswitch "Check Jest Configs" \ + node scripts/check_jest_configs diff --git a/.ci/teamcity/checks/plugins_with_circular_deps.sh b/.ci/teamcity/checks/plugins_with_circular_deps.sh new file mode 100755 index 00000000000000..5acc4b2ae351b0 --- /dev/null +++ b/.ci/teamcity/checks/plugins_with_circular_deps.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +source "$(dirname "${0}")/../util.sh" + +checks-reporter-with-killswitch "Check Plugins With Circular Dependencies" \ + node scripts/find_plugins_with_circular_deps diff --git a/.ci/teamcity/oss/plugin_functional.sh b/.ci/teamcity/oss/plugin_functional.sh index 5d1ecbcbd48ee4..3570bf01e49c49 100755 --- a/.ci/teamcity/oss/plugin_functional.sh +++ b/.ci/teamcity/oss/plugin_functional.sh @@ -13,6 +13,21 @@ if [[ ! -d "target" ]]; then fi cd - -./test/scripts/test/plugin_functional.sh -./test/scripts/test/example_functional.sh -./test/scripts/test/interpreter_functional.sh +checks-reporter-with-killswitch "Plugin Functional Tests" \ + node scripts/functional_tests \ + --config test/plugin_functional/config.ts \ + --bail \ + --debug + +checks-reporter-with-killswitch "Example Functional Tests" \ + node scripts/functional_tests \ + --config test/examples/config.js \ + --bail \ + --debug + +checks-reporter-with-killswitch "Interpreter Functional Tests" \ + node scripts/functional_tests \ + --config test/interpreter_functional/config.ts \ + --bail \ + --debug \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" diff --git a/.ci/teamcity/setup_env.sh b/.ci/teamcity/setup_env.sh index f662d36247a2fd..982d129dae2a6d 100755 --- a/.ci/teamcity/setup_env.sh +++ b/.ci/teamcity/setup_env.sh @@ -25,12 +25,14 @@ tc_set_env FORCE_COLOR 1 tc_set_env TEST_BROWSER_HEADLESS 1 tc_set_env ELASTIC_APM_ENVIRONMENT ci +tc_set_env ELASTIC_APM_TRANSACTION_SAMPLE_RATE 0.1 if [[ "${KIBANA_CI_REPORTER_KEY_BASE64-}" ]]; then tc_set_env KIBANA_CI_REPORTER_KEY "$(echo "$KIBANA_CI_REPORTER_KEY_BASE64" | base64 -d)" fi if is_pr; then + tc_set_env ELASTIC_APM_ACTIVE false tc_set_env CHECKS_REPORTER_ACTIVE true # These can be removed once we're not supporting Jenkins and TeamCity at the same time @@ -39,6 +41,7 @@ if is_pr; then tc_set_env ghprbActualCommit "$GITHUB_PR_TRIGGERED_SHA" tc_set_env BUILD_URL "$TEAMCITY_BUILD_URL" else + tc_set_env ELASTIC_APM_ACTIVE true tc_set_env CHECKS_REPORTER_ACTIVE false fi diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml index 5fa068d0a92e03..e6ec1f1c043c25 100644 --- a/.teamcity/pom.xml +++ b/.teamcity/pom.xml @@ -46,6 +46,14 @@ true + + teamcity + https://artifactory.elstc.co/artifactory/teamcity + + true + always + + @@ -53,6 +61,10 @@ JetBrains https://download.jetbrains.com/teamcity-repository + + teamcity + https://artifactory.elstc.co/artifactory/teamcity + @@ -124,5 +136,10 @@ junit 4.13 + + co.elastic.teamcity + teamcity-common + 1.0.0-SNAPSHOT + diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts index ec1b1c6eb94ef6..28108d019327b6 100644 --- a/.teamcity/settings.kts +++ b/.teamcity/settings.kts @@ -2,7 +2,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.* import projects.Kibana import projects.KibanaConfiguration -version = "2020.1" +version = "2020.2" val config = KibanaConfiguration { agentNetwork = DslContext.getParameter("agentNetwork", "teamcity") diff --git a/.teamcity/src/Agents.kt b/.teamcity/src/Agents.kt new file mode 100644 index 00000000000000..557cce80d0f551 --- /dev/null +++ b/.teamcity/src/Agents.kt @@ -0,0 +1,28 @@ +import co.elastic.teamcity.common.GoogleCloudAgent +import co.elastic.teamcity.common.GoogleCloudAgentDiskType +import co.elastic.teamcity.common.GoogleCloudProfile + +private val sizes = listOf("2", "4", "8", "16") + +val StandardAgents = sizes.map { size -> size to GoogleCloudAgent { + sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" + agentPrefix = "kibana-standard-$size-" + machineType = "n2-standard-$size" + diskSizeGb = 75 + diskType = GoogleCloudAgentDiskType.SSD +} }.toMap() + +val BuildAgent = GoogleCloudAgent { + sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" + agentPrefix = "kibana-c2-16-" + machineType = "c2-standard-16" + diskSizeGb = 250 + diskType = GoogleCloudAgentDiskType.SSD +} + +val CloudProfile = GoogleCloudProfile { + accessKeyId = "447fdd4d-7129-46b7-9822-2e57658c7422" + + agents(StandardAgents) + agent(BuildAgent) +} diff --git a/.teamcity/src/Extensions.kt b/.teamcity/src/Extensions.kt index 120b333d43e724..2942a6385f13f4 100644 --- a/.teamcity/src/Extensions.kt +++ b/.teamcity/src/Extensions.kt @@ -1,9 +1,7 @@ +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.notifications import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import jetbrains.buildServer.configs.kotlin.v2019_2.ui.insert -import projects.kibanaConfiguration fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { feature { @@ -13,40 +11,8 @@ fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { } } -fun ProjectFeatures.kibanaAgent(init: ProjectFeature.() -> Unit) { - feature { - type = "CloudImage" - param("network", kibanaConfiguration.agentNetwork) - param("subnet", kibanaConfiguration.agentSubnet) - param("growingId", "true") - param("agent_pool_id", "-2") - param("preemptible", "false") - param("sourceProject", "elastic-images-prod") - param("sourceImageFamily", "elastic-kibana-ci-ubuntu-1804-lts") - param("zone", "us-central1-a") - param("profileId", "kibana") - param("diskType", "pd-ssd") - param("machineCustom", "false") - param("maxInstances", "200") - param("imageType", "ImageFamily") - param("diskSizeGb", "75") // TODO - init() - } -} - -fun ProjectFeatures.kibanaAgent(size: String, init: ProjectFeature.() -> Unit = {}) { - kibanaAgent { - id = "KIBANA_STANDARD_$size" - param("source-id", "kibana-standard-$size-") - param("machineType", "n2-standard-$size") - init() - } -} - fun BuildType.kibanaAgent(size: String) { - requirements { - startsWith("teamcity.agent.name", "kibana-standard-$size-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents[size]!!) } fun BuildType.kibanaAgent(size: Int) { diff --git a/.teamcity/src/builds/Checks.kt b/.teamcity/src/builds/Checks.kt index 1228ea4d94f4c9..37336316c4c914 100644 --- a/.teamcity/src/builds/Checks.kt +++ b/.teamcity/src/builds/Checks.kt @@ -11,16 +11,18 @@ object Checks : BuildType({ kibanaAgent(4) val checkScripts = mapOf( + "Quick Commit Checks" to ".ci/teamcity/checks/commit.sh", "Check Telemetry Schema" to ".ci/teamcity/checks/telemetry.sh", "Check TypeScript Projects" to ".ci/teamcity/checks/ts_projects.sh", "Check File Casing" to ".ci/teamcity/checks/file_casing.sh", "Check Licenses" to ".ci/teamcity/checks/licenses.sh", "Verify NOTICE" to ".ci/teamcity/checks/verify_notice.sh", - "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", "Check Types" to ".ci/teamcity/checks/type_check.sh", + "Check Jest Configs" to ".ci/teamcity/checks/jest_configs.sh", "Check Doc API Changes" to ".ci/teamcity/checks/doc_api_changes.sh", "Check Bundle Limits" to ".ci/teamcity/checks/bundle_limits.sh", - "Check i18n" to ".ci/teamcity/checks/i18n.sh" + "Check i18n" to ".ci/teamcity/checks/i18n.sh", + "Check Plugins With Circular Dependencies" to ".ci/teamcity/checks/plugins_with_circular_deps.sh" ) steps { diff --git a/.teamcity/src/builds/default/DefaultCiGroup.kt b/.teamcity/src/builds/default/DefaultCiGroup.kt index 7dbe9cd0ba84c4..2c3b0d348591e3 100755 --- a/.teamcity/src/builds/default/DefaultCiGroup.kt +++ b/.teamcity/src/builds/default/DefaultCiGroup.kt @@ -1,5 +1,7 @@ package builds.default +import StandardAgents +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.* import runbld @@ -11,5 +13,7 @@ class DefaultCiGroup(val ciGroup: Int = 0, init: BuildType.() -> Unit = {}) : De runbld("Default CI Group $ciGroup", "./.ci/teamcity/default/ci_group.sh $ciGroup") } + requireAgent(StandardAgents["4"]!!) + init() }) diff --git a/.teamcity/src/builds/default/DefaultCiGroups.kt b/.teamcity/src/builds/default/DefaultCiGroups.kt index 6f1d45598c92eb..4f39283149e73b 100644 --- a/.teamcity/src/builds/default/DefaultCiGroups.kt +++ b/.teamcity/src/builds/default/DefaultCiGroups.kt @@ -3,7 +3,7 @@ package builds.default import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -const val DEFAULT_CI_GROUP_COUNT = 10 +const val DEFAULT_CI_GROUP_COUNT = 11 val defaultCiGroups = (1..DEFAULT_CI_GROUP_COUNT).map { DefaultCiGroup(it) } object DefaultCiGroups : BuildType({ diff --git a/.teamcity/src/builds/es_snapshots/Verify.kt b/.teamcity/src/builds/es_snapshots/Verify.kt index c778814af536c4..4c0307e9eca550 100644 --- a/.teamcity/src/builds/es_snapshots/Verify.kt +++ b/.teamcity/src/builds/es_snapshots/Verify.kt @@ -6,7 +6,7 @@ import builds.default.defaultCiGroups import builds.oss.OssBuild import builds.oss.OssPluginFunctional import builds.oss.ossCiGroups -import builds.test.ApiServerIntegration +import builds.oss.OssApiServerIntegration import builds.test.JestIntegration import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.* @@ -49,7 +49,7 @@ val defaultBuildsToClone = listOf( val defaultCloned = defaultBuildsToClone.map { cloneForVerify(it) } val integrationsBuildsToClone = listOf( - ApiServerIntegration, + OssApiServerIntegration, JestIntegration ) diff --git a/.teamcity/src/builds/test/ApiServerIntegration.kt b/.teamcity/src/builds/oss/OssApiServerIntegration.kt similarity index 62% rename from .teamcity/src/builds/test/ApiServerIntegration.kt rename to .teamcity/src/builds/oss/OssApiServerIntegration.kt index ca58b628cbd221..a04512fb2aba52 100644 --- a/.teamcity/src/builds/test/ApiServerIntegration.kt +++ b/.teamcity/src/builds/oss/OssApiServerIntegration.kt @@ -1,10 +1,8 @@ -package builds.test +package builds.oss -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType import runbld -object ApiServerIntegration : BuildType({ +object OssApiServerIntegration : OssFunctionalBase({ name = "API/Server Integration" description = "Executes API and Server Integration Tests" @@ -12,6 +10,4 @@ object ApiServerIntegration : BuildType({ runbld("API Integration", "./.ci/teamcity/oss/api_integration.sh") runbld("Server Integration", "./.ci/teamcity/oss/server_integration.sh") } - - addTestSettings() }) diff --git a/.teamcity/src/builds/test/AllTests.kt b/.teamcity/src/builds/test/AllTests.kt index d1b5898d1a5f5e..9506d98cbe50ee 100644 --- a/.teamcity/src/builds/test/AllTests.kt +++ b/.teamcity/src/builds/test/AllTests.kt @@ -1,5 +1,6 @@ package builds.test +import builds.oss.OssApiServerIntegration import dependsOn import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType @@ -8,5 +9,5 @@ object AllTests : BuildType({ description = "All Non-Functional Tests" type = Type.COMPOSITE - dependsOn(QuickTests, Jest, XPackJest, JestIntegration, ApiServerIntegration) + dependsOn(QuickTests, Jest, XPackJest, JestIntegration, OssApiServerIntegration) }) diff --git a/.teamcity/src/builds/test/QuickTests.kt b/.teamcity/src/builds/test/QuickTests.kt index 5b1d2541480ad3..a294fce9599c36 100644 --- a/.teamcity/src/builds/test/QuickTests.kt +++ b/.teamcity/src/builds/test/QuickTests.kt @@ -12,7 +12,7 @@ object QuickTests : BuildType({ kibanaAgent(2) val testScripts = mapOf( - "Test Hardening" to ".ci/teamcity/checkes/test_hardening.sh", + "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", "Test Projects" to ".ci/teamcity/tests/test_projects.sh", "Mocha Tests" to ".ci/teamcity/tests/mocha.sh" ) diff --git a/.teamcity/src/projects/Kibana.kt b/.teamcity/src/projects/Kibana.kt index 20c30eedf5b91d..1878f49debe8c1 100644 --- a/.teamcity/src/projects/Kibana.kt +++ b/.teamcity/src/projects/Kibana.kt @@ -5,9 +5,10 @@ import builds.* import builds.default.* import builds.oss.* import builds.test.* +import CloudProfile +import co.elastic.teamcity.common.googleCloudProfile import jetbrains.buildServer.configs.kotlin.v2019_2.* import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.slackConnection -import kibanaAgent import templates.KibanaTemplate import templates.DefaultTemplate import vcs.Elasticsearch @@ -31,7 +32,7 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { param("teamcity.ui.settings.readOnly", "true") // https://github.com/JetBrains/teamcity-webhooks - param("teamcity.internal.webhooks.enable", "true") + param("teamcity.internal.webhooks.enable", "false") param("teamcity.internal.webhooks.events", "BUILD_STARTED;BUILD_FINISHED;BUILD_INTERRUPTED;CHANGES_LOADED;BUILD_TYPE_ADDED_TO_QUEUE;BUILD_PROBLEMS_CHANGED") param("teamcity.internal.webhooks.url", "https://ci-stats.kibana.dev/_teamcity_webhook") param("teamcity.internal.webhooks.username", "automation") @@ -46,36 +47,9 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { defaultTemplate = DefaultTemplate - features { - val sizes = listOf("2", "4", "8", "16") - for (size in sizes) { - kibanaAgent(size) - } - - kibanaAgent { - id = "KIBANA_C2_16" - param("source-id", "kibana-c2-16-") - param("machineType", "c2-standard-16") - } - - feature { - id = "kibana" - type = "CloudProfile" - param("agentPushPreset", "") - param("profileId", "kibana") - param("profileServerUrl", "") - param("name", "kibana") - param("total-work-time", "") - param("credentialsType", "key") - param("description", "") - param("next-hour", "") - param("cloud-code", "google") - param("terminate-after-build", "true") - param("terminate-idle-time", "30") - param("enabled", "true") - param("secure:accessKey", "credentialsJSON:447fdd4d-7129-46b7-9822-2e57658c7422") - } + googleCloudProfile(CloudProfile) + features { slackConnection { id = "KIBANA_SLACK" displayName = "Kibana Slack" @@ -106,7 +80,6 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { buildType(JestIntegration) } - buildType(ApiServerIntegration) buildType(QuickTests) buildType(AllTests) } @@ -125,6 +98,7 @@ fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { buildType(OssFirefox) buildType(OssAccessibility) buildType(OssPluginFunctional) + buildType(OssApiServerIntegration) subProject { id("CIGroups") diff --git a/.teamcity/src/templates/DefaultTemplate.kt b/.teamcity/src/templates/DefaultTemplate.kt index 762218b72ab107..1f7f364600e212 100644 --- a/.teamcity/src/templates/DefaultTemplate.kt +++ b/.teamcity/src/templates/DefaultTemplate.kt @@ -1,15 +1,14 @@ package templates +import StandardAgents +import co.elastic.teamcity.common.requireAgent import jetbrains.buildServer.configs.kotlin.v2019_2.Template import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon object DefaultTemplate : Template({ name = "Default Template" - requirements { - equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") - startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents["2"]!!) params { param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out diff --git a/.teamcity/src/templates/KibanaTemplate.kt b/.teamcity/src/templates/KibanaTemplate.kt index 117c30ddb86e31..83fe4fdaa1edd1 100644 --- a/.teamcity/src/templates/KibanaTemplate.kt +++ b/.teamcity/src/templates/KibanaTemplate.kt @@ -1,5 +1,7 @@ package templates +import StandardAgents +import co.elastic.teamcity.common.requireAgent import vcs.Kibana import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay @@ -21,10 +23,7 @@ object KibanaTemplate : Template({ // checkoutDir = "/dev/shm/%system.teamcity.buildType.id%/%system.build.number%/kibana" } - requirements { - equals("system.cloud.profile_id", "kibana", "RQ_CLOUD_PROFILE_ID") - startsWith("teamcity.agent.name", "kibana-standard-2-", "RQ_AGENT_NAME") - } + requireAgent(StandardAgents["2"]!!) features { perfmon { } @@ -41,7 +40,7 @@ object KibanaTemplate : Template({ } failureConditions { - executionTimeoutMin = 120 + executionTimeoutMin = 160 testFailure = false } diff --git a/.teamcity/tests/projects/KibanaTest.kt b/.teamcity/tests/projects/KibanaTest.kt index 677effec5be658..311c15a1da7cbd 100644 --- a/.teamcity/tests/projects/KibanaTest.kt +++ b/.teamcity/tests/projects/KibanaTest.kt @@ -1,5 +1,7 @@ package projects +import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext import org.junit.Assert.* import org.junit.Test @@ -18,10 +20,11 @@ class KibanaTest { @Test fun test_CloudImages_Exist() { + DslContext.projectId = AbsoluteId("My Project") val project = Kibana(TestConfig) assertTrue(project.features.items.any { - it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "network"} + it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "teamcity" } }) } } From 7e9177d3a9e0da3c812de08f11d673af99334451 Mon Sep 17 00:00:00 2001 From: Nick Partridge Date: Fri, 18 Dec 2020 19:07:00 -0600 Subject: [PATCH 26/40] Rename chartLibrary setting to legacyChartsLibrary (#86529) * rename chartLibrary setting to legacyChartsLibrary * fix spelling * fix plugin setting check boolean --- docs/management/advanced-options.asciidoc | 4 ++-- src/plugins/vis_type_vislib/public/plugin.ts | 4 ++-- src/plugins/vis_type_xy/common/index.ts | 2 +- .../public/components/split_chart_warning.tsx | 4 ++-- src/plugins/vis_type_xy/public/plugin.ts | 4 ++-- .../public/vis_types/split_tooltip.tsx | 5 +---- src/plugins/vis_type_xy/server/plugin.ts | 14 +++++++------- .../apps/dashboard/dashboard_state.ts | 14 +++++++------- test/functional/apps/dashboard/index.ts | 4 ++-- .../apps/getting_started/_shakespeare.ts | 8 ++++---- test/functional/apps/getting_started/index.ts | 6 +++--- test/functional/apps/visualize/index.ts | 6 +++--- .../page_objects/visualize_chart_page.ts | 17 +++++++++-------- .../page_objects/visualize_editor_page.ts | 2 +- test/functional/page_objects/visualize_page.ts | 2 +- 15 files changed, 47 insertions(+), 49 deletions(-) diff --git a/docs/management/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 9a87d4c9d886ac..99fadb240335a5 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -453,8 +453,8 @@ of buckets to try to represent. ==== Visualization [horizontal] -[[visualization-visualize-chartslibrary]]`visualization:visualize:chartsLibrary`:: -Enables the new charts library for area, line, and bar charts in visualization panels. Does *not* support the split chart aggregation. +[[visualization-visualize-chartslibrary]]`visualization:visualize:legacyChartsLibrary`:: +Enables legacy charts library for area, line and bar charts in visualize. Currently, only legacy charts library supports split chart aggregation. [[visualization-colormapping]]`visualization:colorMapping`:: **This setting is deprecated and will not be supported as of 8.0.** diff --git a/src/plugins/vis_type_vislib/public/plugin.ts b/src/plugins/vis_type_vislib/public/plugin.ts index bef8ad26fb12c9..36a184d3da5072 100644 --- a/src/plugins/vis_type_vislib/public/plugin.ts +++ b/src/plugins/vis_type_vislib/public/plugin.ts @@ -24,7 +24,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { DataPublicPluginStart } from '../../data/public'; import { KibanaLegacyStart } from '../../kibana_legacy/public'; -import { CHARTS_LIBRARY } from '../../vis_type_xy/public'; +import { LEGACY_CHARTS_LIBRARY } from '../../vis_type_xy/public'; import { createVisTypeVislibVisFn } from './vis_type_vislib_vis_fn'; import { createPieVisFn } from './pie_fn'; @@ -61,7 +61,7 @@ export class VisTypeVislibPlugin core: VisTypeVislibCoreSetup, { expressions, visualizations, charts }: VisTypeVislibPluginSetupDependencies ) { - if (core.uiSettings.get(CHARTS_LIBRARY)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { // Register only non-replaced vis types convertedTypeDefinitions.forEach(visualizations.createBaseVisualization); visualizations.createBaseVisualization(pieVisTypeDefinition); diff --git a/src/plugins/vis_type_xy/common/index.ts b/src/plugins/vis_type_xy/common/index.ts index bf498229a1b549..edee1ea3219db6 100644 --- a/src/plugins/vis_type_xy/common/index.ts +++ b/src/plugins/vis_type_xy/common/index.ts @@ -34,4 +34,4 @@ export type ChartType = $Values; */ export type XyVisType = ChartType | 'horizontal_bar'; -export const CHARTS_LIBRARY = 'visualization:visualize:chartsLibrary'; +export const LEGACY_CHARTS_LIBRARY = 'visualization:visualize:legacyChartsLibrary'; diff --git a/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx b/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx index a9aa2bf24601b5..7265e70a791a3c 100644 --- a/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx +++ b/src/plugins/vis_type_xy/public/components/split_chart_warning.tsx @@ -38,13 +38,13 @@ export const SplitChartWarning: FC = () => { >
    ), diff --git a/src/plugins/vis_type_xy/public/plugin.ts b/src/plugins/vis_type_xy/public/plugin.ts index c8812b07e59494..7425c5f7248aca 100644 --- a/src/plugins/vis_type_xy/public/plugin.ts +++ b/src/plugins/vis_type_xy/public/plugin.ts @@ -34,7 +34,7 @@ import { setDocLinks, } from './services'; import { visTypesDefinitions } from './vis_types'; -import { CHARTS_LIBRARY } from '../common'; +import { LEGACY_CHARTS_LIBRARY } from '../common'; import { xyVisRenderer } from './vis_renderer'; // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -71,7 +71,7 @@ export class VisTypeXyPlugin core: VisTypeXyCoreSetup, { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies ) { - if (core.uiSettings.get(CHARTS_LIBRARY, false)) { + if (!core.uiSettings.get(LEGACY_CHARTS_LIBRARY, true)) { setUISettings(core.uiSettings); setThemeService(charts.theme); setColorsService(charts.legacyColors); diff --git a/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx b/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx index 2abe3e9b8cf712..84f1fd9187f4f7 100644 --- a/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx +++ b/src/plugins/vis_type_xy/public/vis_types/split_tooltip.tsx @@ -25,10 +25,7 @@ export function SplitTooltip() { return ( charts library, - }} + defaultMessage="Split chart aggregation is not supported with the new charts library. Please enable the legacy charts library advanced setting to use split chart aggregation." /> ); } diff --git a/src/plugins/vis_type_xy/server/plugin.ts b/src/plugins/vis_type_xy/server/plugin.ts index 51d741f9374fe7..b5999535064aaa 100644 --- a/src/plugins/vis_type_xy/server/plugin.ts +++ b/src/plugins/vis_type_xy/server/plugin.ts @@ -22,21 +22,21 @@ import { schema } from '@kbn/config-schema'; import { CoreSetup, Plugin, UiSettingsParams } from 'kibana/server'; -import { CHARTS_LIBRARY } from '../common'; +import { LEGACY_CHARTS_LIBRARY } from '../common'; export const uiSettingsConfig: Record> = { // TODO: Remove this when vis_type_vislib is removed // https://github.com/elastic/kibana/issues/56143 - [CHARTS_LIBRARY]: { - name: i18n.translate('visTypeXy.advancedSettings.visualization.chartsLibrary', { - defaultMessage: 'Charts library', + [LEGACY_CHARTS_LIBRARY]: { + name: i18n.translate('visTypeXy.advancedSettings.visualization.legacyChartsLibrary.name', { + defaultMessage: 'Legacy charts library', }), - value: false, + value: true, description: i18n.translate( - 'visTypeXy.advancedSettings.visualization.chartsLibrary.description', + 'visTypeXy.advancedSettings.visualization.legacyChartsLibrary.description', { defaultMessage: - 'Enables new charts library for areas, lines and bars in visualize. Currently, does not support split chart aggregation.', + 'Enables legacy charts library for area, line and bar charts in visualize. Currently, only legacy charts library supports split chart aggregation.', } ), category: ['visualization'], diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts index 48fec4efee5db2..728787543927c7 100644 --- a/test/functional/apps/dashboard/dashboard_state.ts +++ b/test/functional/apps/dashboard/dashboard_state.ts @@ -46,16 +46,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dashboard state', function describeIndexTests() { // Used to track flag before and after reset - let isNewChartUiEnabled = false; + let isNewChartsLibraryEnabled = false; before(async function () { - isNewChartUiEnabled = await PageObjects.visChart.isNewChartUiEnabled(); + isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await PageObjects.dashboard.initTests(); await PageObjects.dashboard.preserveCrossAppState(); - if (isNewChartUiEnabled) { + if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); } @@ -73,12 +73,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const visName = await PageObjects.visChart.getExpectedValue( AREA_CHART_VIS_NAME, - `${AREA_CHART_VIS_NAME} - chartsLibrary` + `${AREA_CHART_VIS_NAME} - new charts library` ); await dashboardAddPanel.addVisualization(visName); const dashboarName = await PageObjects.visChart.getExpectedValue( 'Overridden colors', - 'Overridden colors - chartsLibrary' + 'Overridden colors - new charts library' ); await PageObjects.dashboard.saveDashboard(dashboarName); @@ -93,7 +93,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.loadSavedDashboard(dashboarName); - if (await PageObjects.visChart.isNewChartUiEnabled()) { + if (await PageObjects.visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); await queryBar.submitQuery(); } diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index f2dba4785ea05b..6fb5f874022a03 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -126,7 +126,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { before(async () => { await loadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); @@ -134,7 +134,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { after(async () => { await unloadLogstash(); await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index fa7d932aca1e86..09fdff9977b5ed 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -49,22 +49,22 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // order they are added. let aggIndex = 1; // Used to track flag before and after reset - let isNewChartUiEnabled = false; + let isNewChartsLibraryEnabled = false; before(async function () { log.debug( 'Load empty_kibana and Shakespeare Getting Started data\n' + 'https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html' ); - isNewChartUiEnabled = await PageObjects.visChart.isNewChartUiEnabled(); + isNewChartsLibraryEnabled = await PageObjects.visChart.isNewChartsLibraryEnabled(); await security.testUser.setRoles(['kibana_admin', 'test_shakespeare_reader']); await esArchiver.load('empty_kibana', { skipExisting: true }); log.debug('Load shakespeare data'); await esArchiver.loadIfNeeded('getting_started/shakespeare'); - if (isNewChartUiEnabled) { + if (isNewChartsLibraryEnabled) { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); } diff --git a/test/functional/apps/getting_started/index.ts b/test/functional/apps/getting_started/index.ts index 4cef16a47571ef..b832c797adac60 100644 --- a/test/functional/apps/getting_started/index.ts +++ b/test/functional/apps/getting_started/index.ts @@ -31,17 +31,17 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); // TODO: Remove when vislib is removed - describe('chartsLibrary', function () { + describe('new charts library', function () { before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index ad69ad5ab41cd2..94b0c5b6c8a277 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -43,19 +43,19 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); // TODO: Remove when vislib is removed - describe('chartsLibrary', function () { + describe('new charts library', function () { this.tags('ciGroup7'); before(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': true, + 'visualization:visualize:legacyChartsLibrary': false, }); await browser.refresh(); }); after(async () => { await kibanaServer.uiSettings.update({ - 'visualization:visualize:chartsLibrary': false, + 'visualization:visualize:legacyChartsLibrary': true, }); await browser.refresh(); }); diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts index 2ad7c77a58ca94..41af5a4c47e783 100644 --- a/test/functional/page_objects/visualize_chart_page.ts +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -41,22 +41,23 @@ export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrPr } /** - * Is chartsLibrary advanced setting enabled + * Is new charts library advanced setting enabled */ - public async isNewChartUiEnabled(): Promise { - const enabled = - Boolean(await kibanaServer.uiSettings.get('visualization:visualize:chartsLibrary')) ?? - false; - log.debug(`-- isNewChartUiEnabled = ${enabled}`); + public async isNewChartsLibraryEnabled(): Promise { + const legacyChartsLibrary = + Boolean(await kibanaServer.uiSettings.get('visualization:visualize:legacyChartsLibrary')) ?? + true; + const enabled = !legacyChartsLibrary; + log.debug(`-- isNewChartsLibraryEnabled = ${enabled}`); return enabled; } /** - * Is chartsLibrary enabled and an area, line or histogram chart is available + * Is new charts library enabled and an area, line or histogram chart exists */ private async isVisTypeXYChart(): Promise { - const enabled = await this.isNewChartUiEnabled(); + const enabled = await this.isNewChartsLibraryEnabled(); if (!enabled) { log.debug(`-- isVisTypeXYChart = false`); diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts index d6134883332e3b..18573c5084618c 100644 --- a/test/functional/page_objects/visualize_editor_page.ts +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -74,7 +74,7 @@ export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrP } public async clickGo() { - if (await visChart.isNewChartUiEnabled()) { + if (await visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); } diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts index e5bd6a0f10d825..d8329f492fe808 100644 --- a/test/functional/page_objects/visualize_page.ts +++ b/test/functional/page_objects/visualize_page.ts @@ -99,7 +99,7 @@ export function VisualizePageProvider({ getService, getPageObjects }: FtrProvide } public async clickRefresh() { - if (await visChart.isNewChartUiEnabled()) { + if (await visChart.isNewChartsLibraryEnabled()) { await elasticChart.setNewChartUiDebugFlag(); } await queryBar.clickQuerySubmitButton(); From 9a3e2910a33527f74a2add9145ec598865fe19cc Mon Sep 17 00:00:00 2001 From: Luke Elmers Date: Fri, 18 Dec 2020 18:34:07 -0700 Subject: [PATCH 27/40] App Services: Remove remaining uiActions, expressions, data, embeddable circular dependencies. (#82791) * Move applyFilter, selectRange, valueClick triggers to data/embeddables. * Update imports. * Remove embeddable references to non-existent data plugin dependency. * remove data mocks from embeddable * Remove query, filters, timeRange from EmbeddableInput and move to apps. * Remove data plugin imports from embeddable test samples. * Remove circular dependencies caused by expressions renderer handlers. * Update circular deps allowList. * Remove data dependency on embeddable. * Revert accidental data plugin change. * Fix new circular deps issues. * Update generated docs. * Fix type errors in vis_type_xy * Fix inspector data table. --- ...lugins-data-public.apply_filter_trigger.md | 11 +++ ...plyglobalfilteractioncontext.embeddable.md | 2 +- ...a-public.applyglobalfilteractioncontext.md | 2 +- .../kibana-plugin-plugins-data-public.md | 1 + ...plugin-plugins-data-server.plugin.setup.md | 4 +- ...plugin-plugins-data-server.plugin.start.md | 4 +- ...ble-public.embeddablecontext.embeddable.md | 2 +- ...ins-embeddable-public.embeddablecontext.md | 4 +- ...ugins-embeddable-public.embeddableinput.md | 3 - ...public.embeddablesetupdependencies.data.md | 11 --- ...able-public.embeddablesetupdependencies.md | 1 - ...public.embeddablestartdependencies.data.md | 11 --- ...able-public.embeddablestartdependencies.md | 1 - ...able-public.iscontextmenutriggercontext.md | 2 +- ...kibana-plugin-plugins-embeddable-public.md | 2 + ...-embeddable-public.select_range_trigger.md | 11 +++ ...s-embeddable-public.value_click_trigger.md | 11 +++ ...sions-public.iinterpreterrenderhandlers.md | 2 +- ...blic.iinterpreterrenderhandlers.uistate.md | 4 +- ...sions-server.iinterpreterrenderhandlers.md | 2 +- ...rver.iinterpreterrenderhandlers.uistate.md | 4 +- ...-ui_actions-public.apply_filter_trigger.md | 11 --- ...ns-ui_actions-public.applyfiltertrigger.md | 11 --- ...kibana-plugin-plugins-ui_actions-public.md | 6 -- ...tions-public.rowclickcontext.embeddable.md | 2 +- ...ugins-ui_actions-public.rowclickcontext.md | 2 +- ...-ui_actions-public.select_range_trigger.md | 11 --- ...ns-ui_actions-public.selectrangetrigger.md | 11 --- ...in-plugins-ui_actions-public.trigger.id.md | 2 +- ...lugin-plugins-ui_actions-public.trigger.md | 2 +- ...ic.triggercontextmapping.filter_trigger.md | 11 --- ...ui_actions-public.triggercontextmapping.md | 3 - ...ggercontextmapping.select_range_trigger.md | 11 --- ...iggercontextmapping.value_click_trigger.md | 11 --- ...ublic.uiactionsservice.addtriggeraction.md | 2 +- ...ns-public.uiactionsservice.attachaction.md | 2 +- ....uiactionsservice.executetriggeractions.md | 2 +- ...tions-public.uiactionsservice.getaction.md | 2 +- ...ions-public.uiactionsservice.gettrigger.md | 2 +- ...blic.uiactionsservice.gettriggeractions.md | 2 +- ...ionsservice.gettriggercompatibleactions.md | 2 +- ...gins-ui_actions-public.uiactionsservice.md | 16 ++--- ...-public.uiactionsservice.registeraction.md | 2 +- ...s-ui_actions-public.value_click_trigger.md | 11 --- ...ins-ui_actions-public.valueclicktrigger.md | 11 --- .../run_find_plugins_with_circular_deps.ts | 7 +- .../public/actions/apply_filter_action.ts | 5 +- .../create_filters_from_range_select.ts | 12 +++- .../create_filters_from_value_click.test.ts | 8 ++- .../create_filters_from_value_click.ts | 15 +++- .../public/actions/select_range_action.ts | 22 +++--- .../data/public/actions/value_click_action.ts | 26 +++++-- src/plugins/data/public/index.ts | 1 + src/plugins/data/public/plugin.ts | 18 ++--- src/plugins/data/public/public.api.md | 9 ++- .../public/triggers/apply_filter_trigger.ts | 6 +- .../public/triggers/index.ts} | 14 +--- .../components/data_table.tsx | 6 +- src/plugins/data/server/server.api.md | 4 +- .../embeddable/search_embeddable.ts | 3 +- src/plugins/embeddable/common/types.ts | 17 ----- src/plugins/embeddable/public/bootstrap.ts | 26 ++++--- src/plugins/embeddable/public/index.ts | 2 + .../lib/embeddables/embeddable.test.tsx | 8 ++- .../add_panel/add_panel_action.test.tsx | 6 +- .../inspect_panel_action.test.tsx | 3 +- .../remove_panel_action.test.tsx | 6 +- .../embeddables/filterable_container.tsx | 6 +- .../embeddables/filterable_embeddable.tsx | 10 ++- .../public/lib/triggers/triggers.ts | 26 ++++++- src/plugins/embeddable/public/mocks.tsx | 3 - src/plugins/embeddable/public/plugin.tsx | 5 +- src/plugins/embeddable/public/public.api.md | 71 +++++-------------- .../embeddable/public/tests/container.test.ts | 6 +- .../public/tests/explicit_input.test.ts | 6 +- .../embeddable/public/tests/test_plugin.ts | 3 - .../common/expression_renderers/types.ts | 9 ++- src/plugins/expressions/public/public.api.md | 4 +- src/plugins/expressions/server/server.api.md | 4 +- src/plugins/ui_actions/public/index.ts | 6 -- src/plugins/ui_actions/public/plugin.ts | 12 +--- src/plugins/ui_actions/public/public.api.md | 67 +++-------------- .../ui_actions/public/triggers/index.ts | 3 - .../public/triggers/row_click_trigger.ts | 5 +- .../public/triggers/select_range_trigger.ts | 32 --------- .../ui_actions/public/triggers/trigger.ts | 3 +- .../public/triggers/trigger_contract.ts | 3 +- src/plugins/ui_actions/public/types.ts | 8 --- .../public/components/table_visualization.tsx | 3 +- .../public/utils/use/use_ui_state.ts | 6 +- .../public/timeseries_vis_renderer.tsx | 3 +- .../vis_type_vislib/public/vis_controller.tsx | 6 +- .../vis_type_vislib/public/vis_wrapper.tsx | 7 +- .../vis_type_xy/public/vis_component.tsx | 3 +- .../vis_type_xy/public/vis_renderer.tsx | 3 +- .../public/embeddable/events.ts | 9 +-- .../public/embeddable/visualize_embeddable.ts | 3 + .../drilldowns_with_embeddable_example.tsx | 6 +- .../dashboard_hello_world_drilldown/index.tsx | 6 +- .../index.tsx | 6 +- .../drilldown.tsx | 2 +- .../dashboard_to_discover_drilldown/types.ts | 3 +- .../button_embeddable/button_embeddable.ts | 7 +- x-pack/plugins/dashboard_enhanced/kibana.json | 3 +- .../abstract_dashboard_drilldown/types.ts | 2 +- .../drilldowns/actions/drilldown_shared.ts | 6 +- .../embeddable_to_dashboard_drilldown.tsx | 19 +++-- .../explore_data/explore_data_chart_action.ts | 5 +- .../explore_data_context_menu_action.ts | 21 ++++-- .../discover_enhanced/public/plugin.ts | 7 +- .../url_drilldown/public/lib/test/data.ts | 4 ++ .../public/lib/url_drilldown.test.ts | 7 +- .../public/lib/url_drilldown.tsx | 23 ++++-- .../public/lib/url_drilldown_scope.ts | 30 ++++---- .../embeddable_action_storage.test.ts | 2 +- .../embeddable/embeddable.tsx | 10 ++- x-pack/plugins/lens/public/types.ts | 5 +- .../maps/public/embeddable/map_embeddable.tsx | 6 +- .../plugins/maps/public/embeddable/types.ts | 5 +- .../translations/translations/ja-JP.json | 12 ++-- .../translations/translations/zh-CN.json | 12 ++-- .../components/action_wizard/test_data.tsx | 7 +- 122 files changed, 437 insertions(+), 581 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md delete mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md delete mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md delete mode 100644 docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md rename src/plugins/{ui_actions => data}/public/triggers/apply_filter_trigger.ts (85%) rename src/plugins/{ui_actions/public/triggers/value_click_trigger.ts => data/public/triggers/index.ts} (62%) delete mode 100644 src/plugins/ui_actions/public/triggers/select_range_trigger.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md new file mode 100644 index 00000000000000..aaed18b3b88907 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.apply_filter_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-data-public.apply_filter_trigger.md) + +## APPLY\_FILTER\_TRIGGER variable + +Signature: + +```typescript +APPLY_FILTER_TRIGGER = "FILTER_TRIGGER" +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md index 027ae4209b77fd..dbeeeb9979aae3 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable?: IEmbeddable; +embeddable?: unknown; ``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md index 62817cd0a1e33f..2f844b6844645d 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.md @@ -14,7 +14,7 @@ export interface ApplyGlobalFilterActionContext | Property | Type | Description | | --- | --- | --- | -| [embeddable](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.embeddable.md) | unknown | | | [filters](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.filters.md) | Filter[] | | | [timeFieldName](./kibana-plugin-plugins-data-public.applyglobalfilteractioncontext.timefieldname.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 8de3821161ab43..2040043d4351b9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -102,6 +102,7 @@ | [ACTION\_GLOBAL\_APPLY\_FILTER](./kibana-plugin-plugins-data-public.action_global_apply_filter.md) | | | [AggGroupLabels](./kibana-plugin-plugins-data-public.agggrouplabels.md) | | | [AggGroupNames](./kibana-plugin-plugins-data-public.agggroupnames.md) | | +| [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-data-public.apply_filter_trigger.md) | | | [baseFormattersPublic](./kibana-plugin-plugins-data-public.baseformatterspublic.md) | | | [castEsToKbnFieldTypeName](./kibana-plugin-plugins-data-public.castestokbnfieldtypename.md) | Get the KbnFieldType name for an esType string | | [connectToQueryState](./kibana-plugin-plugins-data-public.connecttoquerystate.md) | Helper to setup two-way syncing of global data and a state container | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md index b90018c3d9cdd9..bd90f23b4ab590 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.setup.md @@ -11,7 +11,7 @@ setup(core: CoreSetup, { bfetch, e __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }; ``` @@ -29,7 +29,7 @@ setup(core: CoreSetup, { bfetch, e __enhance: (enhancements: DataEnhancements) => void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index 8a3dbe5a6350c1..88f85eb7a7d05e 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("src/core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md index 06e51958a2d1e5..92926d10a543c9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable: IEmbeddable; +embeddable: T; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md index a2c2d9245eabe3..753a3ff2ec6ec9 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablecontext.md @@ -7,12 +7,12 @@ Signature: ```typescript -export interface EmbeddableContext +export interface EmbeddableContext ``` ## Properties | Property | Type | Description | | --- | --- | --- | -| [embeddable](./kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-embeddable-public.embeddablecontext.embeddable.md) | T | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md index f36f7b4ee77a4c..0f14215ff13090 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddableinput.md @@ -16,9 +16,6 @@ export declare type EmbeddableInput = { enhancements?: SerializableState; disabledActions?: string[]; disableTriggers?: boolean; - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; searchSessionId?: string; }; ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md deleted file mode 100644 index d3a62657372ac1..00000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableSetupDependencies](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md) > [data](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md) - -## EmbeddableSetupDependencies.data property - -Signature: - -```typescript -data: DataPublicPluginSetup; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md index fdd31ca75be2a7..957e3f279ff602 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.md @@ -14,6 +14,5 @@ export interface EmbeddableSetupDependencies | Property | Type | Description | | --- | --- | --- | -| [data](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.data.md) | DataPublicPluginSetup | | | [uiActions](./kibana-plugin-plugins-embeddable-public.embeddablesetupdependencies.uiactions.md) | UiActionsSetup | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md deleted file mode 100644 index 0595609b11e498..00000000000000 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [EmbeddableStartDependencies](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md) > [data](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md) - -## EmbeddableStartDependencies.data property - -Signature: - -```typescript -data: DataPublicPluginStart; -``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md index 5a1b5d1e068610..342163ed2e4137 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.md @@ -14,7 +14,6 @@ export interface EmbeddableStartDependencies | Property | Type | Description | | --- | --- | --- | -| [data](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.data.md) | DataPublicPluginStart | | | [inspector](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.inspector.md) | InspectorStart | | | [uiActions](./kibana-plugin-plugins-embeddable-public.embeddablestartdependencies.uiactions.md) | UiActionsStart | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md index 62610624655a14..2f5966f9ba940d 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.iscontextmenutriggercontext.md @@ -7,5 +7,5 @@ Signature: ```typescript -isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext +isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext> ``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index a6aeba23cd280c..b875b1fce42887 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -86,6 +86,8 @@ | [PANEL\_NOTIFICATION\_TRIGGER](./kibana-plugin-plugins-embeddable-public.panel_notification_trigger.md) | | | [panelBadgeTrigger](./kibana-plugin-plugins-embeddable-public.panelbadgetrigger.md) | | | [panelNotificationTrigger](./kibana-plugin-plugins-embeddable-public.panelnotificationtrigger.md) | | +| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.select_range_trigger.md) | | +| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-embeddable-public.value_click_trigger.md) | | | [withEmbeddableSubscription](./kibana-plugin-plugins-embeddable-public.withembeddablesubscription.md) | | ## Type Aliases diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md new file mode 100644 index 00000000000000..175e3fe947a0f9 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.select_range_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-embeddable-public.select_range_trigger.md) + +## SELECT\_RANGE\_TRIGGER variable + +Signature: + +```typescript +SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER" +``` diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md new file mode 100644 index 00000000000000..a85be3142d0f2b --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.value_click_trigger.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-embeddable-public.value_click_trigger.md) + +## VALUE\_CLICK\_TRIGGER variable + +Signature: + +```typescript +VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER" +``` diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md index 931e474a410065..c22c8bc6b62456 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.md @@ -20,6 +20,6 @@ export interface IInterpreterRenderHandlers | [hasCompatibleActions](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.reload.md) | () => void | | -| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | PersistedState | | +| [uiState](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [update](./kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.update.md) | (params: any) => void | | diff --git a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md index 8d74c8e555fee6..461bf861d4d5ee 100644 --- a/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md +++ b/docs/development/plugins/expressions/public/kibana-plugin-plugins-expressions-public.iinterpreterrenderhandlers.uistate.md @@ -4,8 +4,10 @@ ## IInterpreterRenderHandlers.uiState property +This uiState interface is actually `PersistedState` from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. + Signature: ```typescript -uiState?: PersistedState; +uiState?: unknown; ``` diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md index 273703cacca063..547608f40e6aa2 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.md @@ -20,6 +20,6 @@ export interface IInterpreterRenderHandlers | [hasCompatibleActions](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.hascompatibleactions.md) | (event: any) => Promise<boolean> | | | [onDestroy](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.ondestroy.md) | (fn: () => void) => void | | | [reload](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.reload.md) | () => void | | -| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | PersistedState | | +| [uiState](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md) | unknown | This uiState interface is actually PersistedState from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. | | [update](./kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.update.md) | (params: any) => void | | diff --git a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md index b09433c6454adb..ca1c8eec8c2f77 100644 --- a/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md +++ b/docs/development/plugins/expressions/server/kibana-plugin-plugins-expressions-server.iinterpreterrenderhandlers.uistate.md @@ -4,8 +4,10 @@ ## IInterpreterRenderHandlers.uiState property +This uiState interface is actually `PersistedState` from the visualizations plugin, but expressions cannot know about vis or it creates a mess of circular dependencies. Downstream consumers of the uiState handler will need to cast for now. + Signature: ```typescript -uiState?: PersistedState; +uiState?: unknown; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md deleted file mode 100644 index 94e66bf404f5ce..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) - -## APPLY\_FILTER\_TRIGGER variable - -Signature: - -```typescript -APPLY_FILTER_TRIGGER = "FILTER_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md deleted file mode 100644 index e1fb6d342457e8..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) - -## applyFilterTrigger variable - -Signature: - -```typescript -applyFilterTrigger: Trigger<'FILTER_TRIGGER'> -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md index fd1ea7df4fb745..76e347bddd1682 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.md @@ -41,14 +41,8 @@ | [ACTION\_VISUALIZE\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_field.md) | | | [ACTION\_VISUALIZE\_GEO\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_geo_field.md) | | | [ACTION\_VISUALIZE\_LENS\_FIELD](./kibana-plugin-plugins-ui_actions-public.action_visualize_lens_field.md) | | -| [APPLY\_FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.apply_filter_trigger.md) | | -| [applyFilterTrigger](./kibana-plugin-plugins-ui_actions-public.applyfiltertrigger.md) | | | [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.row_click_trigger.md) | | | [rowClickTrigger](./kibana-plugin-plugins-ui_actions-public.rowclicktrigger.md) | | -| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) | | -| [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) | | -| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) | | -| [valueClickTrigger](./kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md) | | | [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.visualize_field_trigger.md) | | | [VISUALIZE\_GEO\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.visualize_geo_field_trigger.md) | | | [visualizeFieldTrigger](./kibana-plugin-plugins-ui_actions-public.visualizefieldtrigger.md) | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md index e8baf44ff9cbc3..a75637e8ea9d34 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md @@ -7,5 +7,5 @@ Signature: ```typescript -embeddable?: IEmbeddable; +embeddable?: unknown; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md index 74b55d85f10e31..b69734cfc3233d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.rowclickcontext.md @@ -15,5 +15,5 @@ export interface RowClickContext | Property | Type | Description | | --- | --- | --- | | [data](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.data.md) | {
    rowIndex: number;
    table: Datatable;
    columns?: string[];
    } | | -| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | IEmbeddable | | +| [embeddable](./kibana-plugin-plugins-ui_actions-public.rowclickcontext.embeddable.md) | unknown | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md deleted file mode 100644 index fd784ff17fa840..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.select_range_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.select_range_trigger.md) - -## SELECT\_RANGE\_TRIGGER variable - -Signature: - -```typescript -SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md deleted file mode 100644 index 0d9fa2d83ee57a..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [selectRangeTrigger](./kibana-plugin-plugins-ui_actions-public.selectrangetrigger.md) - -## selectRangeTrigger variable - -Signature: - -```typescript -selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md index 426f17f9a03529..5603c852ad39db 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.id.md @@ -4,7 +4,7 @@ ## Trigger.id property -Unique name of the trigger as identified in `ui_actions` plugin trigger registry, such as "SELECT\_RANGE\_TRIGGER" or "VALUE\_CLICK\_TRIGGER". +Unique name of the trigger as identified in `ui_actions` plugin trigger registry. Signature: diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md index b69bba892f4753..ed76cfea976845 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.trigger.md @@ -21,6 +21,6 @@ export interface Trigger | Property | Type | Description | | --- | --- | --- | | [description](./kibana-plugin-plugins-ui_actions-public.trigger.description.md) | string | A longer user friendly description of the trigger. | -| [id](./kibana-plugin-plugins-ui_actions-public.trigger.id.md) | ID | Unique name of the trigger as identified in ui_actions plugin trigger registry, such as "SELECT\_RANGE\_TRIGGER" or "VALUE\_CLICK\_TRIGGER". | +| [id](./kibana-plugin-plugins-ui_actions-public.trigger.id.md) | ID | Unique name of the trigger as identified in ui_actions plugin trigger registry. | | [title](./kibana-plugin-plugins-ui_actions-public.trigger.title.md) | string | User friendly name of the trigger. | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md deleted file mode 100644 index 0ccf8aa3d7415a..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) - -## TriggerContextMapping.FILTER\_TRIGGER property - -Signature: - -```typescript -[APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md index 2f0d22cf6dd741..da7a7a8bfe6452 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md @@ -15,10 +15,7 @@ export interface TriggerContextMapping | Property | Type | Description | | --- | --- | --- | | [""](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.__.md) | TriggerContext | | -| [FILTER\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.filter_trigger.md) | ApplyGlobalFilterActionContext | | | [ROW\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.row_click_trigger.md) | RowClickContext | | -| [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) | RangeSelectContext | | -| [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) | ValueClickContext | | | [VISUALIZE\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_field_trigger.md) | VisualizeFieldContext | | | [VISUALIZE\_GEO\_FIELD\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.visualize_geo_field_trigger.md) | VisualizeFieldContext | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md deleted file mode 100644 index c5ef6843390b34..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [SELECT\_RANGE\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.select_range_trigger.md) - -## TriggerContextMapping.SELECT\_RANGE\_TRIGGER property - -Signature: - -```typescript -[SELECT_RANGE_TRIGGER]: RangeSelectContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md deleted file mode 100644 index 129144a66cee56..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [TriggerContextMapping](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.triggercontextmapping.value_click_trigger.md) - -## TriggerContextMapping.VALUE\_CLICK\_TRIGGER property - -Signature: - -```typescript -[VALUE_CLICK_TRIGGER]: ValueClickContext; -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md index ca999322b7a567..f29d487d774e0d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md @@ -11,5 +11,5 @@ Signature: ```typescript -readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; +readonly addTriggerAction: (triggerId: T, action: ActionDefinition | Action) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md index e95e7e1eb38b61..1ebb30c49c0b32 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly attachAction: (triggerId: T, actionId: string) => void; +readonly attachAction: (triggerId: T, actionId: string) => void; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md index 8e7fb8b8bbf29f..b20f08520c43d5 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md @@ -12,5 +12,5 @@ Signature: ```typescript -readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; +readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md index d540de76374413..300c46a47c47f6 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; +readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md index b996620686a282..95b737a8d6cae0 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTrigger: (triggerId: T) => TriggerContract; +readonly getTrigger: (triggerId: T) => TriggerContract; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md index f94b34ecc2d908..27c1b1eb48f165 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerActions: (triggerId: T) => Action[]; +readonly getTriggerActions: (triggerId: T) => Action[]; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md index dff958608ef9e0..edb7d2d3a15516 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; +readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md index e35eb503ab62b5..4fe8431770deae 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.md @@ -21,19 +21,19 @@ export declare class UiActionsService | Property | Modifiers | Type | Description | | --- | --- | --- | --- | | [actions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.actions.md) | | ActionRegistry | | -| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | -| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, actionId: string) => void | | +| [addTriggerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.addtriggeraction.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, action: ActionDefinition<TriggerContextMapping[T]> | Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">) => void | addTriggerAction is similar to attachAction as it attaches action to a trigger, but it also registers the action, if it has not been registered, yet.addTriggerAction also infers better typing of the action argument. | +| [attachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.attachaction.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, actionId: string) => void | | | [clear](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.clear.md) | | () => void | Removes all registered triggers and actions. | | [detachAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.detachaction.md) | | (triggerId: TriggerId, actionId: string) => void | | -| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | +| [executeTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executetriggeractions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, context: TriggerContext<T>) => Promise<void> | | | [executionService](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.executionservice.md) | | UiActionsExecutionService | | | [fork](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.fork.md) | | () => UiActionsService | "Fork" a separate instance of UiActionsService that inherits all existing triggers and actions, but going forward all new triggers and actions added to this instance of UiActionsService are only available within this instance. | -| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | -| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => TriggerContract<T> | | -| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[] | | -| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER" | "ROW_CLICK_TRIGGER" | "FILTER_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">[]> | | +| [getAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.getaction.md) | | <T extends ActionDefinition<{}>>(id: string) => Action<ActionContext<T>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel"> | | +| [getTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettrigger.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T) => TriggerContract<T> | | +| [getTriggerActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggeractions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T) => Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">[] | | +| [getTriggerCompatibleActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.gettriggercompatibleactions.md) | | <T extends "" | "ROW_CLICK_TRIGGER" | "VISUALIZE_FIELD_TRIGGER" | "VISUALIZE_GEO_FIELD_TRIGGER" | "FILTER_TRIGGER" | "CONTEXT_MENU_TRIGGER" | "PANEL_BADGE_TRIGGER" | "PANEL_NOTIFICATION_TRIGGER" | "SELECT_RANGE_TRIGGER" | "VALUE_CLICK_TRIGGER">(triggerId: T, context: TriggerContextMapping[T]) => Promise<Action<TriggerContextMapping[T], "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">[]> | | | [hasAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.hasaction.md) | | (actionId: string) => boolean | | -| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV"> | | +| [registerAction](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md) | | <A extends ActionDefinition<{}>>(definition: A) => Action<ActionContext<A>, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel"> | | | [registerTrigger](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.registertrigger.md) | | (trigger: Trigger) => void | | | [triggers](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggers.md) | | TriggerRegistry | | | [triggerToActions](./kibana-plugin-plugins-ui_actions-public.uiactionsservice.triggertoactions.md) | | TriggerToActionsRegistry | | diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md index 6f03777e14552a..dee5f75f7c074d 100644 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md +++ b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.uiactionsservice.registeraction.md @@ -7,5 +7,5 @@ Signature: ```typescript -readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; +readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; ``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md deleted file mode 100644 index bd8d4dc50b8fdf..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.value_click_trigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [VALUE\_CLICK\_TRIGGER](./kibana-plugin-plugins-ui_actions-public.value_click_trigger.md) - -## VALUE\_CLICK\_TRIGGER variable - -Signature: - -```typescript -VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER" -``` diff --git a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md b/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md deleted file mode 100644 index 5c4fc284d83b1c..00000000000000 --- a/docs/development/plugins/ui_actions/public/kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-ui\_actions-public](./kibana-plugin-plugins-ui_actions-public.md) > [valueClickTrigger](./kibana-plugin-plugins-ui_actions-public.valueclicktrigger.md) - -## valueClickTrigger variable - -Signature: - -```typescript -valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> -``` diff --git a/src/dev/run_find_plugins_with_circular_deps.ts b/src/dev/run_find_plugins_with_circular_deps.ts index f4662820a1fb07..1a087e2a01fb27 100644 --- a/src/dev/run_find_plugins_with_circular_deps.ts +++ b/src/dev/run_find_plugins_with_circular_deps.ts @@ -31,13 +31,8 @@ interface Options { type CircularDepList = Set; const allowedList: CircularDepList = new Set([ - 'src/plugins/charts -> src/plugins/expressions', + 'src/plugins/charts -> src/plugins/discover', 'src/plugins/charts -> src/plugins/vis_default_editor', - 'src/plugins/data -> src/plugins/embeddable', - 'src/plugins/data -> src/plugins/expressions', - 'src/plugins/data -> src/plugins/ui_actions', - 'src/plugins/embeddable -> src/plugins/ui_actions', - 'src/plugins/expressions -> src/plugins/visualizations', 'src/plugins/vis_default_editor -> src/plugins/visualizations', 'src/plugins/vis_default_editor -> src/plugins/visualize', 'src/plugins/visualizations -> src/plugins/visualize', diff --git a/src/plugins/data/public/actions/apply_filter_action.ts b/src/plugins/data/public/actions/apply_filter_action.ts index 944da72bd11d1d..84ce5b03826247 100644 --- a/src/plugins/data/public/actions/apply_filter_action.ts +++ b/src/plugins/data/public/actions/apply_filter_action.ts @@ -22,7 +22,6 @@ import { toMountPoint } from '../../../kibana_react/public'; import { ActionByType, createAction, IncompatibleActionError } from '../../../ui_actions/public'; import { getOverlays, getIndexPatterns } from '../services'; import { applyFiltersPopover } from '../ui/apply_filters'; -import type { IEmbeddable } from '../../../embeddable/public'; import { Filter, FilterManager, TimefilterContract, esFilters } from '..'; export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER'; @@ -30,7 +29,9 @@ export const ACTION_GLOBAL_APPLY_FILTER = 'ACTION_GLOBAL_APPLY_FILTER'; export interface ApplyGlobalFilterActionContext { filters: Filter[]; timeFieldName?: string; - embeddable?: IEmbeddable; + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; } async function isCompatible(context: ApplyGlobalFilterActionContext) { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts index 2d7aeff79a689a..2b0911b72abd58 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_range_select.ts @@ -19,12 +19,20 @@ import { last } from 'lodash'; import moment from 'moment'; +import { Datatable } from 'src/plugins/expressions'; import { esFilters, IFieldType, RangeFilterParams } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; -import { RangeSelectContext } from '../../../../embeddable/public'; import { AggConfigSerialized } from '../../../common/search/aggs'; -export async function createFiltersFromRangeSelectAction(event: RangeSelectContext['data']) { +/** @internal */ +export interface RangeSelectDataContext { + table: Datatable; + column: number; + range: number[]; + timeFieldName?: string; +} + +export async function createFiltersFromRangeSelectAction(event: RangeSelectDataContext) { const column: Record = event.table.columns[event.column]; if (!column || !column.meta) { diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts index 23d2ab080d75ef..04801a5ee1ceab 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.test.ts @@ -25,8 +25,10 @@ import { } from '../../../public'; import { dataPluginMock } from '../../../public/mocks'; import { setIndexPatterns, setSearchService } from '../../../public/services'; -import { createFiltersFromValueClickAction } from './create_filters_from_value_click'; -import { ValueClickContext } from '../../../../embeddable/public'; +import { + createFiltersFromValueClickAction, + ValueClickDataContext, +} from './create_filters_from_value_click'; const mockField = { name: 'bytes', @@ -34,7 +36,7 @@ const mockField = { }; describe('createFiltersFromValueClick', () => { - let dataPoints: ValueClickContext['data']['data']; + let dataPoints: ValueClickDataContext['data']; beforeEach(() => { dataPoints = [ diff --git a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts index ce7ecf434056a6..30fef7e3a7c66f 100644 --- a/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts +++ b/src/plugins/data/public/actions/filters/create_filters_from_value_click.ts @@ -20,9 +20,20 @@ import { Datatable } from '../../../../../plugins/expressions/public'; import { esFilters, Filter } from '../../../public'; import { getIndexPatterns, getSearchService } from '../../../public/services'; -import { ValueClickContext } from '../../../../embeddable/public'; import { AggConfigSerialized } from '../../../common/search/aggs'; +/** @internal */ +export interface ValueClickDataContext { + data: Array<{ + table: Pick; + column: number; + row: number; + value: any; + }>; + timeFieldName?: string; + negate?: boolean; +} + /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -120,7 +131,7 @@ const createFilter = async ( export const createFiltersFromValueClickAction = async ({ data, negate, -}: ValueClickContext['data']) => { +}: ValueClickDataContext) => { const filters: Filter[] = []; await Promise.all( diff --git a/src/plugins/data/public/actions/select_range_action.ts b/src/plugins/data/public/actions/select_range_action.ts index 1781da980dc30d..3b84523d782f60 100644 --- a/src/plugins/data/public/actions/select_range_action.ts +++ b/src/plugins/data/public/actions/select_range_action.ts @@ -17,16 +17,22 @@ * under the License. */ -import { - ActionByType, - APPLY_FILTER_TRIGGER, - createAction, - UiActionsStart, -} from '../../../../plugins/ui_actions/public'; +import { Datatable } from 'src/plugins/expressions/public'; +import { ActionByType, createAction, UiActionsStart } from '../../../../plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../triggers'; import { createFiltersFromRangeSelectAction } from './filters/create_filters_from_range_select'; -import type { RangeSelectContext } from '../../../embeddable/public'; -export type SelectRangeActionContext = RangeSelectContext; +export interface SelectRangeActionContext { + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; + data: { + table: Datatable; + column: number; + range: number[]; + timeFieldName?: string; + }; +} export const ACTION_SELECT_RANGE = 'ACTION_SELECT_RANGE'; diff --git a/src/plugins/data/public/actions/value_click_action.ts b/src/plugins/data/public/actions/value_click_action.ts index 81e62380eacfb5..8f207e94e8fbe9 100644 --- a/src/plugins/data/public/actions/value_click_action.ts +++ b/src/plugins/data/public/actions/value_click_action.ts @@ -17,19 +17,31 @@ * under the License. */ -import { - ActionByType, - APPLY_FILTER_TRIGGER, - createAction, - UiActionsStart, -} from '../../../../plugins/ui_actions/public'; +import { Datatable } from 'src/plugins/expressions/public'; +import { ActionByType, createAction, UiActionsStart } from '../../../../plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../triggers'; import { createFiltersFromValueClickAction } from './filters/create_filters_from_value_click'; import type { Filter } from '../../common/es_query/filters'; -import type { ValueClickContext } from '../../../embeddable/public'; export type ValueClickActionContext = ValueClickContext; export const ACTION_VALUE_CLICK = 'ACTION_VALUE_CLICK'; +export interface ValueClickContext { + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; + data: { + data: Array<{ + table: Pick; + column: number; + row: number; + value: any; + }>; + timeFieldName?: string; + negate?: boolean; + }; +} + export function createValueClickAction( getStartServices: () => { uiActions: UiActionsStart } ): ActionByType { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 3dda04d738c969..7b15e2576e704a 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -483,6 +483,7 @@ export { export { isTimeRange, isQuery, isFilter, isFilters } from '../common'; export { ACTION_GLOBAL_APPLY_FILTER, ApplyGlobalFilterActionContext } from './actions'; +export { APPLY_FILTER_TRIGGER } from './triggers'; /* * Plugin setup diff --git a/src/plugins/data/public/plugin.ts b/src/plugins/data/public/plugin.ts index eb3a053b78a2d3..c60a1efabf9871 100644 --- a/src/plugins/data/public/plugin.ts +++ b/src/plugins/data/public/plugin.ts @@ -48,11 +48,6 @@ import { setUiSettings, } from './services'; import { createSearchBar } from './ui/search_bar/create_search_bar'; -import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, - APPLY_FILTER_TRIGGER, -} from '../../ui_actions/public'; import { ACTION_GLOBAL_APPLY_FILTER, createFilterAction, @@ -66,13 +61,18 @@ import { createValueClickAction, createSelectRangeAction, } from './actions'; - +import { APPLY_FILTER_TRIGGER, applyFilterTrigger } from './triggers'; import { SavedObjectsClientPublicToCommon } from './index_patterns'; import { getIndexPatternLoad } from './index_patterns/expressions'; import { UsageCollectionSetup } from '../../usage_collection/public'; import { getTableViewDescription } from './utils/table_inspector_view'; +import { TriggerId } from '../../ui_actions/public'; declare module '../../ui_actions/public' { + export interface TriggerContextMapping { + [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; + } + export interface ActionContextMapping { [ACTION_GLOBAL_APPLY_FILTER]: ApplyGlobalFilterActionContext; [ACTION_SELECT_RANGE]: SelectRangeActionContext; @@ -118,19 +118,21 @@ export class DataPublicPlugin storage: this.storage, }); + uiActions.registerTrigger(applyFilterTrigger); + uiActions.registerAction( createFilterAction(queryService.filterManager, queryService.timefilter.timefilter) ); uiActions.addTriggerAction( - SELECT_RANGE_TRIGGER, + 'SELECT_RANGE_TRIGGER' as TriggerId, createSelectRangeAction(() => ({ uiActions: startServices().plugins.uiActions, })) ); uiActions.addTriggerAction( - VALUE_CLICK_TRIGGER, + 'VALUE_CLICK_TRIGGER' as TriggerId, createValueClickAction(() => ({ uiActions: startServices().plugins.uiActions, })) diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index e5df6d860b4041..120540ddb92ecf 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -464,14 +464,17 @@ export type AggsStart = Assign; +// Warning: (ae-missing-release-tag) "APPLY_FILTER_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const APPLY_FILTER_TRIGGER = "FILTER_TRIGGER"; + // Warning: (ae-missing-release-tag) "ApplyGlobalFilterActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export interface ApplyGlobalFilterActionContext { - // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts - // // (undocumented) - embeddable?: IEmbeddable; + embeddable?: unknown; // (undocumented) filters: Filter[]; // (undocumented) diff --git a/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts b/src/plugins/data/public/triggers/apply_filter_trigger.ts similarity index 85% rename from src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts rename to src/plugins/data/public/triggers/apply_filter_trigger.ts index aa54706476a8f5..816c1737608daa 100644 --- a/src/plugins/ui_actions/public/triggers/apply_filter_trigger.ts +++ b/src/plugins/data/public/triggers/apply_filter_trigger.ts @@ -18,15 +18,15 @@ */ import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; +import { Trigger } from '../../../ui_actions/public'; export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'> = { id: APPLY_FILTER_TRIGGER, - title: i18n.translate('uiActions.triggers.applyFilterTitle', { + title: i18n.translate('data.triggers.applyFilterTitle', { defaultMessage: 'Apply filter', }), - description: i18n.translate('uiActions.triggers.applyFilterDescription', { + description: i18n.translate('data.triggers.applyFilterDescription', { defaultMessage: 'When kibana filter is applied. Could be a single value or a range filter.', }), }; diff --git a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts b/src/plugins/data/public/triggers/index.ts similarity index 62% rename from src/plugins/ui_actions/public/triggers/value_click_trigger.ts rename to src/plugins/data/public/triggers/index.ts index f1aff6322522ae..36a38ae76bc0e9 100644 --- a/src/plugins/ui_actions/public/triggers/value_click_trigger.ts +++ b/src/plugins/data/public/triggers/index.ts @@ -17,16 +17,4 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; - -export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; -export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { - id: VALUE_CLICK_TRIGGER, - title: i18n.translate('uiActions.triggers.valueClickTitle', { - defaultMessage: 'Single click', - }), - description: i18n.translate('uiActions.triggers.valueClickDescription', { - defaultMessage: 'A data point click on the visualization', - }), -}; +export * from './apply_filter_trigger'; diff --git a/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx index f4d1a8988da788..f842568859fc24 100644 --- a/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx +++ b/src/plugins/data/public/utils/table_inspector_view/components/data_table.tsx @@ -38,7 +38,7 @@ import { DataViewRow, DataViewColumn } from '../types'; import { IUiSettingsClient } from '../../../../../../core/public'; import { Datatable, DatatableColumn } from '../../../../../expressions/public'; import { FieldFormatsStart } from '../../../field_formats'; -import { UiActionsStart } from '../../../../../ui_actions/public'; +import { TriggerId, UiActionsStart } from '../../../../../ui_actions/public'; interface DataTableFormatState { columns: DataViewColumn[]; @@ -112,7 +112,7 @@ export class DataTableFormat extends Component { const value = table.rows[rowIndex][column.id]; const eventData = { table, column: columnIndex, row: rowIndex, value }; - uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER' as TriggerId, { data: { data: [eventData] }, }); }} @@ -145,7 +145,7 @@ export class DataTableFormat extends Component { const value = table.rows[rowIndex][column.id]; const eventData = { table, column: columnIndex, row: rowIndex, value }; - uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER', { + uiActions.executeTriggerActions('VALUE_CLICK_TRIGGER' as TriggerId, { data: { data: [eventData], negate: true }, }); }} diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 4d24e6d1afd498..60d5e167921cc7 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1115,7 +1115,7 @@ export class Plugin implements Plugin_2 void; search: ISearchSetup; fieldFormats: { - register: (customFieldFormat: import("../common").FieldFormatInstanceType) => number; + register: (customFieldFormat: import("../public").FieldFormatInstanceType) => number; }; }; // (undocumented) @@ -1124,7 +1124,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("src/core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index ff408ba431ed95..d0c3907d312420 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -21,9 +21,10 @@ import angular from 'angular'; import _ from 'lodash'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { UiActionsStart, APPLY_FILTER_TRIGGER } from '../../../../ui_actions/public'; +import { UiActionsStart } from '../../../../ui_actions/public'; import { RequestAdapter, Adapters } from '../../../../inspector/public'; import { + APPLY_FILTER_TRIGGER, esFilters, Filter, TimeRange, diff --git a/src/plugins/embeddable/common/types.ts b/src/plugins/embeddable/common/types.ts index 8965446cc85fa7..d893724f616d2b 100644 --- a/src/plugins/embeddable/common/types.ts +++ b/src/plugins/embeddable/common/types.ts @@ -18,8 +18,6 @@ */ import { PersistableStateService, SerializableState } from '../../kibana_utils/common'; -import { Query, TimeRange } from '../../data/common/query'; -import { Filter } from '../../data/common/es_query/filters'; export enum ViewMode { EDIT = 'edit', @@ -53,21 +51,6 @@ export type EmbeddableInput = { */ disableTriggers?: boolean; - /** - * Time range of the chart. - */ - timeRange?: TimeRange; - - /** - * Visualization query string used to narrow down results. - */ - query?: Query; - - /** - * Visualization filters used to narrow down results. - */ - filters?: Filter[]; - /** * Search session id to group searches */ diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 5c95214ef591bf..efaff42c19e2f5 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -18,18 +18,24 @@ */ import { UiActionsSetup } from '../../ui_actions/public'; import { - contextMenuTrigger, - panelBadgeTrigger, - EmbeddableContext, - CONTEXT_MENU_TRIGGER, - PANEL_BADGE_TRIGGER, ACTION_ADD_PANEL, ACTION_CUSTOMIZE_PANEL, - ACTION_INSPECT_PANEL, - REMOVE_PANEL_ACTION, ACTION_EDIT_PANEL, - panelNotificationTrigger, + ACTION_INSPECT_PANEL, + CONTEXT_MENU_TRIGGER, + contextMenuTrigger, + EmbeddableContext, + PANEL_BADGE_TRIGGER, PANEL_NOTIFICATION_TRIGGER, + panelBadgeTrigger, + panelNotificationTrigger, + RangeSelectContext, + REMOVE_PANEL_ACTION, + SELECT_RANGE_TRIGGER, + selectRangeTrigger, + ValueClickContext, + VALUE_CLICK_TRIGGER, + valueClickTrigger, } from './lib'; declare module '../../ui_actions/public' { @@ -37,6 +43,8 @@ declare module '../../ui_actions/public' { [CONTEXT_MENU_TRIGGER]: EmbeddableContext; [PANEL_BADGE_TRIGGER]: EmbeddableContext; [PANEL_NOTIFICATION_TRIGGER]: EmbeddableContext; + [SELECT_RANGE_TRIGGER]: RangeSelectContext; + [VALUE_CLICK_TRIGGER]: ValueClickContext; } export interface ActionContextMapping { @@ -56,4 +64,6 @@ export const bootstrap = (uiActions: UiActionsSetup) => { uiActions.registerTrigger(contextMenuTrigger); uiActions.registerTrigger(panelBadgeTrigger); uiActions.registerTrigger(panelNotificationTrigger); + uiActions.registerTrigger(selectRangeTrigger); + uiActions.registerTrigger(valueClickTrigger); }; diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 0fc7c7965010b2..d537ef2bd0c5c8 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -65,6 +65,8 @@ export { PanelNotFoundError, PanelState, PropertySpec, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, ViewMode, withEmbeddableSubscription, SavedObjectEmbeddableInput, diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx index c7c71656bceb22..c0e13a84066ca0 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable.test.tsx @@ -24,8 +24,10 @@ import { Embeddable } from './embeddable'; import { EmbeddableOutput, EmbeddableInput } from './i_embeddable'; import { ViewMode } from '../types'; import { ContactCardEmbeddable } from '../test_samples/embeddables/contact_card/contact_card_embeddable'; -import { FilterableEmbeddable } from '../test_samples/embeddables/filterable_embeddable'; -import type { Filter } from '../../../../data/public'; +import { + MockFilter, + FilterableEmbeddable, +} from '../test_samples/embeddables/filterable_embeddable'; class TestClass { constructor() {} @@ -83,7 +85,7 @@ test('Embeddable reload is called if lastReloadRequest input time changes', asyn test('Embeddable reload is called if lastReloadRequest input time changed and new input is used', async () => { const hello = new FilterableEmbeddable({ id: '123', filters: [], lastReloadRequestTime: 0 }); - const aFilter = ({} as unknown) as Filter; + const aFilter = ({} as unknown) as MockFilter; hello.reload = jest.fn(() => { // when reload is called embeddable already has new input expect(hello.getInput().filters).toEqual([aFilter]); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx index 0361939fd07e6a..cb78fac5471a94 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_action.test.tsx @@ -20,6 +20,7 @@ import { ViewMode, EmbeddableOutput, isErrorEmbeddable } from '../../../../'; import { AddPanelAction } from './add_panel_action'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddable, FilterableEmbeddableInput, @@ -28,7 +29,6 @@ import { FilterableEmbeddableFactory } from '../../../../test_samples/embeddable import { FilterableContainer } from '../../../../test_samples/embeddables/filterable_container'; import { coreMock } from '../../../../../../../../core/public/mocks'; import { ContactCardEmbeddable } from '../../../../test_samples'; -import { esFilters, Filter } from '../../../../../../../../plugins/data/public'; import { EmbeddableStart } from '../../../../../plugin'; import { embeddablePluginMock } from '../../../../../mocks'; import { defaultTrigger } from '../../../../../../../ui_actions/public/triggers'; @@ -51,8 +51,8 @@ beforeEach(async () => { () => null ); - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx index eb836414489869..b784a46127305c 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/inspect_panel_action.test.tsx @@ -29,7 +29,6 @@ import { import { inspectorPluginMock } from '../../../../../../../plugins/inspector/public/mocks'; import { EmbeddableOutput, isErrorEmbeddable, ErrorEmbeddable } from '../../../embeddables'; import { of } from '../../../../tests/helpers'; -import { esFilters } from '../../../../../../../plugins/data/public'; import { embeddablePluginMock } from '../../../../mocks'; import { EmbeddableStart } from '../../../../plugin'; @@ -43,7 +42,7 @@ const setupTests = async () => { panels: {}, filters: [ { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }, diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx index dea4a88bda0823..ce6a1cc20fc4da 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/remove_panel_action.test.tsx @@ -21,6 +21,7 @@ import { EmbeddableOutput, isErrorEmbeddable } from '../../../'; import { RemovePanelAction } from './remove_panel_action'; import { EmbeddableStart } from '../../../../plugin'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddable, FilterableEmbeddableInput, @@ -29,7 +30,6 @@ import { FilterableEmbeddableFactory } from '../../../test_samples/embeddables/f import { FilterableContainer } from '../../../test_samples/embeddables/filterable_container'; import { ViewMode } from '../../../types'; import { ContactCardEmbeddable } from '../../../test_samples/embeddables/contact_card/contact_card_embeddable'; -import { esFilters, Filter } from '../../../../../../../plugins/data/public'; import { embeddablePluginMock } from '../../../../mocks'; const { setup, doStart } = embeddablePluginMock.createInstance(); @@ -39,8 +39,8 @@ let container: FilterableContainer; let embeddable: FilterableEmbeddable; beforeEach(async () => { - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx index db71b94ac855fd..23696612fd82aa 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_container.tsx @@ -18,13 +18,13 @@ */ import { Container, ContainerInput } from '../../containers'; -import { Filter } from '../../../../../data/public'; import { EmbeddableStart } from '../../../plugin'; +import { MockFilter } from './filterable_embeddable'; export const FILTERABLE_CONTAINER = 'FILTERABLE_CONTAINER'; export interface FilterableContainerInput extends ContainerInput { - filters: Filter[]; + filters: MockFilter[]; } /** @@ -33,7 +33,7 @@ export interface FilterableContainerInput extends ContainerInput { * here instead */ export type InheritedChildrenInput = { - filters: Filter[]; + filters: MockFilter[]; id?: string; }; diff --git a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx index fd6ea3b9aa2b28..99d21198dd151d 100644 --- a/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx +++ b/src/plugins/embeddable/public/lib/test_samples/embeddables/filterable_embeddable.tsx @@ -19,12 +19,18 @@ import { IContainer } from '../../containers'; import { EmbeddableOutput, EmbeddableInput, Embeddable } from '../../embeddables'; -import { Filter } from '../../../../../data/public'; + +/** @internal */ +export interface MockFilter { + $state?: any; + meta: any; + query?: any; +} export const FILTERABLE_EMBEDDABLE = 'FILTERABLE_EMBEDDABLE'; export interface FilterableEmbeddableInput extends EmbeddableInput { - filters: Filter[]; + filters: MockFilter[]; } export class FilterableEmbeddable extends Embeddable { diff --git a/src/plugins/embeddable/public/lib/triggers/triggers.ts b/src/plugins/embeddable/public/lib/triggers/triggers.ts index c3b1496b8eca82..d9fb063a5bb569 100644 --- a/src/plugins/embeddable/public/lib/triggers/triggers.ts +++ b/src/plugins/embeddable/public/lib/triggers/triggers.ts @@ -22,8 +22,8 @@ import { Datatable } from '../../../../expressions'; import { Trigger, RowClickContext } from '../../../../ui_actions/public'; import { IEmbeddable } from '..'; -export interface EmbeddableContext { - embeddable: IEmbeddable; +export interface EmbeddableContext { + embeddable: T; } export interface ValueClickContext { @@ -88,6 +88,28 @@ export const panelNotificationTrigger: Trigger<'PANEL_NOTIFICATION_TRIGGER'> = { }), }; +export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; +export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { + id: SELECT_RANGE_TRIGGER, + title: i18n.translate('embeddableApi.selectRangeTrigger.title', { + defaultMessage: 'Range selection', + }), + description: i18n.translate('embeddableApi.selectRangeTrigger.description', { + defaultMessage: 'A range of values on the visualization', + }), +}; + +export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; +export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'> = { + id: VALUE_CLICK_TRIGGER, + title: i18n.translate('embeddableApi.valueClickTrigger.title', { + defaultMessage: 'Single click', + }), + description: i18n.translate('embeddableApi.valueClickTrigger.description', { + defaultMessage: 'A data point click on the visualization', + }), +}; + export const isValueClickTriggerContext = ( context: ChartActionContext ): context is ValueClickContext => context.data && 'data' in context.data; diff --git a/src/plugins/embeddable/public/mocks.tsx b/src/plugins/embeddable/public/mocks.tsx index df24d9c0393fe6..c41ecaabe84797 100644 --- a/src/plugins/embeddable/public/mocks.tsx +++ b/src/plugins/embeddable/public/mocks.tsx @@ -34,7 +34,6 @@ import { coreMock } from '../../../core/public/mocks'; import { UiActionsService } from './lib/ui_actions'; import { CoreStart } from '../../../core/public'; import { Start as InspectorStart } from '../../inspector/public'; -import { dataPluginMock } from '../../data/public/mocks'; import { inspectorPluginMock } from '../../inspector/public/mocks'; import { uiActionsPluginMock } from '../../ui_actions/public/mocks'; @@ -136,13 +135,11 @@ const createInstance = (setupPlugins: Partial = {}) const plugin = new EmbeddablePublicPlugin({} as any); const setup = plugin.setup(coreMock.createSetup(), { uiActions: setupPlugins.uiActions || uiActionsPluginMock.createSetupContract(), - data: dataPluginMock.createSetupContract(), }); const doStart = (startPlugins: Partial = {}) => plugin.start(coreMock.createStart(), { uiActions: startPlugins.uiActions || uiActionsPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), - data: dataPluginMock.createStartContract(), }); return { plugin, diff --git a/src/plugins/embeddable/public/plugin.tsx b/src/plugins/embeddable/public/plugin.tsx index 6f43d87bdcd53c..a417fb3938b8a6 100644 --- a/src/plugins/embeddable/public/plugin.tsx +++ b/src/plugins/embeddable/public/plugin.tsx @@ -19,7 +19,6 @@ import React from 'react'; import { Subscription } from 'rxjs'; import { identity } from 'lodash'; -import { DataPublicPluginSetup, DataPublicPluginStart } from '../../data/public'; import { getSavedObjectFinder, showSaveModal } from '../../saved_objects/public'; import { UiActionsSetup, UiActionsStart } from '../../ui_actions/public'; import { Start as InspectorStart } from '../../inspector/public'; @@ -62,12 +61,10 @@ import { } from '../common/lib'; export interface EmbeddableSetupDependencies { - data: DataPublicPluginSetup; uiActions: UiActionsSetup; } export interface EmbeddableStartDependencies { - data: DataPublicPluginStart; uiActions: UiActionsStart; inspector: InspectorStart; } @@ -144,7 +141,7 @@ export class EmbeddablePublicPlugin implements Plugin { this.embeddableFactories.set( diff --git a/src/plugins/embeddable/public/public.api.md b/src/plugins/embeddable/public/public.api.md index 7563b66e58ae9a..a401795c498b3f 100644 --- a/src/plugins/embeddable/public/public.api.md +++ b/src/plugins/embeddable/public/public.api.md @@ -8,48 +8,29 @@ import { Action } from 'history'; import { Action as Action_3 } from 'src/plugins/ui_actions/public'; import { ActionExecutionContext as ActionExecutionContext_2 } from 'src/plugins/ui_actions/public'; import { ApiResponse } from '@elastic/elasticsearch/lib/Transport'; -import { ApiResponse as ApiResponse_2 } from '@elastic/elasticsearch'; import { ApplicationStart as ApplicationStart_2 } from 'kibana/public'; -import { Assign } from '@kbn/utility-types'; -import { BehaviorSubject } from 'rxjs'; -import { BfetchPublicSetup } from 'src/plugins/bfetch/public'; import Boom from '@hapi/boom'; import { ConfigDeprecationProvider } from '@kbn/config'; -import { CoreSetup as CoreSetup_2 } from 'src/core/public'; -import { CoreSetup as CoreSetup_3 } from 'kibana/public'; -import { CoreStart as CoreStart_2 } from 'kibana/public'; import * as CSS from 'csstype'; -import { DatatableColumn as DatatableColumn_2 } from 'src/plugins/expressions'; import { EmbeddableStart as EmbeddableStart_2 } from 'src/plugins/embeddable/public/plugin'; -import { Ensure } from '@kbn/utility-types'; import { EnvironmentMode } from '@kbn/config'; -import { ErrorToastOptions as ErrorToastOptions_2 } from 'src/core/public/notifications'; import { EuiBreadcrumb } from '@elastic/eui'; import { EuiButtonEmptyProps } from '@elastic/eui'; -import { EuiComboBoxProps } from '@elastic/eui'; import { EuiConfirmModalProps } from '@elastic/eui'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiFlyoutSize } from '@elastic/eui'; import { EuiGlobalToastListToast } from '@elastic/eui'; import { EventEmitter } from 'events'; -import { ExpressionAstExpression } from 'src/plugins/expressions/common'; import { History } from 'history'; import { Href } from 'history'; -import { HttpSetup as HttpSetup_2 } from 'kibana/public'; import { I18nStart as I18nStart_2 } from 'src/core/public'; import { IconType } from '@elastic/eui'; -import { ISearchOptions } from 'src/plugins/data/public'; -import { ISearchSource } from 'src/plugins/data/public'; -import { IStorageWrapper as IStorageWrapper_2 } from 'src/plugins/kibana_utils/public'; -import { IUiSettingsClient as IUiSettingsClient_2 } from 'src/core/public'; import { KibanaClient } from '@elastic/elasticsearch/api/kibana'; import { Location } from 'history'; import { LocationDescriptorObject } from 'history'; import { Logger } from '@kbn/logging'; import { LogMeta } from '@kbn/logging'; import { MaybePromise } from '@kbn/utility-types'; -import { Moment } from 'moment'; -import { NameList } from 'elasticsearch'; import { NotificationsStart as NotificationsStart_2 } from 'src/core/public'; import { Observable } from 'rxjs'; import { Optional } from '@kbn/utility-types'; @@ -57,39 +38,23 @@ import { OverlayStart as OverlayStart_2 } from 'src/core/public'; import { PackageInfo } from '@kbn/config'; import { Path } from 'history'; import { PluginInitializerContext } from 'src/core/public'; -import { PluginInitializerContext as PluginInitializerContext_3 } from 'kibana/public'; import * as PropTypes from 'prop-types'; -import { PublicContract } from '@kbn/utility-types'; import { PublicMethodsOf } from '@kbn/utility-types'; import { PublicUiSettingsParams } from 'src/core/server/types'; import React from 'react'; import { RecursiveReadonly } from '@kbn/utility-types'; -import { RequestAdapter as RequestAdapter_2 } from 'src/plugins/inspector/common'; -import { Required } from '@kbn/utility-types'; import * as Rx from 'rxjs'; -import { SavedObject as SavedObject_2 } from 'kibana/server'; -import { SavedObject as SavedObject_3 } from 'src/core/server'; import { SavedObjectAttributes } from 'kibana/server'; import { SavedObjectAttributes as SavedObjectAttributes_2 } from 'src/core/public'; import { SavedObjectAttributes as SavedObjectAttributes_3 } from 'kibana/public'; -import { SavedObjectsClientContract as SavedObjectsClientContract_3 } from 'src/core/public'; -import { SavedObjectsFindOptions as SavedObjectsFindOptions_3 } from 'kibana/public'; -import { SavedObjectsFindResponse as SavedObjectsFindResponse_2 } from 'kibana/server'; -import { Search } from '@elastic/elasticsearch/api/requestParams'; -import { SearchResponse } from 'elasticsearch'; -import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; import { ShallowPromise } from '@kbn/utility-types'; import { SimpleSavedObject as SimpleSavedObject_2 } from 'src/core/public'; import { Start as Start_2 } from 'src/plugins/inspector/public'; -import { StartServicesAccessor as StartServicesAccessor_2 } from 'kibana/public'; -import { ToastInputFields as ToastInputFields_2 } from 'src/core/public/notifications'; -import { ToastsSetup as ToastsSetup_2 } from 'kibana/public'; import { TransportRequestOptions } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestParams } from '@elastic/elasticsearch/lib/Transport'; import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; import { TypeOf } from '@kbn/config-schema'; import { UiComponent } from 'src/plugins/kibana_utils/public'; -import { UiCounterMetricType } from '@kbn/analytics'; import { UnregisterCallback } from 'history'; import { UserProvidedValues } from 'src/core/server/types'; @@ -348,7 +313,7 @@ export abstract class Embeddable { +export class EmbeddableChildPanel extends React.Component { constructor(props: EmbeddableChildPanelProps); // (undocumented) [panel: string]: any; @@ -381,9 +346,9 @@ export interface EmbeddableChildPanelProps { // Warning: (ae-missing-release-tag) "EmbeddableContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export interface EmbeddableContext { +export interface EmbeddableContext { // (undocumented) - embeddable: IEmbeddable; + embeddable: T; } // @public @@ -444,9 +409,6 @@ export type EmbeddableInput = { enhancements?: SerializableState; disabledActions?: string[]; disableTriggers?: boolean; - timeRange?: TimeRange; - query?: Query; - filters?: Filter[]; searchSessionId?: string; }; @@ -501,7 +463,7 @@ export interface EmbeddablePackageState { // Warning: (ae-missing-release-tag) "EmbeddablePanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export class EmbeddablePanel extends React.Component { +export class EmbeddablePanel extends React.Component { constructor(props: Props); // (undocumented) closeMyContextMenuPanel: () => void; @@ -571,10 +533,6 @@ export interface EmbeddableSetup { // // @public (undocumented) export interface EmbeddableSetupDependencies { - // Warning: (ae-forgotten-export) The symbol "DataPublicPluginSetup" needs to be exported by the entry point index.d.ts - // - // (undocumented) - data: DataPublicPluginSetup; // Warning: (ae-forgotten-export) The symbol "UiActionsSetup" needs to be exported by the entry point index.d.ts // // (undocumented) @@ -610,10 +568,6 @@ export interface EmbeddableStart extends PersistableStateService context is EmbeddableContext; +export const isContextMenuTriggerContext: (context: unknown) => context is EmbeddableContext>; // Warning: (ae-missing-release-tag) "isEmbeddable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -869,6 +823,16 @@ export interface SavedObjectEmbeddableInput extends EmbeddableInput { savedObjectId: string; } +// Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER"; + +// Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER"; + // Warning: (ae-missing-release-tag) "ValueClickContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -912,10 +876,7 @@ export const withEmbeddableSubscription: { test('Explicit embeddable input mapped to undefined will default to inherited', async () => { const { start } = await creatHelloWorldContainerAndEmbeddable(); - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/tests/explicit_input.test.ts b/src/plugins/embeddable/public/tests/explicit_input.test.ts index 24785dd50a0322..531fbcee94db6e 100644 --- a/src/plugins/embeddable/public/tests/explicit_input.test.ts +++ b/src/plugins/embeddable/public/tests/explicit_input.test.ts @@ -20,6 +20,7 @@ import { skip } from 'rxjs/operators'; import { testPlugin } from './test_plugin'; import { + MockFilter, FILTERABLE_EMBEDDABLE, FilterableEmbeddableInput, } from '../lib/test_samples/embeddables/filterable_embeddable'; @@ -34,7 +35,6 @@ import { FilterableContainer } from '../lib/test_samples/embeddables/filterable_ import { isErrorEmbeddable } from '../lib'; import { HelloWorldContainer } from '../lib/test_samples/embeddables/hello_world_container'; import { coreMock } from '../../../../core/public/mocks'; -import { esFilters, Filter } from '../../../../plugins/data/public'; import { createEmbeddablePanelMock } from '../mocks'; const { setup, doStart, coreStart, uiActions } = testPlugin( @@ -56,8 +56,8 @@ setup.registerEmbeddableFactory( const start = doStart(); test('Explicit embeddable input mapped to undefined will default to inherited', async () => { - const derivedFilter: Filter = { - $state: { store: esFilters.FilterStateStore.APP_STATE }, + const derivedFilter: MockFilter = { + $state: { store: 'appState' }, meta: { disabled: false, alias: 'name', negate: false }, query: { match: {} }, }; diff --git a/src/plugins/embeddable/public/tests/test_plugin.ts b/src/plugins/embeddable/public/tests/test_plugin.ts index 2c298b437a118b..74bb70e913bccc 100644 --- a/src/plugins/embeddable/public/tests/test_plugin.ts +++ b/src/plugins/embeddable/public/tests/test_plugin.ts @@ -21,7 +21,6 @@ import { CoreSetup, CoreStart } from 'src/core/public'; import { UiActionsStart } from '../../../ui_actions/public'; import { uiActionsPluginMock } from '../../../ui_actions/public/mocks'; import { inspectorPluginMock } from '../../../inspector/public/mocks'; -import { dataPluginMock } from '../../../data/public/mocks'; import { coreMock } from '../../../../core/public/mocks'; import { EmbeddablePublicPlugin, EmbeddableSetup, EmbeddableStart } from '../plugin'; @@ -42,7 +41,6 @@ export const testPlugin = ( const initializerContext = {} as any; const plugin = new EmbeddablePublicPlugin(initializerContext); const setup = plugin.setup(coreSetup, { - data: dataPluginMock.createSetupContract(), uiActions: uiActions.setup, }); @@ -53,7 +51,6 @@ export const testPlugin = ( setup, doStart: (anotherCoreStart: CoreStart = coreStart) => { const start = plugin.start(anotherCoreStart, { - data: dataPluginMock.createStartContract(), inspector: inspectorPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), }); diff --git a/src/plugins/expressions/common/expression_renderers/types.ts b/src/plugins/expressions/common/expression_renderers/types.ts index 88aca4c07ee312..fca1694747ce2d 100644 --- a/src/plugins/expressions/common/expression_renderers/types.ts +++ b/src/plugins/expressions/common/expression_renderers/types.ts @@ -17,8 +17,6 @@ * under the License. */ -import { PersistedState } from 'src/plugins/visualizations/public'; - export interface ExpressionRenderDefinition { /** * Technical name of the renderer, used as ID to identify renderer in @@ -84,5 +82,10 @@ export interface IInterpreterRenderHandlers { event: (event: any) => void; hasCompatibleActions?: (event: any) => Promise; getRenderMode: () => RenderMode; - uiState?: PersistedState; + /** + * This uiState interface is actually `PersistedState` from the visualizations plugin, + * but expressions cannot know about vis or it creates a mess of circular dependencies. + * Downstream consumers of the uiState handler will need to cast for now. + */ + uiState?: unknown; } diff --git a/src/plugins/expressions/public/public.api.md b/src/plugins/expressions/public/public.api.md index bb1f5dd9270d5f..404df2db019a1e 100644 --- a/src/plugins/expressions/public/public.api.md +++ b/src/plugins/expressions/public/public.api.md @@ -12,7 +12,6 @@ import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; -import { PersistedState } from 'src/plugins/visualizations/public'; import { Plugin as Plugin_2 } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import React from 'react'; @@ -924,8 +923,7 @@ export interface IInterpreterRenderHandlers { onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; - // (undocumented) - uiState?: PersistedState; + uiState?: unknown; // (undocumented) update: (params: any) => void; } diff --git a/src/plugins/expressions/server/server.api.md b/src/plugins/expressions/server/server.api.md index 7c1ab11f75027c..8b8678371dd838 100644 --- a/src/plugins/expressions/server/server.api.md +++ b/src/plugins/expressions/server/server.api.md @@ -10,7 +10,6 @@ import { Ensure } from '@kbn/utility-types'; import { EventEmitter } from 'events'; import { KibanaRequest } from 'src/core/server'; import { Observable } from 'rxjs'; -import { PersistedState } from 'src/plugins/visualizations/public'; import { Plugin as Plugin_2 } from 'src/core/server'; import { PluginInitializerContext } from 'src/core/server'; import { UnwrapPromiseOrReturn } from '@kbn/utility-types'; @@ -741,8 +740,7 @@ export interface IInterpreterRenderHandlers { onDestroy: (fn: () => void) => void; // (undocumented) reload: () => void; - // (undocumented) - uiState?: PersistedState; + uiState?: unknown; // (undocumented) update: (params: any) => void; } diff --git a/src/plugins/ui_actions/public/index.ts b/src/plugins/ui_actions/public/index.ts index d223c0abcccb7a..7890e4bab44a3c 100644 --- a/src/plugins/ui_actions/public/index.ts +++ b/src/plugins/ui_actions/public/index.ts @@ -40,12 +40,6 @@ export { export { Trigger, TriggerContext, - SELECT_RANGE_TRIGGER, - selectRangeTrigger, - VALUE_CLICK_TRIGGER, - valueClickTrigger, - APPLY_FILTER_TRIGGER, - applyFilterTrigger, VISUALIZE_FIELD_TRIGGER, visualizeFieldTrigger, VISUALIZE_GEO_FIELD_TRIGGER, diff --git a/src/plugins/ui_actions/public/plugin.ts b/src/plugins/ui_actions/public/plugin.ts index fdb75e9a426e9e..84a7ae45fc7b81 100644 --- a/src/plugins/ui_actions/public/plugin.ts +++ b/src/plugins/ui_actions/public/plugin.ts @@ -20,14 +20,7 @@ import { CoreStart, CoreSetup, Plugin, PluginInitializerContext } from 'src/core/public'; import { PublicMethodsOf } from '@kbn/utility-types'; import { UiActionsService } from './service'; -import { - selectRangeTrigger, - valueClickTrigger, - rowClickTrigger, - applyFilterTrigger, - visualizeFieldTrigger, - visualizeGeoFieldTrigger, -} from './triggers'; +import { rowClickTrigger, visualizeFieldTrigger, visualizeGeoFieldTrigger } from './triggers'; export type UiActionsSetup = Pick< UiActionsService, @@ -47,10 +40,7 @@ export class UiActionsPlugin implements Plugin { constructor(initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup): UiActionsSetup { - this.service.registerTrigger(selectRangeTrigger); - this.service.registerTrigger(valueClickTrigger); this.service.registerTrigger(rowClickTrigger); - this.service.registerTrigger(applyFilterTrigger); this.service.registerTrigger(visualizeFieldTrigger); this.service.registerTrigger(visualizeGeoFieldTrigger); return this.service; diff --git a/src/plugins/ui_actions/public/public.api.md b/src/plugins/ui_actions/public/public.api.md index 2384dfab13c8c1..808cb1f3fbca03 100644 --- a/src/plugins/ui_actions/public/public.api.md +++ b/src/plugins/ui_actions/public/public.api.md @@ -8,14 +8,11 @@ import { CoreSetup } from 'src/core/public'; import { CoreStart } from 'src/core/public'; import { EnvironmentMode } from '@kbn/config'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; -import { EventEmitter } from 'events'; -import { Observable } from 'rxjs'; import { PackageInfo } from '@kbn/config'; import { Plugin } from 'src/core/public'; import { PluginInitializerContext as PluginInitializerContext_2 } from 'src/core/public'; import { PublicMethodsOf } from '@kbn/utility-types'; import React from 'react'; -import * as Rx from 'rxjs'; import { UiComponent } from 'src/plugins/kibana_utils/public'; // Warning: (ae-forgotten-export) The symbol "BaseContext" needs to be exported by the entry point index.d.ts @@ -95,16 +92,6 @@ export interface ActionExecutionMeta { // @public (undocumented) export type ActionType = keyof ActionContextMapping; -// Warning: (ae-missing-release-tag) "APPLY_FILTER_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const APPLY_FILTER_TRIGGER = "FILTER_TRIGGER"; - -// Warning: (ae-missing-release-tag) "applyFilterTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const applyFilterTrigger: Trigger<'FILTER_TRIGGER'>; - // Warning: (ae-forgotten-export) The symbol "BuildContextMenuParams" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "buildContextMenuForActions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -148,10 +135,8 @@ export interface RowClickContext { table: Datatable; columns?: string[]; }; - // Warning: (ae-forgotten-export) The symbol "IEmbeddable" needs to be exported by the entry point index.d.ts - // // (undocumented) - embeddable?: IEmbeddable; + embeddable?: unknown; } // Warning: (ae-missing-release-tag) "rowClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -159,16 +144,6 @@ export interface RowClickContext { // @public (undocumented) export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'>; -// Warning: (ae-missing-release-tag) "SELECT_RANGE_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const SELECT_RANGE_TRIGGER = "SELECT_RANGE_TRIGGER"; - -// Warning: (ae-missing-release-tag) "selectRangeTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'>; - // Warning: (ae-missing-release-tag) "Trigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public @@ -192,20 +167,8 @@ export interface TriggerContextMapping { // // (undocumented) [DEFAULT_TRIGGER]: TriggerContext_2; - // Warning: (ae-forgotten-export) The symbol "ApplyGlobalFilterActionContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; // (undocumented) [ROW_CLICK_TRIGGER]: RowClickContext; - // Warning: (ae-forgotten-export) The symbol "RangeSelectContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [SELECT_RANGE_TRIGGER]: RangeSelectContext; - // Warning: (ae-forgotten-export) The symbol "ValueClickContext" needs to be exported by the entry point index.d.ts - // - // (undocumented) - [VALUE_CLICK_TRIGGER]: ValueClickContext; // (undocumented) [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext; // (undocumented) @@ -262,35 +225,35 @@ export class UiActionsService { // // (undocumented) protected readonly actions: ActionRegistry; - readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; + readonly addTriggerAction: (triggerId: T, action: UiActionsActionDefinition | Action) => void; // (undocumented) - readonly attachAction: (triggerId: T, actionId: string) => void; + readonly attachAction: (triggerId: T, actionId: string) => void; readonly clear: () => void; // (undocumented) readonly detachAction: (triggerId: TriggerId, actionId: string) => void; // @deprecated (undocumented) - readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; + readonly executeTriggerActions: (triggerId: T, context: TriggerContext) => Promise; // Warning: (ae-forgotten-export) The symbol "UiActionsExecutionService" needs to be exported by the entry point index.d.ts // // (undocumented) readonly executionService: UiActionsExecutionService; readonly fork: () => UiActionsService; // (undocumented) - readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; + readonly getAction: >(id: string) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; // Warning: (ae-forgotten-export) The symbol "TriggerContract" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly getTrigger: (triggerId: T) => TriggerContract; + readonly getTrigger: (triggerId: T) => TriggerContract; // (undocumented) - readonly getTriggerActions: (triggerId: T) => Action[]; + readonly getTriggerActions: (triggerId: T) => Action[]; // (undocumented) - readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; + readonly getTriggerCompatibleActions: (triggerId: T, context: TriggerContextMapping[T]) => Promise[]>; // (undocumented) readonly hasAction: (actionId: string) => boolean; // Warning: (ae-forgotten-export) The symbol "ActionContext" needs to be exported by the entry point index.d.ts // // (undocumented) - readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel" | "togglePanel" | "replacePanel" | "clonePanel" | "addToFromLibrary" | "unlinkFromLibrary" | "ACTION_LIBRARY_NOTIFICATION" | "ACTION_EXPORT_CSV">; + readonly registerAction: >(definition: A) => Action, "" | "ACTION_VISUALIZE_FIELD" | "ACTION_VISUALIZE_GEO_FIELD" | "ACTION_VISUALIZE_LENS_FIELD" | "ACTION_GLOBAL_APPLY_FILTER" | "ACTION_SELECT_RANGE" | "ACTION_VALUE_CLICK" | "ACTION_CUSTOMIZE_PANEL" | "ACTION_ADD_PANEL" | "openInspector" | "deletePanel" | "editPanel">; // (undocumented) readonly registerTrigger: (trigger: Trigger) => void; // Warning: (ae-forgotten-export) The symbol "TriggerRegistry" needs to be exported by the entry point index.d.ts @@ -326,16 +289,6 @@ export type UiActionsSetup = Pick; -// Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const VALUE_CLICK_TRIGGER = "VALUE_CLICK_TRIGGER"; - -// Warning: (ae-missing-release-tag) "valueClickTrigger" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const valueClickTrigger: Trigger<'VALUE_CLICK_TRIGGER'>; - // Warning: (ae-missing-release-tag) "VISUALIZE_FIELD_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -371,7 +324,7 @@ export const visualizeGeoFieldTrigger: Trigger<'VISUALIZE_GEO_FIELD_TRIGGER'>; // Warnings were encountered during analysis: // -// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:45:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts +// src/plugins/ui_actions/public/triggers/row_click_trigger.ts:46:5 - (ae-forgotten-export) The symbol "Datatable" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/ui_actions/public/triggers/index.ts b/src/plugins/ui_actions/public/triggers/index.ts index ecbf4d1f7b9881..6bba87e85eb95e 100644 --- a/src/plugins/ui_actions/public/triggers/index.ts +++ b/src/plugins/ui_actions/public/triggers/index.ts @@ -20,10 +20,7 @@ export * from './trigger'; export * from './trigger_contract'; export * from './trigger_internal'; -export * from './select_range_trigger'; -export * from './value_click_trigger'; export * from './row_click_trigger'; -export * from './apply_filter_trigger'; export * from './visualize_field_trigger'; export * from './visualize_geo_field_trigger'; export * from './default_trigger'; diff --git a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts index 87bca03f8c3bab..0fc261b3e1fb35 100644 --- a/src/plugins/ui_actions/public/triggers/row_click_trigger.ts +++ b/src/plugins/ui_actions/public/triggers/row_click_trigger.ts @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import { IEmbeddable } from '../../../embeddable/public'; import { Trigger } from '.'; import { Datatable } from '../../../expressions'; @@ -35,7 +34,9 @@ export const rowClickTrigger: Trigger<'ROW_CLICK_TRIGGER'> = { }; export interface RowClickContext { - embeddable?: IEmbeddable; + // Need to make this unknown to prevent circular dependencies. + // Apps using this property will need to cast to `IEmbeddable`. + embeddable?: unknown; data: { /** * Row index, starting from 0, where user clicked. diff --git a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts b/src/plugins/ui_actions/public/triggers/select_range_trigger.ts deleted file mode 100644 index 312e75314bd926..00000000000000 --- a/src/plugins/ui_actions/public/triggers/select_range_trigger.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -import { Trigger } from '.'; - -export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; -export const selectRangeTrigger: Trigger<'SELECT_RANGE_TRIGGER'> = { - id: SELECT_RANGE_TRIGGER, - title: i18n.translate('uiActions.triggers.selectRangeTitle', { - defaultMessage: 'Range selection', - }), - description: i18n.translate('uiActions.triggers.selectRangeDescription', { - defaultMessage: 'A range of values on the visualization', - }), -}; diff --git a/src/plugins/ui_actions/public/triggers/trigger.ts b/src/plugins/ui_actions/public/triggers/trigger.ts index 2c019b09881d17..1b1231c132dde7 100644 --- a/src/plugins/ui_actions/public/triggers/trigger.ts +++ b/src/plugins/ui_actions/public/triggers/trigger.ts @@ -32,8 +32,7 @@ import { TriggerContextMapping, TriggerId } from '../types'; */ export interface Trigger { /** - * Unique name of the trigger as identified in `ui_actions` plugin trigger - * registry, such as "SELECT_RANGE_TRIGGER" or "VALUE_CLICK_TRIGGER". + * Unique name of the trigger as identified in `ui_actions` plugin trigger registry. */ id: ID; diff --git a/src/plugins/ui_actions/public/triggers/trigger_contract.ts b/src/plugins/ui_actions/public/triggers/trigger_contract.ts index 04a75cb3a53d08..7e7fba0ba80d3a 100644 --- a/src/plugins/ui_actions/public/triggers/trigger_contract.ts +++ b/src/plugins/ui_actions/public/triggers/trigger_contract.ts @@ -25,8 +25,7 @@ import { TriggerId, TriggerContextMapping } from '../types'; */ export class TriggerContract { /** - * Unique name of the trigger as identified in `ui_actions` plugin trigger - * registry, such as "SELECT_RANGE_TRIGGER" or "VALUE_CLICK_TRIGGER". + * Unique name of the trigger as identified in `ui_actions` plugin trigger registry. */ public readonly id: T; diff --git a/src/plugins/ui_actions/public/types.ts b/src/plugins/ui_actions/public/types.ts index 0266a755be9269..62fac245514cdd 100644 --- a/src/plugins/ui_actions/public/types.ts +++ b/src/plugins/ui_actions/public/types.ts @@ -20,17 +20,12 @@ import { ActionInternal } from './actions/action_internal'; import { TriggerInternal } from './triggers/trigger_internal'; import { - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, ROW_CLICK_TRIGGER, - APPLY_FILTER_TRIGGER, VISUALIZE_FIELD_TRIGGER, VISUALIZE_GEO_FIELD_TRIGGER, DEFAULT_TRIGGER, RowClickContext, } from './triggers'; -import type { RangeSelectContext, ValueClickContext } from '../../embeddable/public'; -import type { ApplyGlobalFilterActionContext } from '../../data/public'; export type TriggerRegistry = Map>; export type ActionRegistry = Map; @@ -49,10 +44,7 @@ export type TriggerContext = BaseContext; export interface TriggerContextMapping { [DEFAULT_TRIGGER]: TriggerContext; - [SELECT_RANGE_TRIGGER]: RangeSelectContext; - [VALUE_CLICK_TRIGGER]: ValueClickContext; [ROW_CLICK_TRIGGER]: RowClickContext; - [APPLY_FILTER_TRIGGER]: ApplyGlobalFilterActionContext; [VISUALIZE_FIELD_TRIGGER]: VisualizeFieldContext; [VISUALIZE_GEO_FIELD_TRIGGER]: VisualizeFieldContext; } diff --git a/src/plugins/vis_type_table/public/components/table_visualization.tsx b/src/plugins/vis_type_table/public/components/table_visualization.tsx index 2d38acc57519f2..a1e4fad719dc42 100644 --- a/src/plugins/vis_type_table/public/components/table_visualization.tsx +++ b/src/plugins/vis_type_table/public/components/table_visualization.tsx @@ -23,6 +23,7 @@ import classNames from 'classnames'; import { CoreStart } from 'kibana/public'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import type { PersistedState } from 'src/plugins/visualizations/public'; import { KibanaContextProvider } from '../../../kibana_react/public'; import { TableVisConfig } from '../types'; import { TableContext } from '../table_vis_response_handler'; @@ -47,7 +48,7 @@ const TableVisualizationComponent = ({ handlers.done(); }, [handlers]); - const uiStateProps = useUiState(handlers.uiState); + const uiStateProps = useUiState(handlers.uiState as PersistedState); const className = classNames('tbvChart', { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts b/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts index 68bad16972ec24..cc43fc6bcb582d 100644 --- a/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts +++ b/src/plugins/vis_type_table/public/utils/use/use_ui_state.ts @@ -19,7 +19,7 @@ import { debounce, isEqual } from 'lodash'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import type { PersistedState } from 'src/plugins/visualizations/public'; import { ColumnWidthData, TableVisUiState, TableVisUseUiStateProps } from '../../types'; @@ -28,9 +28,7 @@ const defaultSort = { direction: null, }; -export const useUiState = ( - uiState: IInterpreterRenderHandlers['uiState'] -): TableVisUseUiStateProps => { +export const useUiState = (uiState: PersistedState): TableVisUseUiStateProps => { const [sort, setSortState] = useState( uiState?.get('vis.params.sort') || defaultSort ); diff --git a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx index 67ed487d293783..dec169c59c6ef7 100644 --- a/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx +++ b/src/plugins/vis_type_timeseries/public/timeseries_vis_renderer.tsx @@ -21,6 +21,7 @@ import React, { lazy } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { IUiSettingsClient } from 'kibana/public'; +import type { PersistedState } from '../../visualizations/public'; import { VisualizationContainer } from '../../visualizations/public'; import { ExpressionRenderDefinition } from '../../expressions/common/expression_renderers'; import { TimeseriesRenderValue, TimeseriesVisParams } from './metrics_fn'; @@ -63,7 +64,7 @@ export const getTimeseriesVisRenderer: (deps: { handlers={handlers} model={config.visParams} visData={config.visData as TimeseriesVisData} - uiState={handlers.uiState!} + uiState={handlers.uiState! as PersistedState} /> , domNode diff --git a/src/plugins/vis_type_vislib/public/vis_controller.tsx b/src/plugins/vis_type_vislib/public/vis_controller.tsx index 1804d0d52ae7a6..2a32d19874c228 100644 --- a/src/plugins/vis_type_vislib/public/vis_controller.tsx +++ b/src/plugins/vis_type_vislib/public/vis_controller.tsx @@ -22,7 +22,7 @@ import React, { RefObject } from 'react'; import { mountReactNode } from '../../../core/public/utils'; import { ChartsPluginSetup } from '../../charts/public'; -import { PersistedState } from '../../visualizations/public'; +import type { PersistedState } from '../../visualizations/public'; import { IInterpreterRenderHandlers } from '../../expressions/public'; import { VisTypeVislibCoreSetup } from './plugin'; @@ -115,7 +115,7 @@ export const createVislibVisController = ( }) .addClass((legendClassName as any)[visParams.legendPosition]); - this.mountLegend(esResponse, visParams, fireEvent, uiState); + this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); } this.vislibVis.render(esResponse, uiState); @@ -128,7 +128,7 @@ export const createVislibVisController = ( CUSTOM_LEGEND_VIS_TYPES.includes(this.vislibVis.visConfigArgs.type) ) { this.unmountLegend?.(); - this.mountLegend(esResponse, visParams, fireEvent, uiState); + this.mountLegend(esResponse, visParams, fireEvent, uiState as PersistedState); this.vislibVis.render(esResponse, uiState); } } diff --git a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx index 280a957396c348..b8dbd0f945c325 100644 --- a/src/plugins/vis_type_vislib/public/vis_wrapper.tsx +++ b/src/plugins/vis_type_vislib/public/vis_wrapper.tsx @@ -22,6 +22,7 @@ import { EuiResizeObserver } from '@elastic/eui'; import { debounce } from 'lodash'; import { IInterpreterRenderHandlers } from '../../expressions/public'; +import type { PersistedState } from '../../visualizations/public'; import { ChartsPluginSetup } from '../../charts/public'; import { VislibRenderValue } from './vis_type_vislib_vis_fn'; @@ -66,10 +67,12 @@ const VislibWrapper = ({ core, charts, visData, visConfig, handlers }: VislibWra useEffect(() => { if (handlers.uiState) { - handlers.uiState.on('change', updateChart); + const uiState = handlers.uiState as PersistedState; + + uiState.on('change', updateChart); return () => { - handlers.uiState?.off('change', updateChart); + uiState?.off('change', updateChart); }; } }, [handlers.uiState, updateChart]); diff --git a/src/plugins/vis_type_xy/public/vis_component.tsx b/src/plugins/vis_type_xy/public/vis_component.tsx index d87500218975a5..dcf9f69cc291d8 100644 --- a/src/plugins/vis_type_xy/public/vis_component.tsx +++ b/src/plugins/vis_type_xy/public/vis_component.tsx @@ -50,6 +50,7 @@ import { ClickTriggerEvent, } from '../../charts/public'; import { Datatable, IInterpreterRenderHandlers } from '../../expressions/public'; +import type { PersistedState } from '../../visualizations/public'; import { VisParams } from './types'; import { @@ -77,7 +78,7 @@ import { export interface VisComponentProps { visParams: VisParams; visData: Datatable; - uiState: IInterpreterRenderHandlers['uiState']; + uiState: PersistedState; fireEvent: IInterpreterRenderHandlers['event']; renderComplete: IInterpreterRenderHandlers['done']; } diff --git a/src/plugins/vis_type_xy/public/vis_renderer.tsx b/src/plugins/vis_type_xy/public/vis_renderer.tsx index 4ac7c23c30893a..16463abb07631e 100644 --- a/src/plugins/vis_type_xy/public/vis_renderer.tsx +++ b/src/plugins/vis_type_xy/public/vis_renderer.tsx @@ -24,6 +24,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { ExpressionRenderDefinition } from '../../expressions/public'; import { VisualizationContainer } from '../../visualizations/public'; +import type { PersistedState } from '../../visualizations/public'; import { XyVisType } from '../common'; import { SplitChartWarning } from './components/split_chart_warning'; @@ -59,7 +60,7 @@ export const xyVisRenderer: ExpressionRenderDefinition = { visData={visData} renderComplete={handlers.done} fireEvent={handlers.event} - uiState={handlers.uiState} + uiState={handlers.uiState as PersistedState} /> diff --git a/src/plugins/visualizations/public/embeddable/events.ts b/src/plugins/visualizations/public/embeddable/events.ts index 41e52c3ac13272..7065d758bbc490 100644 --- a/src/plugins/visualizations/public/embeddable/events.ts +++ b/src/plugins/visualizations/public/embeddable/events.ts @@ -17,12 +17,9 @@ * under the License. */ -import { - APPLY_FILTER_TRIGGER, - SELECT_RANGE_TRIGGER, - VALUE_CLICK_TRIGGER, - ROW_CLICK_TRIGGER, -} from '../../../ui_actions/public'; +import { ROW_CLICK_TRIGGER } from '../../../ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../plugins/data/public'; +import { SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER } from '../../../../plugins/embeddable/public'; export interface VisEventToTrigger { ['applyFilter']: typeof APPLY_FILTER_TRIGGER; diff --git a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts index f7592977858d25..5661acc26fdb64 100644 --- a/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -71,6 +71,9 @@ export interface VisualizeInput extends EmbeddableInput { }; savedVis?: SerializedVis; table?: unknown; + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; } export interface VisualizeOutput extends EmbeddableOutput { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx index 48b64a1c845883..27c63aba791dd3 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/containers/drilldowns_with_embeddable_example/drilldowns_with_embeddable_example.tsx @@ -18,10 +18,12 @@ import { EuiFlexGroup, } from '@elastic/eui'; import { SampleMlJob, SampleApp1ClickContext } from '../../triggers'; -import { EmbeddableRoot } from '../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableRoot, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { ButtonEmbeddable } from '../../embeddables/button_embeddable'; import { useUiActions } from '../../context'; -import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; export const job: SampleMlJob = { job_id: '123', diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx index a7324f55301307..50ad350cd90b90 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_drilldown/index.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; -import { ChartActionContext } from '../../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; import { + ChartActionContext, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; +import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; export type ActionContext = ChartActionContext; diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx index 24385bd6baa42f..4e5b3187af42b5 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_hello_world_only_range_select_drilldown/index.tsx @@ -8,9 +8,11 @@ import React from 'react'; import { EuiFormRow, EuiFieldText } from '@elastic/eui'; import { reactToUiComponent } from '../../../../../../src/plugins/kibana_react/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; -import { RangeSelectContext } from '../../../../../../src/plugins/embeddable/public'; +import { + RangeSelectContext, + SELECT_RANGE_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; -import { SELECT_RANGE_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; import { BaseActionFactoryContext } from '../../../../../plugins/ui_actions_enhanced/public/dynamic_actions'; export type Config = { diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx index 9cda534a340d66..2f161efe6f3889 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/drilldown.tsx @@ -13,7 +13,7 @@ import { CollectConfigContainer } from './collect_config_container'; import { SAMPLE_DASHBOARD_TO_DISCOVER_DRILLDOWN } from './constants'; import { UiActionsEnhancedDrilldownDefinition as Drilldown } from '../../../../../plugins/ui_actions_enhanced/public'; import { txtGoToDiscover } from './i18n'; -import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/data/public'; const isOutputWithIndexPatterns = ( output: unknown diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts index f0497780430d4a..7c8915c3f143fd 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/drilldowns/dashboard_to_discover_drilldown/types.ts @@ -6,8 +6,9 @@ import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { ApplyGlobalFilterActionContext } from '../../../../../../src/plugins/data/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; -export type ActionContext = ApplyGlobalFilterActionContext; +export type ActionContext = ApplyGlobalFilterActionContext & { embeddable: IEmbeddable }; export type Config = { /** diff --git a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts index fcd0c9b94c988c..e58ab5b8ea9d25 100644 --- a/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts +++ b/x-pack/examples/ui_actions_enhanced_examples/public/embeddables/button_embeddable/button_embeddable.ts @@ -7,9 +7,12 @@ import { createElement } from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { AdvancedUiActionsStart } from '../../../../../plugins/ui_actions_enhanced/public'; -import { Embeddable, EmbeddableInput } from '../../../../../../src/plugins/embeddable/public'; +import { + Embeddable, + EmbeddableInput, + VALUE_CLICK_TRIGGER, +} from '../../../../../../src/plugins/embeddable/public'; import { ButtonEmbeddableComponent } from './button_embeddable_component'; -import { VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; export const BUTTON_EMBEDDABLE = 'BUTTON_EMBEDDABLE'; diff --git a/x-pack/plugins/dashboard_enhanced/kibana.json b/x-pack/plugins/dashboard_enhanced/kibana.json index b24c0b6983f407..d3a1f29f0c7ffd 100644 --- a/x-pack/plugins/dashboard_enhanced/kibana.json +++ b/x-pack/plugins/dashboard_enhanced/kibana.json @@ -9,7 +9,6 @@ "embeddable", "kibanaUtils", "embeddableEnhanced", - "kibanaReact", - "uiActions" + "kibanaReact" ] } diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts index 7f5137812ee324..d2d3c37a692874 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/abstract_dashboard_drilldown/types.ts @@ -5,7 +5,7 @@ */ import { UiActionsEnhancedBaseActionFactoryContext } from '../../../../../ui_actions_enhanced/public'; -import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/data/public'; import { DrilldownConfig } from '../../../../common'; export type Config = DrilldownConfig; diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts index e1d8a2b3671a25..ff79cda1bb2158 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/actions/drilldown_shared.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { APPLY_FILTER_TRIGGER } from '../../../../../../../src/plugins/data/public'; import { - APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, - TriggerId, VALUE_CLICK_TRIGGER, -} from '../../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../../src/plugins/embeddable/public'; +import { TriggerId } from '../../../../../../../src/plugins/ui_actions/public'; /** * We know that VALUE_CLICK_TRIGGER and SELECT_RANGE_TRIGGER are also triggering APPLY_FILTER_TRIGGER. diff --git a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx index 921c2aed00624a..c2bf48188c313d 100644 --- a/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx +++ b/x-pack/plugins/dashboard_enhanced/public/services/drilldowns/embeddable_to_dashboard_drilldown/embeddable_to_dashboard_drilldown.tsx @@ -4,17 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - TriggerContextMapping, - APPLY_FILTER_TRIGGER, -} from '../../../../../../../src/plugins/ui_actions/public'; +import { TriggerContextMapping } from '../../../../../../../src/plugins/ui_actions/public'; import { DashboardUrlGeneratorState } from '../../../../../../../src/plugins/dashboard/public'; import { + APPLY_FILTER_TRIGGER, esFilters, + Filter, isFilters, isQuery, isTimeRange, + Query, + TimeRange, } from '../../../../../../../src/plugins/data/public'; +import { IEmbeddable, EmbeddableInput } from '../../../../../../../src/plugins/embeddable/public'; import { AbstractDashboardDrilldown, AbstractDashboardDrilldownParams, @@ -24,6 +26,12 @@ import { KibanaURL } from '../../../../../../../src/plugins/share/public'; import { EMBEDDABLE_TO_DASHBOARD_DRILLDOWN } from './constants'; import { createExtract, createInject } from '../../../../common'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + type Trigger = typeof APPLY_FILTER_TRIGGER; type Context = TriggerContextMapping[Trigger]; export type Params = AbstractDashboardDrilldownParams; @@ -46,7 +54,8 @@ export class EmbeddableToDashboardDrilldown extends AbstractDashboardDrilldown; + const input = embeddable.getInput(); if (isQuery(input.query) && config.useCurrentFilters) state.query = input.query; // if useCurrentDashboardDataRange is enabled, then preserve current time range diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts index 52946b3dca7a1f..2f983ba2476254 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_chart_action.ts @@ -13,11 +13,14 @@ import { ApplyGlobalFilterActionContext, esFilters, } from '../../../../../../src/plugins/data/public'; +import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public'; import { KibanaURL } from '../../../../../../src/plugins/share/public'; import * as shared from './shared'; import { AbstractExploreDataAction } from './abstract_explore_data_action'; -export type ExploreDataChartActionContext = ApplyGlobalFilterActionContext; +export interface ExploreDataChartActionContext extends ApplyGlobalFilterActionContext { + embeddable?: IEmbeddable; +} export const ACTION_EXPLORE_DATA_CHART = 'ACTION_EXPLORE_DATA_CHART'; diff --git a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts index fdd096e718339d..fda07fb47ab0d7 100644 --- a/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts +++ b/x-pack/plugins/discover_enhanced/public/actions/explore_data/explore_data_context_menu_action.ts @@ -5,12 +5,25 @@ */ import { Action } from '../../../../../../src/plugins/ui_actions/public'; -import { EmbeddableContext } from '../../../../../../src/plugins/embeddable/public'; +import { + EmbeddableContext, + EmbeddableInput, + IEmbeddable, +} from '../../../../../../src/plugins/embeddable/public'; +import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/public'; import { DiscoverUrlGeneratorState } from '../../../../../../src/plugins/discover/public'; import { KibanaURL } from '../../../../../../src/plugins/share/public'; import * as shared from './shared'; import { AbstractExploreDataAction } from './abstract_explore_data_action'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + +type EmbeddableQueryContext = EmbeddableContext>; + export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; /** @@ -18,15 +31,15 @@ export const ACTION_EXPLORE_DATA = 'ACTION_EXPLORE_DATA'; * menu of a dashboard panel. */ export class ExploreDataContextMenuAction - extends AbstractExploreDataAction - implements Action { + extends AbstractExploreDataAction + implements Action { public readonly id = ACTION_EXPLORE_DATA; public readonly type = ACTION_EXPLORE_DATA; public readonly order = 200; - protected readonly getUrl = async (context: EmbeddableContext): Promise => { + protected readonly getUrl = async (context: EmbeddableQueryContext): Promise => { const { plugins } = this.params.start(); const { urlGenerator } = plugins.discover; diff --git a/x-pack/plugins/discover_enhanced/public/plugin.ts b/x-pack/plugins/discover_enhanced/public/plugin.ts index f1273ab00bdd40..78f3464484ccf3 100644 --- a/x-pack/plugins/discover_enhanced/public/plugin.ts +++ b/x-pack/plugins/discover_enhanced/public/plugin.ts @@ -6,11 +6,8 @@ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; import { PluginInitializerContext } from 'kibana/public'; -import { - UiActionsSetup, - UiActionsStart, - APPLY_FILTER_TRIGGER, -} from '../../../../src/plugins/ui_actions/public'; +import { UiActionsSetup, UiActionsStart } from '../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../src/plugins/data/public'; import { createStartServicesGetter } from '../../../../src/plugins/kibana_utils/public'; import { DiscoverSetup, DiscoverStart } from '../../../../src/plugins/discover/public'; import { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts index e0627c521bb795..23e73bea5dfec6 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/test/data.ts @@ -5,6 +5,7 @@ */ import { DatatableColumnType } from '../../../../../../../src/plugins/expressions/common'; +import { Query, Filter, TimeRange } from '../../../../../../../src/plugins/data/public'; import { Embeddable, EmbeddableInput, @@ -159,6 +160,9 @@ export const rowClickData = { interface TestInput extends EmbeddableInput { savedObjectId?: string; + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; } interface TestOutput extends EmbeddableOutput { diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts index 24406cefce7a21..e3730084d70207 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.test.ts @@ -6,14 +6,11 @@ import { IExternalUrl } from 'src/core/public'; import { UrlDrilldown, ActionContext, Config } from './url_drilldown'; -import { IEmbeddable } from '../../../../../../src/plugins/embeddable/public/lib/embeddables'; +import { IEmbeddable, VALUE_CLICK_TRIGGER } from '../../../../../../src/plugins/embeddable/public'; import { DatatableColumnType } from '../../../../../../src/plugins/expressions/common'; import { of } from '../../../../../../src/plugins/kibana_utils'; import { createPoint, rowClickData, TestEmbeddable } from './test/data'; -import { - VALUE_CLICK_TRIGGER, - ROW_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +import { ROW_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; const mockDataPoints = [ { diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx index 8dfb2c54c5ab02..bfeab263d20e31 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown.tsx @@ -12,13 +12,13 @@ import { ChartActionContext, CONTEXT_MENU_TRIGGER, IEmbeddable, -} from '../../../../../../src/plugins/embeddable/public'; -import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; -import { - ROW_CLICK_TRIGGER, + EmbeddableInput, SELECT_RANGE_TRIGGER, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; +import { ROW_CLICK_TRIGGER } from '../../../../../../src/plugins/ui_actions/public'; +import { Query, Filter, TimeRange } from '../../../../../../src/plugins/data/public'; +import { CollectConfigProps as CollectConfigPropsBase } from '../../../../../../src/plugins/kibana_utils/public'; import { UiActionsEnhancedDrilldownDefinition as Drilldown, UrlDrilldownGlobalScope, @@ -31,6 +31,15 @@ import { import { getPanelVariables, getEventScope, getEventVariableList } from './url_drilldown_scope'; import { txtUrlDrilldownDisplayName } from './i18n'; +interface EmbeddableQueryInput extends EmbeddableInput { + query?: Query; + filters?: Filter[]; + timeRange?: TimeRange; +} + +/** @internal */ +export type EmbeddableWithQueryInput = IEmbeddable; + interface UrlDrilldownDeps { externalUrl: IExternalUrl; getGlobalScope: () => UrlDrilldownGlobalScope; @@ -39,7 +48,7 @@ interface UrlDrilldownDeps { getVariablesHelpDocsLink: () => string; } -export type ActionContext = ChartActionContext; +export type ActionContext = ChartActionContext; export type Config = UrlDrilldownConfig; export type UrlTrigger = | typeof VALUE_CLICK_TRIGGER @@ -48,7 +57,7 @@ export type UrlTrigger = | typeof CONTEXT_MENU_TRIGGER; export interface ActionFactoryContext extends BaseActionFactoryContext { - embeddable?: IEmbeddable; + embeddable?: EmbeddableWithQueryInput; } export type CollectConfigProps = CollectConfigPropsBase; diff --git a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts index 3e5fc0a968d39c..12b74d475e9322 100644 --- a/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts +++ b/x-pack/plugins/drilldowns/url_drilldown/public/lib/url_drilldown_scope.ts @@ -11,20 +11,24 @@ import type { Filter, Query, TimeRange } from '../../../../../../src/plugins/data/public'; import { - IEmbeddable, isRangeSelectTriggerContext, isValueClickTriggerContext, isRowClickTriggerContext, isContextMenuTriggerContext, RangeSelectContext, + SELECT_RANGE_TRIGGER, ValueClickContext, + VALUE_CLICK_TRIGGER, + EmbeddableInput, EmbeddableOutput, } from '../../../../../../src/plugins/embeddable/public'; -import type { ActionContext, ActionFactoryContext } from './url_drilldown'; +import type { + ActionContext, + ActionFactoryContext, + EmbeddableWithQueryInput, +} from './url_drilldown'; import { - SELECT_RANGE_TRIGGER, RowClickContext, - VALUE_CLICK_TRIGGER, ROW_CLICK_TRIGGER, } from '../../../../../../src/plugins/ui_actions/public'; @@ -32,7 +36,7 @@ import { * Part of context scope extracted from an embeddable * Expose on the scope as: `{{context.panel.id}}`, `{{context.panel.filters.[0]}}` */ -interface EmbeddableUrlDrilldownContextScope { +interface EmbeddableUrlDrilldownContextScope extends EmbeddableInput { /** * ID of the embeddable panel. */ @@ -59,10 +63,8 @@ interface EmbeddableUrlDrilldownContextScope { savedObjectId?: string; } -export function getPanelVariables(contextScopeInput: { - embeddable?: IEmbeddable; -}): EmbeddableUrlDrilldownContextScope { - function hasEmbeddable(val: unknown): val is { embeddable: IEmbeddable } { +export function getPanelVariables(contextScopeInput: unknown): EmbeddableUrlDrilldownContextScope { + function hasEmbeddable(val: unknown): val is { embeddable: EmbeddableWithQueryInput } { if (val && typeof val === 'object' && 'embeddable' in val) return true; return false; } @@ -99,7 +101,7 @@ function getIndexPatternIds(output: EmbeddableOutput): string[] { } export function getEmbeddableVariables( - embeddable: IEmbeddable + embeddable: EmbeddableWithQueryInput ): EmbeddableUrlDrilldownContextScope { const input = embeddable.getInput(); const output = embeddable.getOutput(); @@ -195,10 +197,10 @@ function getEventScopeFromValueClickTriggerContext( }); } -function getEventScopeFromRowClickTriggerContext({ - embeddable, - data, -}: RowClickContext): RowClickTriggerEventScope { +function getEventScopeFromRowClickTriggerContext(ctx: RowClickContext): RowClickTriggerEventScope { + const { data } = ctx; + const embeddable = ctx.embeddable as EmbeddableWithQueryInput; + const { rowIndex } = data; const columns = data.columns || data.table.columns.map(({ id }) => id); const values: Primitive[] = []; diff --git a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts index 9856d3a558e24a..8361b002c8206b 100644 --- a/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts +++ b/x-pack/plugins/embeddable_enhanced/public/embeddables/embeddable_action_storage.test.ts @@ -12,7 +12,7 @@ import { import { UiActionsEnhancedSerializedEvent } from '../../../ui_actions_enhanced/public'; import { of } from '../../../../../src/plugins/kibana_utils/public'; // use real const to make test fail in case someone accidentally changes it -import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../src/plugins/data/public'; class TestEmbeddable extends Embeddable { public readonly type = 'test'; diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx index 190470e6c42357..b00760e9664f35 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable.tsx @@ -53,11 +53,17 @@ import { LensAttributeService } from '../../lens_attribute_service'; export type LensSavedObjectAttributes = Omit; +interface LensBaseEmbeddableInput extends EmbeddableInput { + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; +} + export type LensByValueInput = { attributes: LensSavedObjectAttributes; -} & EmbeddableInput; +} & LensBaseEmbeddableInput; -export type LensByReferenceInput = SavedObjectEmbeddableInput & EmbeddableInput; +export type LensByReferenceInput = SavedObjectEmbeddableInput & LensBaseEmbeddableInput; export type LensEmbeddableInput = (LensByValueInput | LensByReferenceInput) & { palette?: PaletteOutput; renderMode?: RenderMode; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index d5283822107e47..57308f9c18b403 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -20,12 +20,11 @@ import { DragContextState } from './drag_drop'; import { Document } from './persistence'; import { DateRange } from '../common'; import { Query, Filter, SavedQuery, IFieldFormat } from '../../../../src/plugins/data/public'; +import { TriggerContext, VisualizeFieldContext } from '../../../../src/plugins/ui_actions/public'; import { SELECT_RANGE_TRIGGER, - TriggerContext, VALUE_CLICK_TRIGGER, - VisualizeFieldContext, -} from '../../../../src/plugins/ui_actions/public'; +} from '../../../../src/plugins/embeddable/public'; import type { LensSortActionData, LENS_EDIT_SORT_ACTION, diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 7aaabc427790af..5e5d431c7c2129 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -14,15 +14,15 @@ import { Embeddable, IContainer, ReferenceOrValueEmbeddable, + VALUE_CLICK_TRIGGER, } from '../../../../../src/plugins/embeddable/public'; -import { ACTION_GLOBAL_APPLY_FILTER } from '../../../../../src/plugins/data/public'; import { - APPLY_FILTER_TRIGGER, - VALUE_CLICK_TRIGGER, ActionExecutionContext, TriggerContextMapping, } from '../../../../../src/plugins/ui_actions/public'; import { + ACTION_GLOBAL_APPLY_FILTER, + APPLY_FILTER_TRIGGER, esFilters, TimeRange, Filter, diff --git a/x-pack/plugins/maps/public/embeddable/types.ts b/x-pack/plugins/maps/public/embeddable/types.ts index a401cafcff8ea6..417991ea2d367e 100644 --- a/x-pack/plugins/maps/public/embeddable/types.ts +++ b/x-pack/plugins/maps/public/embeddable/types.ts @@ -10,7 +10,7 @@ import { EmbeddableOutput, SavedObjectEmbeddableInput, } from '../../../../../src/plugins/embeddable/public'; -import { RefreshInterval } from '../../../../../src/plugins/data/common'; +import { RefreshInterval, Query, Filter, TimeRange } from '../../../../../src/plugins/data/common'; import { MapCenterAndZoom } from '../../common/descriptor_types'; import { MapSavedObjectAttributes } from '../../common/map_saved_object_type'; @@ -30,6 +30,9 @@ interface MapEmbeddableState { mapCenter?: MapCenterAndZoom; hiddenLayers?: string[]; hideFilterActions?: boolean; + filters?: Filter[]; + query?: Query; + timeRange?: TimeRange; } export type MapByValueInput = { attributes: MapSavedObjectAttributes; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 1023c7ac7d889d..e38058a312f3c0 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3490,12 +3490,12 @@ "uiActions.actionPanel.more": "詳細", "uiActions.actionPanel.title": "オプション", "uiActions.errors.incompatibleAction": "操作に互換性がありません", - "uiActions.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。", - "uiActions.triggers.applyFilterTitle": "フィルターを適用", - "uiActions.triggers.selectRangeDescription": "ビジュアライゼーションでの値の範囲", - "uiActions.triggers.selectRangeTitle": "範囲選択", - "uiActions.triggers.valueClickDescription": "ビジュアライゼーションでデータポイントをクリック", - "uiActions.triggers.valueClickTitle": "シングルクリック", + "data.triggers.applyFilterDescription": "Kibanaフィルターが適用されるとき。単一の値または範囲フィルターにすることができます。", + "data.triggers.applyFilterTitle": "フィルターを適用", + "embeddableApi.selectRangeTrigger.description": "ビジュアライゼーションでの値の範囲", + "embeddableApi.selectRangeTrigger.title": "範囲選択", + "embeddableApi.valueClickTrigger.description": "ビジュアライゼーションでデータポイントをクリック", + "embeddableApi.valueClickTrigger.title": "シングルクリック", "usageCollection.stats.notReadyMessage": "まだ統計が準備できていません。しばらくたってから再試行してください。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高度な設定", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "{schema} エディターを切り替える", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f803ff48df76d3..17bcd61898efe9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3491,12 +3491,12 @@ "uiActions.actionPanel.more": "更多", "uiActions.actionPanel.title": "选项", "uiActions.errors.incompatibleAction": "操作不兼容", - "uiActions.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。", - "uiActions.triggers.applyFilterTitle": "应用筛选", - "uiActions.triggers.selectRangeDescription": "可视化上的一组值", - "uiActions.triggers.selectRangeTitle": "范围选择", - "uiActions.triggers.valueClickDescription": "可视化上的数据点单击", - "uiActions.triggers.valueClickTitle": "单击", + "data.triggers.applyFilterDescription": "应用 kibana 筛选时。可能是单个值或范围筛选。", + "data.triggers.applyFilterTitle": "应用筛选", + "embeddableApi.selectRangeTrigger.description": "可视化上的一组值", + "embeddableApi.selectRangeTrigger.title": "范围选择", + "embeddableApi.valueClickTrigger.description": "可视化上的数据点单击", + "embeddableApi.valueClickTrigger.title": "单击", "usageCollection.stats.notReadyMessage": "统计信息尚未准备就绪。请稍后重试。", "visDefaultEditor.advancedToggle.advancedLinkLabel": "高级", "visDefaultEditor.agg.toggleEditorButtonAriaLabel": "切换 {schema} 编辑器", diff --git a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx index 9bb506b3ebf148..77362752f6960f 100644 --- a/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx +++ b/x-pack/plugins/ui_actions_enhanced/public/components/action_wizard/test_data.tsx @@ -11,13 +11,12 @@ import { ActionWizard } from './action_wizard'; import { ActionFactory, ActionFactoryDefinition, BaseActionConfig } from '../../dynamic_actions'; import { CollectConfigProps } from '../../../../../../src/plugins/kibana_utils/public'; import { licensingMock } from '../../../../licensing/public/mocks'; +import { Trigger, TriggerId } from '../../../../../../src/plugins/ui_actions/public'; +import { APPLY_FILTER_TRIGGER } from '../../../../../../src/plugins/data/public'; import { - APPLY_FILTER_TRIGGER, SELECT_RANGE_TRIGGER, - Trigger, - TriggerId, VALUE_CLICK_TRIGGER, -} from '../../../../../../src/plugins/ui_actions/public'; +} from '../../../../../../src/plugins/embeddable/public'; export const dashboards = [ { id: 'dashboard1', title: 'Dashboard 1' }, From e8b21bc6c12cfd793c46e1d86577d5e5ec8a71f8 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sat, 19 Dec 2020 13:10:11 +0100 Subject: [PATCH 28/40] Upgrade to hapi version 20 (#85406) --- ...in-core-server.legacyelasticsearcherror.md | 2 +- package.json | 35 +- .../server/elasticsearch/legacy/errors.ts | 4 +- src/core/server/http/http_server.test.ts | 4 +- src/core/server/http/http_server.ts | 58 +- .../server/http/lifecycle/on_pre_response.ts | 12 +- src/core/server/http/router/error_wrapper.ts | 2 +- .../server/http/router/response_adapter.ts | 7 +- src/core/server/http/router/router.ts | 5 +- .../saved_objects/service/lib/errors.ts | 2 +- src/core/server/server.api.md | 2 +- .../server/routes/fields.ts | 2 +- .../apm/server/routes/create_api/index.ts | 2 +- .../plugins/case/server/routes/api/utils.ts | 2 +- .../fleet/server/errors/handlers.test.ts | 4 +- .../plugins/fleet/server/errors/handlers.ts | 2 +- .../epm/elasticsearch/template/install.ts | 4 +- .../server/services/epm/packages/install.ts | 2 +- .../lens/server/routes/existing_fields.ts | 2 +- .../ml/common/types/data_frame_analytics.ts | 2 +- .../ml/common/util/errors/errors.test.ts | 3 +- x-pack/plugins/ml/common/util/errors/types.ts | 9 +- .../plugins/ml/server/client/error_wrapper.ts | 2 +- x-pack/plugins/monitoring/server/plugin.ts | 2 +- .../reporting/server/routes/generation.ts | 4 +- .../authentication_service.test.ts | 2 +- .../authentication/providers/kerberos.ts | 4 +- .../lib/detection_engine/routes/utils.test.ts | 6 +- .../lib/create_empty_failure_response.ts | 4 +- x-pack/plugins/spaces/server/lib/errors.ts | 2 +- .../server/routes/api/error_utils.ts | 5 +- yarn.lock | 579 ++++++++---------- 32 files changed, 349 insertions(+), 428 deletions(-) diff --git a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md index 40fc1a8e05a68b..7c53356615ee9f 100644 --- a/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md +++ b/docs/development/core/server/kibana-plugin-core-server.legacyelasticsearcherror.md @@ -9,7 +9,7 @@ Signature: ```typescript -export interface LegacyElasticsearchError extends Boom +export interface LegacyElasticsearchError extends Boom.Boom ``` ## Properties diff --git a/package.json b/package.json index 8e99b5c693ceca..252c8130c38d63 100644 --- a/package.json +++ b/package.json @@ -73,10 +73,6 @@ "url": "https://github.com/elastic/kibana.git" }, "resolutions": { - "**/@hapi/iron": "^5.1.4", - "**/@types/hapi__boom": "^7.4.1", - "**/@types/hapi__hapi": "^18.2.6", - "**/@types/hapi__mimos": "4.1.0", "**/@types/node": "14.14.14", "**/chokidar": "^3.4.3", "**/cross-fetch/node-fetch": "^2.6.1", @@ -115,17 +111,17 @@ "@elastic/request-crypto": "1.1.4", "@elastic/safer-lodash-set": "link:packages/elastic-safer-lodash-set", "@elastic/search-ui-app-search-connector": "^1.5.0", - "@hapi/boom": "^7.4.11", - "@hapi/cookie": "^10.1.2", - "@hapi/good-squeeze": "5.2.1", - "@hapi/h2o2": "^8.3.2", - "@hapi/hapi": "^18.4.1", - "@hapi/hoek": "^8.5.1", - "@hapi/inert": "^5.2.2", - "@hapi/podium": "^3.4.3", - "@hapi/statehood": "^6.1.2", - "@hapi/vision": "^5.5.4", - "@hapi/wreck": "^15.0.2", + "@hapi/boom": "^9.1.1", + "@hapi/cookie": "^11.0.2", + "@hapi/good-squeeze": "6.0.0", + "@hapi/h2o2": "^9.0.2", + "@hapi/hapi": "^20.0.3", + "@hapi/hoek": "^9.1.0", + "@hapi/inert": "^6.0.3", + "@hapi/podium": "^4.1.1", + "@hapi/statehood": "^7.0.3", + "@hapi/vision": "^6.0.1", + "@hapi/wreck": "^17.1.0", "@kbn/ace": "link:packages/kbn-ace", "@kbn/analytics": "link:packages/kbn-analytics", "@kbn/apm-config-loader": "link:packages/kbn-apm-config-loader", @@ -452,14 +448,11 @@ "@types/graphql": "^0.13.2", "@types/gulp": "^4.0.6", "@types/gulp-zip": "^4.0.1", - "@types/hapi__boom": "^7.4.1", "@types/hapi__cookie": "^10.1.1", - "@types/hapi__h2o2": "8.3.0", - "@types/hapi__hapi": "^18.2.6", - "@types/hapi__hoek": "^6.2.0", - "@types/hapi__inert": "^5.2.1", + "@types/hapi__h2o2": "^8.3.2", + "@types/hapi__hapi": "^20.0.2", + "@types/hapi__inert": "^5.2.2", "@types/hapi__podium": "^3.4.1", - "@types/hapi__wreck": "^15.0.1", "@types/has-ansi": "^3.0.0", "@types/he": "^1.1.1", "@types/history": "^4.7.3", diff --git a/src/core/server/elasticsearch/legacy/errors.ts b/src/core/server/elasticsearch/legacy/errors.ts index e557e7395fe566..adc1fa07287842 100644 --- a/src/core/server/elasticsearch/legacy/errors.ts +++ b/src/core/server/elasticsearch/legacy/errors.ts @@ -30,7 +30,7 @@ enum ErrorCode { * @deprecated. The new elasticsearch client doesn't wrap errors anymore. * @public * */ -export interface LegacyElasticsearchError extends Boom { +export interface LegacyElasticsearchError extends Boom.Boom { [code]?: string; } @@ -86,7 +86,7 @@ export class LegacyElasticsearchErrorHelpers { const decoratedError = decorate(error, ErrorCode.NOT_AUTHORIZED, 401, reason); const wwwAuthHeader = get(error, 'body.error.header[WWW-Authenticate]') as string; - decoratedError.output.headers['WWW-Authenticate'] = + (decoratedError.output.headers as { [key: string]: string })['WWW-Authenticate'] = wwwAuthHeader || 'Basic realm="Authorization Required"'; return decoratedError; diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index 71040598d34b12..cbb60480c4cf17 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -1214,7 +1214,7 @@ describe('timeout options', () => { router.get( { path: '/', - validate: { body: schema.any() }, + validate: { body: schema.maybe(schema.any()) }, }, (context, req, res) => { return res.ok({ @@ -1247,7 +1247,7 @@ describe('timeout options', () => { router.get( { path: '/', - validate: { body: schema.any() }, + validate: { body: schema.maybe(schema.any()) }, options: { timeout: { idleSocket: 12000 } }, }, (context, req, res) => { diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 43f5264ff22e31..42e89b66d9c519 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { Server } from '@hapi/hapi'; +import { Server, ServerRoute } from '@hapi/hapi'; import HapiStaticFiles from '@hapi/inert'; import url from 'url'; import uuid from 'uuid'; @@ -167,8 +167,6 @@ export class HttpServer { for (const router of this.registeredRouters) { for (const route of router.getRoutes()) { this.log.debug(`registering route handler for [${route.path}]`); - // Hapi does not allow payload validation to be specified for 'head' or 'get' requests - const validate = isSafeMethod(route.method) ? undefined : { payload: true }; const { authRequired, tags, body = {}, timeout } = route.options; const { accepts: allow, maxBytes, output, parse } = body; @@ -176,57 +174,45 @@ export class HttpServer { xsrfRequired: route.options.xsrfRequired ?? !isSafeMethod(route.method), }; - // To work around https://github.com/hapijs/hapi/issues/4122 until v20, set the socket - // timeout on the route to a fake timeout only when the payload timeout is specified. - // Within the onPreAuth lifecycle of the route itself, we'll override the timeout with the - // real socket timeout. - const fakeSocketTimeout = timeout?.payload ? timeout.payload + 1 : undefined; - - this.server.route({ + const routeOpts: ServerRoute = { handler: route.handler, method: route.method, path: route.path, options: { auth: this.getAuthOption(authRequired), app: kibanaRouteOptions, - ext: { - onPreAuth: { - method: (request, h) => { - // At this point, the socket timeout has only been set to work-around the HapiJS bug. - // We need to either set the real per-route timeout or use the default idle socket timeout - if (timeout?.idleSocket) { - request.raw.req.socket.setTimeout(timeout.idleSocket); - } else if (fakeSocketTimeout) { - // NodeJS uses a socket timeout of `0` to denote "no timeout" - request.raw.req.socket.setTimeout(this.config!.socketTimeout ?? 0); - } - - return h.continue; - }, - }, - }, tags: tags ? Array.from(tags) : undefined, - // TODO: This 'validate' section can be removed once the legacy platform is completely removed. - // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default - // validation applied in ./http_tools#getServerOptions - // (All NP routes are already required to specify their own validation in order to access the payload) - validate, - payload: [allow, maxBytes, output, parse, timeout?.payload].some( - (v) => typeof v !== 'undefined' - ) + // @ts-expect-error Types are outdated and doesn't allow `payload.multipart` to be `true` + payload: [allow, maxBytes, output, parse, timeout?.payload].some((x) => x !== undefined) ? { allow, maxBytes, output, parse, timeout: timeout?.payload, + multipart: true, } : undefined, timeout: { - socket: fakeSocketTimeout, + socket: timeout?.idleSocket ?? this.config!.socketTimeout, }, }, - }); + }; + + // Hapi does not allow payload validation to be specified for 'head' or 'get' requests + if (!isSafeMethod(route.method)) { + // TODO: This 'validate' section can be removed once the legacy platform is completely removed. + // We are telling Hapi that NP routes can accept any payload, so that it can bypass the default + // validation applied in ./http_tools#getServerOptions + // (All NP routes are already required to specify their own validation in order to access the payload) + // TODO: Move the setting of the validate option back up to being set at `routeOpts` creation-time once + // https://github.com/hapijs/hoek/pull/365 is merged and released in @hapi/hoek v9.1.1. At that point I + // imagine the ts-error below will go away as well. + // @ts-expect-error "Property 'validate' does not exist on type 'RouteOptions'" <-- ehh?!? yes it does! + routeOpts.options!.validate = { payload: true }; + } + + this.server.route(routeOpts); } } diff --git a/src/core/server/http/lifecycle/on_pre_response.ts b/src/core/server/http/lifecycle/on_pre_response.ts index 42179374ec6727..9efcf46148e1fc 100644 --- a/src/core/server/http/lifecycle/on_pre_response.ts +++ b/src/core/server/http/lifecycle/on_pre_response.ts @@ -142,7 +142,11 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo if (preResponseResult.isNext(result)) { if (result.headers) { if (isBoom(response)) { - findHeadersIntersection(response.output.headers, result.headers, log); + findHeadersIntersection( + response.output.headers as { [key: string]: string }, + result.headers, + log + ); // hapi wraps all error response in Boom object internally response.output.headers = { ...response.output.headers, @@ -157,7 +161,7 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo const overriddenResponse = responseToolkit.response(result.body).code(statusCode); const originalHeaders = isBoom(response) ? response.output.headers : response.headers; - setHeaders(overriddenResponse, originalHeaders); + setHeaders(overriddenResponse, originalHeaders as { [key: string]: string }); if (result.headers) { setHeaders(overriddenResponse, result.headers); } @@ -178,8 +182,8 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo }; } -function isBoom(response: any): response is Boom { - return response instanceof Boom; +function isBoom(response: any): response is Boom.Boom { + return response instanceof Boom.Boom; } function setHeaders(response: ResponseObject, headers: ResponseHeaders) { diff --git a/src/core/server/http/router/error_wrapper.ts b/src/core/server/http/router/error_wrapper.ts index 5a4b7e9f775823..7d141e81ddf367 100644 --- a/src/core/server/http/router/error_wrapper.ts +++ b/src/core/server/http/router/error_wrapper.ts @@ -29,7 +29,7 @@ export const wrapErrors: RequestHandlerWrapper = (handler) => { return response.customError({ body: e.output.payload, statusCode: e.output.statusCode, - headers: e.output.headers, + headers: e.output.headers as { [key: string]: string }, }); } throw e; diff --git a/src/core/server/http/router/response_adapter.ts b/src/core/server/http/router/response_adapter.ts index 63acd2207ac3aa..d80c21bde8de8e 100644 --- a/src/core/server/http/router/response_adapter.ts +++ b/src/core/server/http/router/response_adapter.ts @@ -56,7 +56,7 @@ export class HapiResponseAdapter { } public toInternalError() { - const error = new Boom('', { + const error = new Boom.Boom('', { statusCode: 500, }); @@ -129,7 +129,7 @@ export class HapiResponseAdapter { } // we use for BWC with Boom payload for error responses - {error: string, message: string, statusCode: string} - const error = new Boom('', { + const error = new Boom.Boom('', { statusCode: kibanaResponse.status, }); @@ -142,8 +142,7 @@ export class HapiResponseAdapter { const headers = kibanaResponse.options.headers; if (headers) { - // Hapi typings for header accept only strings, although string[] is a valid value - error.output.headers = headers as any; + error.output.headers = headers; } return error; diff --git a/src/core/server/http/router/router.ts b/src/core/server/http/router/router.ts index b1e092ba5786ae..ebc41a793f3b38 100644 --- a/src/core/server/http/router/router.ts +++ b/src/core/server/http/router/router.ts @@ -44,7 +44,10 @@ interface RouterRoute { method: RouteMethod; path: string; options: RouteConfigOptions; - handler: (req: Request, responseToolkit: ResponseToolkit) => Promise>; + handler: ( + req: Request, + responseToolkit: ResponseToolkit + ) => Promise>; } /** diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index e8836dbd8f7a18..c6c8eee003e4ed 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -44,7 +44,7 @@ const CODE_GENERAL_ERROR = 'SavedObjectsClient/generalError'; const code = Symbol('SavedObjectsClientErrorCode'); -export interface DecoratedError extends Boom { +export interface DecoratedError extends Boom.Boom { [code]?: string; } diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 5f07a4b5230560..cef5f33726ed56 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1541,7 +1541,7 @@ export type LegacyElasticsearchClientConfig = Pick { return res.customError({ body: err.output.payload, statusCode: err.output.statusCode, - headers: err.output.headers, + headers: err.output.headers as { [key: string]: string }, }); } diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index ef445617e9295b..94711cf76c145f 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -140,7 +140,7 @@ export function createApi() { } function convertBoomToKibanaResponse( - error: Boom, + error: Boom.Boom, response: KibanaResponseFactory ) { const opts = { body: error.message }; diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts index c8753772648c29..917afb487b1f44 100644 --- a/x-pack/plugins/case/server/routes/api/utils.ts +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -96,7 +96,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = isBoom(error) ? error : boomify(error, options); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } diff --git a/x-pack/plugins/fleet/server/errors/handlers.test.ts b/x-pack/plugins/fleet/server/errors/handlers.test.ts index 4f8eff6c6c75a8..98139e9802fec1 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.test.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.test.ts @@ -162,7 +162,7 @@ describe('defaultIngestErrorHandler', () => { describe('Boom', () => { it('500: constructor - one arg', async () => { - const error = new Boom('bam'); + const error = new Boom.Boom('bam'); const response = httpServerMock.createResponseFactory(); await defaultIngestErrorHandler({ error, response }); @@ -181,7 +181,7 @@ describe('defaultIngestErrorHandler', () => { }); it('custom: constructor - 2 args', async () => { - const error = new Boom('Problem doing something', { + const error = new Boom.Boom('Problem doing something', { statusCode: 456, }); const response = httpServerMock.createResponseFactory(); diff --git a/x-pack/plugins/fleet/server/errors/handlers.ts b/x-pack/plugins/fleet/server/errors/handlers.ts index fecfcf145ca999..e0b05d972bd38c 100644 --- a/x-pack/plugins/fleet/server/errors/handlers.ts +++ b/x-pack/plugins/fleet/server/errors/handlers.ts @@ -27,7 +27,7 @@ type IngestErrorHandler = ( params: IngestErrorHandlerParams ) => IKibanaResponse | Promise; interface IngestErrorHandlerParams { - error: IngestManagerError | Boom | Error; + error: IngestManagerError | Boom.Boom | Error; response: KibanaResponseFactory; request?: KibanaRequest; context?: RequestHandlerContext; diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts index 8b018f4a2a906f..ceedc872753936 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/install.ts @@ -109,7 +109,7 @@ const installPreBuiltTemplates = async (paths: string[], callCluster: CallESAsCu try { return await Promise.all(templateInstallPromises); } catch (e) { - throw new Boom(`Error installing prebuilt index templates ${e.message}`, { + throw new Boom.Boom(`Error installing prebuilt index templates ${e.message}`, { statusCode: 400, }); } @@ -144,7 +144,7 @@ const installPreBuiltComponentTemplates = async ( try { return await Promise.all(templateInstallPromises); } catch (e) { - throw new Boom(`Error installing prebuilt component templates ${e.message}`, { + throw new Boom.Boom(`Error installing prebuilt component templates ${e.message}`, { statusCode: 400, }); } diff --git a/x-pack/plugins/fleet/server/services/epm/packages/install.ts b/x-pack/plugins/fleet/server/services/epm/packages/install.ts index 29300818288b42..48dd589dd0b8f6 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/install.ts @@ -122,7 +122,7 @@ export async function handleInstallPackageFailure({ callCluster, }: { savedObjectsClient: SavedObjectsClientContract; - error: IngestManagerError | Boom | Error; + error: IngestManagerError | Boom.Boom | Error; pkgName: string; pkgVersion: string; installedPkg: SavedObject | undefined; diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 43c56af7f71bc8..3de3c6c7187c63 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -14,7 +14,7 @@ import { BASE_API_URL } from '../../common'; import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; import { PluginStartContract } from '../plugin'; -export function isBoomError(error: { isBoom?: boolean }): error is Boom { +export function isBoomError(error: { isBoom?: boolean }): error is Boom.Boom { return error.isBoom === true; } diff --git a/x-pack/plugins/ml/common/types/data_frame_analytics.ts b/x-pack/plugins/ml/common/types/data_frame_analytics.ts index 3455b833c404a4..e87e29a36084b1 100644 --- a/x-pack/plugins/ml/common/types/data_frame_analytics.ts +++ b/x-pack/plugins/ml/common/types/data_frame_analytics.ts @@ -11,7 +11,7 @@ import { DATA_FRAME_TASK_STATE } from '../constants/data_frame_analytics'; export interface DeleteDataFrameAnalyticsWithIndexStatus { success: boolean; - error?: EsErrorBody | Boom; + error?: EsErrorBody | Boom.Boom; } export type IndexName = string; diff --git a/x-pack/plugins/ml/common/util/errors/errors.test.ts b/x-pack/plugins/ml/common/util/errors/errors.test.ts index 166264ebddee17..6f07c0150630de 100644 --- a/x-pack/plugins/ml/common/util/errors/errors.test.ts +++ b/x-pack/plugins/ml/common/util/errors/errors.test.ts @@ -76,8 +76,9 @@ describe('ML - error message utils', () => { expect(extractErrorMessage(bodyWithAttributes)).toBe(testMsg); // boom error - const boomError: Boom = { + const boomError: Boom.Boom = { message: '', + typeof: Boom.Boom.constructor, reformat: () => '', name: '', data: [], diff --git a/x-pack/plugins/ml/common/util/errors/types.ts b/x-pack/plugins/ml/common/util/errors/types.ts index 667e6e34a56406..b6d43d8e0cf929 100644 --- a/x-pack/plugins/ml/common/util/errors/types.ts +++ b/x-pack/plugins/ml/common/util/errors/types.ts @@ -45,7 +45,12 @@ export interface MLHttpFetchError extends HttpFetchError { body: T; } -export type ErrorType = MLHttpFetchError | EsErrorBody | Boom | string | undefined; +export type ErrorType = + | MLHttpFetchError + | EsErrorBody + | Boom.Boom + | string + | undefined; export function isEsErrorBody(error: any): error is EsErrorBody { return error && error.error?.reason !== undefined; @@ -63,6 +68,6 @@ export function isMLResponseError(error: any): error is MLResponseError { return typeof error.body === 'object' && 'message' in error.body; } -export function isBoomError(error: any): error is Boom { +export function isBoomError(error: any): error is Boom.Boom { return error.isBoom === true; } diff --git a/x-pack/plugins/ml/server/client/error_wrapper.ts b/x-pack/plugins/ml/server/client/error_wrapper.ts index e2e97e3a876962..fb41d30e34faeb 100644 --- a/x-pack/plugins/ml/server/client/error_wrapper.ts +++ b/x-pack/plugins/ml/server/client/error_wrapper.ts @@ -17,7 +17,7 @@ export function wrapError(error: any): CustomHttpResponseOptions message: boom, ...(statusCode !== 500 && error.body ? { attributes: { body: error.body } } : {}), }, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode, }; } diff --git a/x-pack/plugins/monitoring/server/plugin.ts b/x-pack/plugins/monitoring/server/plugin.ts index 9478e24c9560f1..22ea6c31dbe69d 100644 --- a/x-pack/plugins/monitoring/server/plugin.ts +++ b/x-pack/plugins/monitoring/server/plugin.ts @@ -59,7 +59,7 @@ const wrapError = (error: any): CustomHttpResponseOptions => { const boom = Boom.isBoom(error) ? error : Boom.boomify(error, options); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; }; diff --git a/x-pack/plugins/reporting/server/routes/generation.ts b/x-pack/plugins/reporting/server/routes/generation.ts index ba69b97bee7f1e..064cdf0b28eb98 100644 --- a/x-pack/plugins/reporting/server/routes/generation.ts +++ b/x-pack/plugins/reporting/server/routes/generation.ts @@ -68,8 +68,8 @@ export function registerJobGenerationRoutes(reporting: ReportingCore, logger: Lo /* * Error should already have been logged by the time we get here */ - function handleError(res: typeof kibanaResponseFactory, err: Error | Boom) { - if (err instanceof Boom) { + function handleError(res: typeof kibanaResponseFactory, err: Error | Boom.Boom) { + if (err instanceof Boom.Boom) { return res.customError({ statusCode: err.output.statusCode, body: err.output.payload.message, diff --git a/x-pack/plugins/security/server/authentication/authentication_service.test.ts b/x-pack/plugins/security/server/authentication/authentication_service.test.ts index d81702691a3a12..244cf1d0a8f51f 100644 --- a/x-pack/plugins/security/server/authentication/authentication_service.test.ts +++ b/x-pack/plugins/security/server/authentication/authentication_service.test.ts @@ -243,7 +243,7 @@ describe('AuthenticationService', () => { it('includes `WWW-Authenticate` header if `authenticate` fails to authenticate user and provides challenges', async () => { const mockResponse = httpServerMock.createLifecycleResponseFactory(); const originalError = Boom.unauthorized('some message'); - originalError.output.headers['WWW-Authenticate'] = [ + (originalError.output.headers as { [key: string]: string })['WWW-Authenticate'] = [ 'Basic realm="Access to prod", charset="UTF-8"', 'Basic', 'Negotiate', diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 9bf419c7dacaae..b7abed979164e3 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -333,7 +333,9 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { * @param error Error to extract challenges from. */ private getNegotiateChallenge(error: LegacyElasticsearchError) { - const challenges = ([] as string[]).concat(error.output.headers[WWWAuthenticateHeaderName]); + const challenges = ([] as string[]).concat( + (error.output.headers as { [key: string]: string })[WWWAuthenticateHeaderName] + ); const negotiateChallenge = challenges.find((challenge) => challenge.toLowerCase().startsWith('negotiate') diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts index 36131c2e2844d0..32c1d8d3cdf56e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/utils.test.ts @@ -35,7 +35,7 @@ let alertsClient: ReturnType; describe('utils', () => { describe('transformError', () => { test('returns transformed output error from boom object with a 500 and payload of internal server error', () => { - const boom = new Boom('some boom message'); + const boom = new Boom.Boom('some boom message'); const transformed = transformError(boom); expect(transformed).toEqual({ message: 'An internal server error occurred', @@ -124,7 +124,7 @@ describe('utils', () => { describe('transformBulkError', () => { test('returns transformed object if it is a boom object', () => { - const boom = new Boom('some boom message', { statusCode: 400 }); + const boom = new Boom.Boom('some boom message', { statusCode: 400 }); const transformed = transformBulkError('rule-1', boom); const expected: BulkError = { rule_id: 'rule-1', @@ -252,7 +252,7 @@ describe('utils', () => { describe('transformImportError', () => { test('returns transformed object if it is a boom object', () => { - const boom = new Boom('some boom message', { statusCode: 400 }); + const boom = new Boom.Boom('some boom message', { statusCode: 400 }); const transformed = transformImportError('rule-1', boom, { success_count: 1, success: false, diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts index 764ae5a87ec0ec..3980eef7caac20 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/create_empty_failure_response.ts @@ -6,10 +6,10 @@ import Boom, { Payload } from '@hapi/boom'; import { SavedObjectsImportError } from 'src/core/server'; -export const createEmptyFailureResponse = (errors?: Array) => { +export const createEmptyFailureResponse = (errors?: Array) => { const errorMessages: Array = (errors || []).map((error) => { if (Boom.isBoom(error as any)) { - return (error as Boom).output.payload as Payload; + return (error as Boom.Boom).output.payload as Payload; } return error as SavedObjectsImportError; }); diff --git a/x-pack/plugins/spaces/server/lib/errors.ts b/x-pack/plugins/spaces/server/lib/errors.ts index 13a5c2440877a8..0f6bf0f1d56b41 100644 --- a/x-pack/plugins/spaces/server/lib/errors.ts +++ b/x-pack/plugins/spaces/server/lib/errors.ts @@ -11,7 +11,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = isBoom(error) ? error : boomify(error); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } diff --git a/x-pack/plugins/transform/server/routes/api/error_utils.ts b/x-pack/plugins/transform/server/routes/api/error_utils.ts index 4986eb718dc2c0..356158913eb925 100644 --- a/x-pack/plugins/transform/server/routes/api/error_utils.ts +++ b/x-pack/plugins/transform/server/routes/api/error_utils.ts @@ -79,7 +79,7 @@ export function wrapError(error: any): CustomHttpResponseOptions const boom = Boom.isBoom(error) ? error : Boom.boomify(error, { statusCode: error.statusCode }); return { body: boom, - headers: boom.output.headers, + headers: boom.output.headers as { [key: string]: string }, statusCode: boom.output.statusCode, }; } @@ -130,7 +130,6 @@ export function wrapEsError(err: any, statusCodeToMessageMap: Record Date: Sat, 19 Dec 2020 18:38:46 -0500 Subject: [PATCH 29/40] [Security Solutions] fix timeline tabs + layout (#86581) * fix timeline tabs + fix screenreader * review * fix jest tests --- .../components/accessibility/helpers.test.ts | 21 ------ .../components/accessibility/helpers.test.tsx | 70 +++++++++++++++++++ .../components/accessibility/helpers.ts | 36 ++++++++++ .../index.test.tsx | 40 +++++++++++ .../tooltip_with_keyboard_shortcut/index.tsx | 8 +-- .../draggable_wrapper_hover_content.test.tsx | 48 ++++++------- .../draggable_wrapper_hover_content.tsx | 1 + .../events_viewer/events_viewer.tsx | 7 +- .../public/common/components/page/index.tsx | 10 ++- .../notes/note_cards/index.test.tsx | 36 +++++++--- .../components/notes/note_cards/index.tsx | 30 +++----- .../open_timeline/note_previews/index.tsx | 12 +++- .../note_previews/translations.ts | 13 ++++ .../__snapshots__/index.test.tsx.snap | 7 ++ .../body/data_driven_columns/index.test.tsx | 2 + .../body/data_driven_columns/index.tsx | 32 +++++++-- .../body/data_driven_columns/translations.ts | 14 ++++ .../body/events/event_column_view.test.tsx | 2 + .../body/events/event_column_view.tsx | 12 +++- .../components/timeline/body/events/index.tsx | 8 +-- .../timeline/body/events/stateful_event.tsx | 36 ++++++++-- .../events/stateful_row_renderer/index.tsx | 57 +++++++++------ .../components/timeline/body/helpers.tsx | 6 ++ .../components/timeline/body/index.test.tsx | 2 +- .../components/timeline/body/index.tsx | 13 ++-- .../body/renderers/get_row_renderer.test.tsx | 14 ++-- .../body/renderers/get_row_renderer.ts | 14 +--- .../timeline/body/renderers/index.ts | 2 - .../timeline/pinned_tab_content/index.tsx | 3 +- .../timeline/query_tab_content/index.tsx | 1 + .../timeline/tabs_content/index.tsx | 2 +- .../timeline/tabs_content/translations.ts | 6 +- 32 files changed, 409 insertions(+), 156 deletions(-) delete mode 100644 x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx create mode 100644 x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts deleted file mode 100644 index c099a1413a88f7..00000000000000 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ariaIndexToArrayIndex, arrayIndexToAriaIndex } from './helpers'; - -describe('helpers', () => { - describe('ariaIndexToArrayIndex', () => { - it('returns the expected array index', () => { - expect(ariaIndexToArrayIndex(1)).toEqual(0); - }); - }); - - describe('arrayIndexToAriaIndex', () => { - it('returns the expected aria index', () => { - expect(arrayIndexToAriaIndex(0)).toEqual(1); - }); - }); -}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx new file mode 100644 index 00000000000000..48db4b1f261b69 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.test.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { + ariaIndexToArrayIndex, + arrayIndexToAriaIndex, + getNotesContainerClassName, + getRowRendererClassName, + isArrowRight, +} from './helpers'; + +describe('helpers', () => { + describe('ariaIndexToArrayIndex', () => { + test('it returns the expected array index', () => { + expect(ariaIndexToArrayIndex(1)).toEqual(0); + }); + }); + + describe('arrayIndexToAriaIndex', () => { + test('it returns the expected aria index', () => { + expect(arrayIndexToAriaIndex(0)).toEqual(1); + }); + }); + + describe('isArrowRight', () => { + test('it returns true if the right arrow key was pressed', () => { + let result = false; + const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { + result = isArrowRight(keyboardEvent); + }; + + const wrapper = mount(
    ); + wrapper.find('div').simulate('keydown', { key: 'ArrowRight' }); + wrapper.update(); + + expect(result).toBe(true); + }); + + test('it returns false if another key was pressed', () => { + let result = false; + const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { + result = isArrowRight(keyboardEvent); + }; + + const wrapper = mount(
    ); + wrapper.find('div').simulate('keydown', { key: 'Enter' }); + wrapper.update(); + + expect(result).toBe(false); + }); + }); + + describe('getRowRendererClassName', () => { + test('it returns the expected class name', () => { + expect(getRowRendererClassName(2)).toBe('row-renderer-2'); + }); + }); + + describe('getNotesContainerClassName', () => { + test('it returns the expected class name', () => { + expect(getNotesContainerClassName(2)).toBe('notes-container-2'); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts index d8603c9d02fcb7..8fc535c680b26f 100644 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/helpers.ts @@ -5,6 +5,11 @@ */ import { DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME } from '../drag_and_drop/helpers'; +import { + NOTES_CONTAINER_CLASS_NAME, + NOTE_CONTENT_CLASS_NAME, + ROW_RENDERER_CLASS_NAME, +} from '../../../timelines/components/timeline/body/helpers'; import { HOVER_ACTIONS_ALWAYS_SHOW_CLASS_NAME } from '../with_hover_actions'; /** @@ -63,6 +68,9 @@ export const isArrowDownOrArrowUp = (event: React.KeyboardEvent): boolean => export const isArrowKey = (event: React.KeyboardEvent): boolean => isArrowRightOrArrowLeft(event) || isArrowDownOrArrowUp(event); +/** Returns `true` if the right arrow key was pressed */ +export const isArrowRight = (event: React.KeyboardEvent): boolean => event.key === 'ArrowRight'; + /** Returns `true` if the escape key was pressed */ export const isEscape = (event: React.KeyboardEvent): boolean => event.key === 'Escape'; @@ -284,6 +292,12 @@ export type OnColumnFocused = ({ newFocusedColumnAriaColindex: number | null; }) => void; +export const getRowRendererClassName = (ariaRowindex: number) => + `${ROW_RENDERER_CLASS_NAME}-${ariaRowindex}`; + +export const getNotesContainerClassName = (ariaRowindex: number) => + `${NOTES_CONTAINER_CLASS_NAME}-${ariaRowindex}`; + /** * This function implements arrow key support for the `onKeyDownFocusHandler`. * @@ -312,6 +326,28 @@ export const onArrowKeyDown = ({ onColumnFocused?: OnColumnFocused; rowindexAttribute: string; }) => { + if (isArrowDown(event) && event.shiftKey) { + const firstRowRendererDraggable = containerElement?.querySelector( + `.${getRowRendererClassName(focusedAriaRowindex)} .${DRAGGABLE_KEYBOARD_WRAPPER_CLASS_NAME}` + ); + + if (firstRowRendererDraggable) { + firstRowRendererDraggable.focus(); + return; + } + } + + if (isArrowRight(event) && event.shiftKey) { + const firstNoteContent = containerElement?.querySelector( + `.${getNotesContainerClassName(focusedAriaRowindex)} .${NOTE_CONTENT_CLASS_NAME}` + ); + + if (firstNoteContent) { + firstNoteContent.focus(); + return; + } + } + const ariaColindex = isArrowRightOrArrowLeft(event) ? getNewAriaColindex({ focusedAriaColindex, diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx new file mode 100644 index 00000000000000..773fc3eeff4836 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mount } from 'enzyme'; +import React from 'react'; + +import { TooltipWithKeyboardShortcut } from '.'; + +const props = { + content:
    {'To pay respect'}
    , + shortcut: 'F', + showShortcut: true, +}; + +describe('TooltipWithKeyboardShortcut', () => { + test('it renders the provided content', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="content"]').text()).toBe('To pay respect'); + }); + + test('it renders the additionalScreenReaderOnlyContext', () => { + const wrapper = mount( + + ); + + expect(wrapper.find('[data-test-subj="additionalScreenReaderOnlyContext"]').text()).toBe( + 'field.name' + ); + }); + + test('it renders the expected shortcut', () => { + const wrapper = mount(); + + expect(wrapper.find('[data-test-subj="shortcut"]').first().text()).toBe('Press\u00a0F'); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx index 807953c51a42c3..ab6f90c8fec816 100644 --- a/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/accessibility/tooltip_with_keyboard_shortcut/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiScreenReaderOnly, EuiText } from '@elastic/eui'; +import { EuiText, EuiScreenReaderOnly } from '@elastic/eui'; import React from 'react'; import * as i18n from './translations'; @@ -23,14 +23,14 @@ const TooltipWithKeyboardShortcutComponent = ({ showShortcut, }: Props) => ( <> -
    {content}
    +
    {content}
    {additionalScreenReaderOnlyContext !== '' && ( - +

    {additionalScreenReaderOnlyContext}

    )} {showShortcut && ( - + {i18n.PRESS} {'\u00a0'} {shortcut} diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx index 1cf03225cec033..9ce5778fb72e5c 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx @@ -14,7 +14,6 @@ import '../../mock/match_media'; import { useKibana } from '../../lib/kibana'; import { TestProviders } from '../../mock'; import { FilterManager } from '../../../../../../../src/plugins/data/public'; -import { useAddToTimeline } from '../../hooks/use_add_to_timeline'; import { useSourcererScope } from '../../containers/sourcerer'; import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content'; import { @@ -41,8 +40,14 @@ jest.mock('uuid', () => { v4: jest.fn(() => 'uuid.v4()'), }; }); - -jest.mock('../../hooks/use_add_to_timeline'); +const mockStartDragToTimeline = jest.fn(); +jest.mock('../../hooks/use_add_to_timeline', () => { + const original = jest.requireActual('../../hooks/use_add_to_timeline'); + return { + ...original, + useAddToTimeline: () => ({ startDragToTimeline: mockStartDragToTimeline }), + }; +}); const mockAddFilters = jest.fn(); const mockGetTimelineFilterManager = jest.fn().mockReturnValue({ addFilters: mockAddFilters, @@ -78,8 +83,7 @@ const defaultProps = { describe('DraggableWrapperHoverContent', () => { beforeAll(() => { - // our mock implementation of the useAddToTimeline hook returns a mock startDragToTimeline function: - (useAddToTimeline as jest.Mock).mockReturnValue({ startDragToTimeline: jest.fn() }); + mockStartDragToTimeline.mockReset(); (useSourcererScope as jest.Mock).mockReturnValue({ browserFields: mockBrowserFields, selectedPatterns: [], @@ -376,7 +380,7 @@ describe('DraggableWrapperHoverContent', () => { }); }); - test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', () => { + test('when clicked, it invokes the `startDragToTimeline` function returned by the `useAddToTimeline` hook', async () => { const wrapper = mount( { ); - // The following "startDragToTimeline" function returned by our mock - // useAddToTimeline hook is called when the user clicks the - // Add to timeline investigation action: - const { startDragToTimeline } = useAddToTimeline({ - draggableId, - fieldName: aggregatableStringField, - }); - wrapper.find('[data-test-subj="add-to-timeline"]').first().simulate('click'); - wrapper.update(); - waitFor(() => { - expect(startDragToTimeline).toHaveBeenCalled(); + await waitFor(() => { + wrapper.update(); + expect(mockStartDragToTimeline).toHaveBeenCalled(); }); }); }); describe('Top N', () => { - test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, async () => { + test(`it renders the 'Show top field' button when showTopN is false and an aggregatable string field is provided`, () => { const aggregatableStringField = 'cloud.account.id'; const wrapper = mount( @@ -425,7 +421,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); }); - test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, async () => { + test(`it renders the 'Show top field' button when showTopN is false and a allowlisted signal field is provided`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -443,7 +439,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(true); }); - test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, async () => { + test(`it does NOT render the 'Show top field' button when showTopN is false and a field not known to BrowserFields is provided`, () => { const notKnownToBrowserFields = 'unknown.field'; const wrapper = mount( @@ -461,7 +457,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); }); - test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, () => { + test(`it should invokes goGetTimelineId when user is over the 'Show top field' button`, async () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -476,12 +472,12 @@ describe('DraggableWrapperHoverContent', () => { ); const button = wrapper.find(`[data-test-subj="show-top-field"]`).first(); button.simulate('mouseenter'); - waitFor(() => { + await waitFor(() => { expect(goGetTimelineId).toHaveBeenCalledWith(true); }); }); - test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, async () => { + test(`invokes the toggleTopN function when the 'Show top field' button is clicked`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -502,7 +498,7 @@ describe('DraggableWrapperHoverContent', () => { expect(toggleTopN).toBeCalled(); }); - test(`it does NOT render the Top N histogram when when showTopN is false`, async () => { + test(`it does NOT render the Top N histogram when when showTopN is false`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -522,7 +518,7 @@ describe('DraggableWrapperHoverContent', () => { ); }); - test(`it does NOT render the 'Show top field' button when showTopN is true`, async () => { + test(`it does NOT render the 'Show top field' button when showTopN is true`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( @@ -541,7 +537,7 @@ describe('DraggableWrapperHoverContent', () => { expect(wrapper.find('[data-test-subj="show-top-field"]').first().exists()).toBe(false); }); - test(`it renders the Top N histogram when when showTopN is true`, async () => { + test(`it renders the Top N histogram when when showTopN is true`, () => { const allowlistedField = 'signal.rule.name'; const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx index 2d3fdb9cb94291..adbb38f20c0280 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx @@ -324,6 +324,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({ color="text" data-test-subj="add-to-timeline" iconType="timeline" + onClick={handleStartDragToTimeline} /> )} diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx index 515758965d6d17..7d38e3b732fc09 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx @@ -16,7 +16,11 @@ import { BrowserFields, DocValueFields } from '../../containers/source'; import { useTimelineEvents } from '../../../timelines/containers'; import { timelineActions } from '../../../timelines/store/timeline'; import { useKibana } from '../../lib/kibana'; -import { ColumnHeaderOptions, KqlMode } from '../../../timelines/store/timeline/model'; +import { + ColumnHeaderOptions, + KqlMode, + TimelineTabs, +} from '../../../timelines/store/timeline/model'; import { HeaderSection } from '../header_section'; import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers'; import { Sort } from '../../../timelines/components/timeline/body/sort'; @@ -334,6 +338,7 @@ const EventsViewerComponent: React.FC = ({ onRuleChange={onRuleChange} refetch={refetch} sort={sort} + tabType={TimelineTabs.query} totalPages={calculateTotalPages({ itemsCount: totalCountMinusDeleted, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/common/components/page/index.tsx b/x-pack/plugins/security_solution/public/common/components/page/index.tsx index 37cc8f4ac3b93a..30a7685a193b2e 100644 --- a/x-pack/plugins/security_solution/public/common/components/page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/page/index.tsx @@ -89,8 +89,14 @@ export const AppGlobalStyle = createGlobalStyle<{ theme: { eui: { euiColorPrimar to zero. */ .euiScreenReaderOnly { - height: 0px; - width: 0px; + clip: rect(1px, 1px, 1px, 1px); + clip-path: inset(50%); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx index afec2055140d34..febbbb23db1efa 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.test.tsx @@ -11,6 +11,7 @@ import '../../../../common/mock/formatted_relative'; import { NoteCards } from '.'; import { TimelineStatus } from '../../../../../common/types/timeline'; import { TestProviders } from '../../../../common/mock'; +import { TimelineResultNote } from '../../open_timeline/types'; const getNotesByIds = () => ({ abc: { @@ -38,35 +39,42 @@ jest.mock('../../../../common/hooks/use_selector', () => ({ })); describe('NoteCards', () => { - const noteIds = ['abc', 'def']; + const notes: TimelineResultNote[] = Object.entries(getNotesByIds()).map( + ([_, { created, id, note, saveObjectId, user }]) => ({ + saveObjectId, + note, + noteId: id, + updated: created.getTime(), + updatedBy: user, + }) + ); const props = { associateNote: jest.fn(), ariaRowindex: 2, getNotesByIds, getNewNoteId: jest.fn(), - noteIds, + notes: [], showAddNote: true, status: TimelineStatus.active, toggleShowAddNote: jest.fn(), updateNote: jest.fn(), }; - test('it renders the notes column when noteIds are specified', () => { + test('it renders the notes column when notes are specified', () => { const wrapper = mount( - + ); expect(wrapper.find('[data-test-subj="notes"]').exists()).toEqual(true); }); - test('it does NOT render the notes column when noteIds are NOT specified', () => { - const testProps = { ...props, noteIds: [] }; + test('it does NOT render the notes column when notes are NOT specified', () => { const wrapper = mount( - + ); @@ -76,7 +84,7 @@ describe('NoteCards', () => { test('renders note cards', () => { const wrapper = mount( - + ); @@ -85,6 +93,18 @@ describe('NoteCards', () => { ); }); + test('renders the expected screenreader only text', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="screenReaderOnly"]').first().text()).toEqual( + 'You are viewing notes for the event in row 2. Press the up arrow key when finished to return to the event.' + ); + }); + test('it shows controls for adding notes when showAddNote is true', () => { const wrapper = mount( diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx index 99cf8740809dae..9b307690cf12c8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/note_cards/index.tsx @@ -5,11 +5,10 @@ */ import { EuiFlexGroup, EuiPanel, EuiScreenReaderOnly } from '@elastic/eui'; -import React, { useState, useCallback, useMemo } from 'react'; +import React, { useState, useCallback } from 'react'; import styled from 'styled-components'; -import { appSelectors } from '../../../../common/store'; -import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { getNotesContainerClassName } from '../../../../common/components/accessibility/helpers'; import { AddNote } from '../add_note'; import { AssociateNote } from '../helpers'; import { NotePreviews, NotePreviewsContainer } from '../../open_timeline/note_previews'; @@ -44,16 +43,14 @@ NotesContainer.displayName = 'NotesContainer'; interface Props { ariaRowindex: number; associateNote: AssociateNote; - noteIds: string[]; + notes: TimelineResultNote[]; showAddNote: boolean; toggleShowAddNote: () => void; } /** A view for entering and reviewing notes */ export const NoteCards = React.memo( - ({ ariaRowindex, associateNote, noteIds, showAddNote, toggleShowAddNote }) => { - const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); - const notesById = useDeepEqualSelector(getNotesByIds); + ({ ariaRowindex, associateNote, notes, showAddNote, toggleShowAddNote }) => { const [newNote, setNewNote] = useState(''); const associateNoteAndToggleShow = useCallback( @@ -64,23 +61,16 @@ export const NoteCards = React.memo( [associateNote, toggleShowAddNote] ); - const notes: TimelineResultNote[] = useMemo( - () => - appSelectors.getNotes(notesById, noteIds).map((note) => ({ - savedObjectId: note.saveObjectId, - note: note.note, - noteId: note.id, - updated: (note.lastEdit ?? note.created).getTime(), - updatedBy: note.user, - })), - [notesById, noteIds] - ); - return ( {notes.length ? ( - +

    {i18n.YOU_ARE_VIEWING_NOTES(ariaRowindex)}

    diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index 2a1d0d2ad11cf2..fc05e61442e83d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -5,7 +5,7 @@ */ import { uniqBy } from 'lodash/fp'; -import { EuiAvatar, EuiButtonIcon, EuiCommentList } from '@elastic/eui'; +import { EuiAvatar, EuiButtonIcon, EuiCommentList, EuiScreenReaderOnly } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import React, { useCallback, useMemo } from 'react'; import styled from 'styled-components'; @@ -15,6 +15,7 @@ import { TimelineResultNote } from '../types'; import { getEmptyValue, defaultToEmptyTag } from '../../../../common/components/empty_value'; import { MarkdownRenderer } from '../../../../common/components/markdown_editor'; import { timelineActions } from '../../../store/timeline'; +import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; export const NotePreviewsContainer = styled.section` @@ -89,7 +90,14 @@ export const NotePreviews = React.memo( ) : ( getEmptyValue() ), - children: {note.note ?? ''}, + children: ( +
    + +

    {i18n.USER_ADDED_A_NOTE(note.updatedBy ?? i18n.AN_UNKNOWN_USER)}

    +
    + {note.note ?? ''} +
    + ), actions: eventId && timelineId ? ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts index 9857e55e365700..d38dee8a415045 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/translations.ts @@ -12,3 +12,16 @@ export const TOGGLE_EXPAND_EVENT_DETAILS = i18n.translate( defaultMessage: 'Expand event details', } ); + +export const USER_ADDED_A_NOTE = (user: string) => + i18n.translate('xpack.securitySolution.timeline.userAddedANoteScreenReaderOnly', { + values: { user }, + defaultMessage: '{user} added a note', + }); + +export const AN_UNKNOWN_USER = i18n.translate( + 'xpack.securitySolution.timeline.anUnknownUserScreenReaderOnly', + { + defaultMessage: 'an unknown user', + } +); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index 8f514ca49e8480..d112a665d77c00 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -44,6 +44,7 @@ exports[`Columns it renders the expected columns 1`] = ` truncate={true} /> + 0 + 0 + 0 + 0 + 0 + 0 + 0 `; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx index 00b3a10bba5386..d7931b563c7779 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.test.tsx @@ -26,6 +26,8 @@ describe('Columns', () => { columnRenderers={columnRenderers} data={mockTimelineData[0].data} ecsData={mockTimelineData[0].ecs} + hasRowRenderers={false} + notesCount={0} timelineId="test" /> ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx index 6dad9851e5adba..c497d4f459f000 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/index.tsx @@ -21,12 +21,14 @@ import * as i18n from './translations'; interface Props { _id: string; - activeTab?: TimelineTabs; ariaRowindex: number; columnHeaders: ColumnHeaderOptions[]; columnRenderers: ColumnRenderer[]; data: TimelineNonEcsData[]; ecsData: Ecs; + hasRowRenderers: boolean; + notesCount: number; + tabType?: TimelineTabs; timelineId: string; } @@ -74,12 +76,23 @@ export const onKeyDown = (keyboardEvent: React.KeyboardEvent) => { }; export const DataDrivenColumns = React.memo( - ({ _id, activeTab, ariaRowindex, columnHeaders, columnRenderers, data, ecsData, timelineId }) => ( + ({ + _id, + ariaRowindex, + columnHeaders, + columnRenderers, + data, + ecsData, + hasRowRenderers, + notesCount, + tabType, + timelineId, + }) => ( {columnHeaders.map((header, i) => ( ( eventId: _id, field: header, linkValues: getOr([], header.linkField ?? '', ecsData), - timelineId: activeTab != null ? `${timelineId}-${activeTab}` : timelineId, + timelineId: tabType != null ? `${timelineId}-${tabType}` : timelineId, truncate: true, values: getMappedNonEcsValue({ data, @@ -104,6 +117,17 @@ export const DataDrivenColumns = React.memo( })} + {hasRowRenderers && ( + +

    {i18n.EVENT_HAS_AN_EVENT_RENDERER(ariaRowindex)}

    +
    + )} + + {notesCount && ( + +

    {i18n.EVENT_HAS_NOTES({ row: ariaRowindex, notesCount })}

    +
    + )}
    ))}
    diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts index 80199e0026ac32..63086d56d07530 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/data_driven_columns/translations.ts @@ -11,3 +11,17 @@ export const YOU_ARE_IN_A_TABLE_CELL = ({ column, row }: { column: number; row: values: { column, row }, defaultMessage: 'You are in a table cell. row: {row}, column: {column}', }); + +export const EVENT_HAS_AN_EVENT_RENDERER = (row: number) => + i18n.translate('xpack.securitySolution.timeline.eventHasEventRendererScreenReaderOnly', { + values: { row }, + defaultMessage: + 'The event in row {row} has an event renderer. Press shift + down arrow to focus it.', + }); + +export const EVENT_HAS_NOTES = ({ notesCount, row }: { notesCount: number; row: number }) => + i18n.translate('xpack.securitySolution.timeline.eventHasNotesScreenReaderOnly', { + values: { notesCount, row }, + defaultMessage: + 'The event in row {row} has {notesCount, plural, =1 {a note} other {{notesCount} notes}}. Press shift + right arrow to focus notes.', + }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx index 9bb8a695454d74..0525767e616bef 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.test.tsx @@ -36,8 +36,10 @@ describe('EventColumnView', () => { }, eventIdToNoteIds: {}, expanded: false, + hasRowRenderers: false, loading: false, loadingEventIds: [], + notesCount: 0, onEventToggled: jest.fn(), onPinEvent: jest.fn(), onRowSelected: jest.fn(), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx index 6aee6f9d4fdfab..ae8d2a47c7dc78 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/event_column_view.tsx @@ -35,7 +35,6 @@ import * as i18n from '../translations'; interface Props { id: string; actionsColumnWidth: number; - activeTab?: TimelineTabs; ariaRowindex: number; columnHeaders: ColumnHeaderOptions[]; columnRenderers: ColumnRenderer[]; @@ -46,15 +45,18 @@ interface Props { isEventPinned: boolean; isEventViewer?: boolean; loadingEventIds: Readonly; + notesCount: number; onEventToggled: () => void; onPinEvent: OnPinEvent; onRowSelected: OnRowSelected; onUnPinEvent: OnUnPinEvent; refetch: inputsModel.Refetch; onRuleChange?: () => void; + hasRowRenderers: boolean; selectedEventIds: Readonly>; showCheckboxes: boolean; showNotes: boolean; + tabType?: TimelineTabs; timelineId: string; toggleShowNotes: () => void; } @@ -65,7 +67,6 @@ export const EventColumnView = React.memo( ({ id, actionsColumnWidth, - activeTab, ariaRowindex, columnHeaders, columnRenderers, @@ -76,15 +77,18 @@ export const EventColumnView = React.memo( isEventPinned = false, isEventViewer = false, loadingEventIds, + notesCount, onEventToggled, onPinEvent, onRowSelected, onUnPinEvent, refetch, + hasRowRenderers, onRuleChange, selectedEventIds, showCheckboxes, showNotes, + tabType, timelineId, toggleShowNotes, }) => { @@ -225,12 +229,14 @@ export const EventColumnView = React.memo( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx index bce5f1293e66b8..92ae01b185f7a1 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/index.tsx @@ -24,7 +24,6 @@ import { eventIsPinned } from '../helpers'; const ARIA_ROW_INDEX_OFFSET = 2; interface Props { - activeTab?: TimelineTabs; actionsColumnWidth: number; browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; @@ -43,11 +42,11 @@ interface Props { rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; + tabType?: TimelineTabs; } const EventsComponent: React.FC = ({ actionsColumnWidth, - activeTab, browserFields, columnHeaders, columnRenderers, @@ -65,11 +64,11 @@ const EventsComponent: React.FC = ({ rowRenderers, selectedEventIds, showCheckboxes, + tabType, }) => ( {data.map((event, i) => ( = ({ eventIdToNoteIds={eventIdToNoteIds} isEventPinned={eventIsPinned({ eventId: event._id, pinnedEventIds })} isEventViewer={isEventViewer} - key={`${id}_${activeTab}_${event._id}_${event._index}`} + key={`${id}_${tabType}_${event._id}_${event._index}`} lastFocusedAriaColindex={lastFocusedAriaColindex} loadingEventIds={loadingEventIds} onRowSelected={onRowSelected} @@ -89,6 +88,7 @@ const EventsComponent: React.FC = ({ onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} + tabType={tabType} timelineId={id} /> ))} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx index 9802e4532b05bf..e3f5a744e8b7d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_event.tsx @@ -19,22 +19,22 @@ import { OnPinEvent, OnRowSelected } from '../../events'; import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../../helpers'; import { EventsTrGroup, EventsTrSupplement, EventsTrSupplementContainer } from '../../styles'; import { ColumnRenderer } from '../renderers/column_renderer'; - import { RowRenderer } from '../renderers/row_renderer'; import { isEventBuildingBlockType, getEventType } from '../helpers'; import { NoteCards } from '../../../notes/note_cards'; import { useEventDetailsWidthContext } from '../../../../../common/components/events_viewer/event_details_width_context'; import { EventColumnView } from './event_column_view'; -import { inputsModel } from '../../../../../common/store'; +import { appSelectors, inputsModel } from '../../../../../common/store'; import { timelineActions, timelineSelectors } from '../../../../store/timeline'; import { activeTimeline } from '../../../../containers/active_timeline_context'; +import { TimelineResultNote } from '../../../open_timeline/types'; +import { getRowRenderer } from '../renderers/get_row_renderer'; import { StatefulRowRenderer } from './stateful_row_renderer'; import { NOTES_BUTTON_CLASS_NAME } from '../../properties/helpers'; import { timelineDefaults } from '../../../../store/timeline/defaults'; interface Props { actionsColumnWidth: number; - activeTab?: TimelineTabs; containerRef: React.MutableRefObject; browserFields: BrowserFields; columnHeaders: ColumnHeaderOptions[]; @@ -52,6 +52,7 @@ interface Props { rowRenderers: RowRenderer[]; selectedEventIds: Readonly>; showCheckboxes: boolean; + tabType?: TimelineTabs; timelineId: string; } @@ -66,7 +67,6 @@ EventsTrSupplementContainerWrapper.displayName = 'EventsTrSupplementContainerWra const StatefulEventComponent: React.FC = ({ actionsColumnWidth, - activeTab, browserFields, containerRef, columnHeaders, @@ -84,6 +84,7 @@ const StatefulEventComponent: React.FC = ({ ariaRowindex, selectedEventIds, showCheckboxes, + tabType, timelineId, }) => { const trGroupRef = useRef(null); @@ -93,12 +94,31 @@ const StatefulEventComponent: React.FC = ({ const expandedEvent = useDeepEqualSelector( (state) => (getTimeline(state, timelineId) ?? timelineDefaults).expandedEvent ); - + const getNotesByIds = useMemo(() => appSelectors.notesByIdsSelector(), []); + const notesById = useDeepEqualSelector(getNotesByIds); + const noteIds: string[] = eventIdToNoteIds[event._id] || emptyNotes; const isExpanded = useMemo(() => expandedEvent && expandedEvent.eventId === event._id, [ event._id, expandedEvent, ]); + const notes: TimelineResultNote[] = useMemo( + () => + appSelectors.getNotes(notesById, noteIds).map((note) => ({ + savedObjectId: note.saveObjectId, + note: note.note, + noteId: note.id, + updated: (note.lastEdit ?? note.created).getTime(), + updatedBy: note.user, + })), + [notesById, noteIds] + ); + + const hasRowRenderers: boolean = useMemo(() => getRowRenderer(event.ecs, rowRenderers) != null, [ + event.ecs, + rowRenderers, + ]); + const onToggleShowNotes = useCallback(() => { const eventId = event._id; @@ -195,7 +215,6 @@ const StatefulEventComponent: React.FC = ({ = ({ ecsData={event.ecs} eventIdToNoteIds={eventIdToNoteIds} expanded={isExpanded} + hasRowRenderers={hasRowRenderers} isEventPinned={isEventPinned} isEventViewer={isEventViewer} loadingEventIds={loadingEventIds} + notesCount={notes.length} onEventToggled={handleOnEventToggled} onPinEvent={onPinEvent} onRowSelected={onRowSelected} @@ -215,6 +236,7 @@ const StatefulEventComponent: React.FC = ({ selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} showNotes={!!showNotes[event._id]} + tabType={tabType} timelineId={timelineId} toggleShowNotes={onToggleShowNotes} /> @@ -228,7 +250,7 @@ const StatefulEventComponent: React.FC = ({ ariaRowindex={ariaRowindex} associateNote={associateNote} data-test-subj="note-cards" - noteIds={eventIdToNoteIds[event._id] || emptyNotes} + notes={notes} showAddNote={!!showNotes[event._id]} toggleShowAddNote={onToggleShowNotes} /> diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx index 1628824b46a08d..4000ebcfd767a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/events/stateful_row_renderer/index.tsx @@ -12,6 +12,7 @@ import { BrowserFields } from '../../../../../../common/containers/source'; import { ARIA_COLINDEX_ATTRIBUTE, ARIA_ROWINDEX_ATTRIBUTE, + getRowRendererClassName, } from '../../../../../../common/components/accessibility/helpers'; import { TimelineItem } from '../../../../../../../common/search_strategy/timeline'; import { getRowRenderer } from '../../renderers/get_row_renderer'; @@ -59,28 +60,44 @@ export const StatefulRowRenderer = ({ rowindexAttribute: ARIA_ROWINDEX_ATTRIBUTE, }); + const rowRenderer = useMemo(() => getRowRenderer(event.ecs, rowRenderers), [ + event.ecs, + rowRenderers, + ]); + const content = useMemo( - () => ( - - -

    {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}

    -
    -
    - {getRowRenderer(event.ecs, rowRenderers).renderRow({ - browserFields, - data: event.ecs, - timelineId, - })} + () => + rowRenderer && ( + // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions +
    + + + +

    {i18n.YOU_ARE_IN_AN_EVENT_RENDERER(ariaRowindex)}

    +
    +
    + {rowRenderer.renderRow({ + browserFields, + data: event.ecs, + timelineId, + })} +
    +
    +
    - - ), - [ariaRowindex, browserFields, event.ecs, focusOwnership, onKeyDown, rowRenderers, timelineId] + ), + [ + ariaRowindex, + browserFields, + event.ecs, + focusOwnership, + onFocus, + onKeyDown, + onOutsideClick, + rowRenderer, + timelineId, + ] ); - return ( - // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -
    - {content} -
    - ); + return content; }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx index 3470dba636aa8c..0295d44b646d77 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/helpers.tsx @@ -160,3 +160,9 @@ const InvestigateInResolverActionComponent: React.FC { setSelected: (jest.fn() as unknown) as StatefulBodyProps['setSelected'], sort: mockSort, showCheckboxes: false, - activeTab: TimelineTabs.query, + tabType: TimelineTabs.query, totalPages: 1, }; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx index f6190b39214e90..4a33d0d3af33e9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.tsx @@ -21,7 +21,7 @@ import { BrowserFields } from '../../../../common/containers/source'; import { TimelineItem } from '../../../../../common/search_strategy/timeline'; import { inputsModel, State } from '../../../../common/store'; import { useManageTimeline } from '../../manage_timeline'; -import { ColumnHeaderOptions, TimelineModel } from '../../../store/timeline/model'; +import { ColumnHeaderOptions, TimelineModel, TimelineTabs } from '../../../store/timeline/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { OnRowSelected, OnSelectAll } from '../events'; @@ -43,6 +43,7 @@ interface OwnProps { isEventViewer?: boolean; sort: Sort[]; refetch: inputsModel.Refetch; + tabType: TimelineTabs; totalPages: number; onRuleChange?: () => void; } @@ -60,7 +61,6 @@ export type StatefulBodyProps = OwnProps & PropsFromRedux; export const BodyComponent = React.memo( ({ - activeTab, activePage, browserFields, columnHeaders, @@ -79,6 +79,7 @@ export const BodyComponent = React.memo( showCheckboxes, refetch, sort, + tabType, totalPages, }) => { const containerRef = useRef(null); @@ -200,7 +201,6 @@ export const BodyComponent = React.memo( ( onRuleChange={onRuleChange} selectedEventIds={selectedEventIds} showCheckboxes={showCheckboxes} + tabType={tabType} /> @@ -225,7 +226,6 @@ export const BodyComponent = React.memo( ); }, (prevProps, nextProps) => - prevProps.activeTab === nextProps.activeTab && deepEqual(prevProps.browserFields, nextProps.browserFields) && deepEqual(prevProps.columnHeaders, nextProps.columnHeaders) && deepEqual(prevProps.data, nextProps.data) && @@ -238,7 +238,8 @@ export const BodyComponent = React.memo( prevProps.id === nextProps.id && prevProps.isEventViewer === nextProps.isEventViewer && prevProps.isSelectAllChecked === nextProps.isSelectAllChecked && - prevProps.showCheckboxes === nextProps.showCheckboxes + prevProps.showCheckboxes === nextProps.showCheckboxes && + prevProps.tabType === nextProps.tabType ); BodyComponent.displayName = 'BodyComponent'; @@ -253,7 +254,6 @@ const makeMapStateToProps = () => { const mapStateToProps = (state: State, { browserFields, id }: OwnProps) => { const timeline: TimelineModel = getTimeline(state, id) ?? timelineDefaults; const { - activeTab, columns, eventIdToNoteIds, excludedRowRendererIds, @@ -265,7 +265,6 @@ const makeMapStateToProps = () => { } = timeline; return { - activeTab: id === TimelineId.active ? activeTab : undefined, columnHeaders: memoizedColumnHeaders(columns, browserFields), eventIdToNoteIds, excludedRowRendererIds, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx index b4fdc427d9db3e..f3a914ff4be29c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -48,7 +48,7 @@ describe('get_column_renderer', () => { test('renders correctly against snapshot', () => { const rowRenderer = getRowRenderer(nonSuricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, timelineId: 'test', @@ -60,7 +60,7 @@ describe('get_column_renderer', () => { test('should render plain row data when it is a non suricata row', () => { const rowRenderer = getRowRenderer(nonSuricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: nonSuricata, timelineId: 'test', @@ -75,7 +75,7 @@ describe('get_column_renderer', () => { test('should render a suricata row data when it is a suricata row', () => { const rowRenderer = getRowRenderer(suricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: suricata, timelineId: 'test', @@ -93,7 +93,7 @@ describe('get_column_renderer', () => { test('should render a suricata row data if event.category is network_traffic', () => { suricata.event = { ...suricata.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(suricata, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: suricata, timelineId: 'test', @@ -111,7 +111,7 @@ describe('get_column_renderer', () => { test('should render a zeek row data if event.category is network_traffic', () => { zeek.event = { ...zeek.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(zeek, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: zeek, timelineId: 'test', @@ -129,7 +129,7 @@ describe('get_column_renderer', () => { test('should render a system row data if event.category is network_traffic', () => { system.event = { ...system.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(system, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: system, timelineId: 'test', @@ -147,7 +147,7 @@ describe('get_column_renderer', () => { test('should render a auditd row data if event.category is network_traffic', () => { auditd.event = { ...auditd.event, ...{ category: ['network_traffic'] } }; const rowRenderer = getRowRenderer(auditd, rowRenderers); - const row = rowRenderer.renderRow({ + const row = rowRenderer?.renderRow({ browserFields: mockBrowserFields, data: auditd, timelineId: 'test', diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts index 779d54216e26c8..1662cf4037cac3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/get_row_renderer.ts @@ -7,15 +7,5 @@ import { Ecs } from '../../../../../../common/ecs'; import { RowRenderer } from './row_renderer'; -const unhandledRowRenderer = (): never => { - throw new Error('Unhandled Row Renderer'); -}; - -export const getRowRenderer = (ecs: Ecs, rowRenderers: RowRenderer[]): RowRenderer => { - const renderer = rowRenderers.find((rowRenderer) => rowRenderer.isInstance(ecs)); - if (renderer == null) { - return unhandledRowRenderer(); - } else { - return renderer; - } -}; +export const getRowRenderer = (ecs: Ecs, rowRenderers: RowRenderer[]): RowRenderer | null => + rowRenderers.find((rowRenderer) => rowRenderer.isInstance(ecs)) ?? null; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts index 8e95fc3ad238a6..f4498b10e4c8d8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/index.ts @@ -9,7 +9,6 @@ import { ColumnRenderer } from './column_renderer'; import { emptyColumnRenderer } from './empty_column_renderer'; import { netflowRowRenderer } from './netflow/netflow_row_renderer'; import { plainColumnRenderer } from './plain_column_renderer'; -import { plainRowRenderer } from './plain_row_renderer'; import { RowRenderer } from './row_renderer'; import { suricataRowRenderer } from './suricata/suricata_row_renderer'; import { unknownColumnRenderer } from './unknown_column_renderer'; @@ -29,7 +28,6 @@ export const rowRenderers: RowRenderer[] = [ suricataRowRenderer, zeekRowRenderer, netflowRowRenderer, - plainRowRenderer, // falls-back to the plain row renderer ]; export const columnRenderers: ColumnRenderer[] = [ diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index 45c190c42605c2..a0d2ca57f90b36 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -23,7 +23,7 @@ import { EventDetailsWidthProvider } from '../../../../common/components/events_ import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { timelineDefaults } from '../../../store/timeline/defaults'; import { useSourcererScope } from '../../../../common/containers/sourcerer'; -import { TimelineModel } from '../../../store/timeline/model'; +import { TimelineModel, TimelineTabs } from '../../../store/timeline/model'; import { EventDetails } from '../event_details'; import { ToggleExpandedEvent } from '../../../store/timeline/actions'; import { State } from '../../../../common/store'; @@ -183,6 +183,7 @@ export const PinnedTabContentComponent: React.FC = ({ id={timelineId} refetch={refetch} sort={sort} + tabType={TimelineTabs.pinned} totalPages={calculateTotalPages({ itemsCount: totalCount, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index f6d6654d7fecee..c0840d58174b32 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -330,6 +330,7 @@ export const QueryTabContentComponent: React.FC = ({ id={timelineId} refetch={refetch} sort={sort} + tabType={TimelineTabs.query} totalPages={calculateTotalPages({ itemsCount: totalCount, itemsPerPage, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx index 7f0809cf9b9d8f..c97571fbbd6f35 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/tabs_content/index.tsx @@ -199,7 +199,7 @@ const TabsContentComponent: React.FC = ({ timelineId, graphEve disabled={!graphEventId} key={TimelineTabs.graph} > - {i18n.GRAPH_TAB} + {i18n.ANALYZER_TAB} Date: Sat, 19 Dec 2020 21:56:06 -0600 Subject: [PATCH 30/40] [index patterns] Fleep app - Keep saved object field list until field caps provides fields (#85370) --- ...s-data-public.indexpattern.allownoindex.md | 13 +++++ ...ublic.indexpattern.getassavedobjectbody.md | 2 + ...plugin-plugins-data-public.indexpattern.md | 1 + ...lic.indexpatternattributes.allownoindex.md | 13 +++++ ...gins-data-public.indexpatternattributes.md | 1 + ...ta-public.indexpatternspec.allownoindex.md | 11 ++++ ...in-plugins-data-public.indexpatternspec.md | 1 + ...s-data-server.indexpattern.allownoindex.md | 13 +++++ ...erver.indexpattern.getassavedobjectbody.md | 2 + ...plugin-plugins-data-server.indexpattern.md | 1 + ...ver.indexpatternattributes.allownoindex.md | 13 +++++ ...gins-data-server.indexpatternattributes.md | 1 + .../__snapshots__/index_pattern.test.ts.snap | 1 + .../__snapshots__/index_patterns.test.ts.snap | 1 + .../index_patterns/index_pattern.ts | 7 +++ .../index_patterns/index_patterns.test.ts | 15 ++++++ .../index_patterns/index_patterns.ts | 19 ++++++- .../data/common/index_patterns/types.ts | 6 +++ .../index_patterns_api_client.ts | 5 +- src/plugins/data/public/public.api.md | 7 ++- .../index_patterns_api_client.ts | 10 +++- .../data/server/index_patterns/routes.ts | 12 ++++- .../routes/create_index_pattern.ts | 1 + .../routes/update_index_pattern.ts | 1 + .../index_pattern_migrations.test.ts | 51 +++++++++++++++++++ .../saved_objects/index_pattern_migrations.ts | 9 ++++ src/plugins/data/server/server.api.md | 5 +- x-pack/plugins/fleet/common/constants/epm.ts | 1 - .../plugins/fleet/server/constants/index.ts | 1 - .../__snapshots__/install.test.ts.snap | 3 +- .../epm/kibana/index_pattern/install.ts | 37 ++------------ x-pack/plugins/fleet/server/services/setup.ts | 2 - .../test/fleet_api_integration/apis/index.js | 2 - .../test/fleet_api_integration/apis/setup.ts | 32 ------------ .../apps/endpoint/resolver.ts | 28 +++++++--- .../apis/resolver/events.ts | 2 +- .../apis/resolver/tree.ts | 2 +- 37 files changed, 241 insertions(+), 91 deletions(-) create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md create mode 100644 docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md delete mode 100644 x-pack/test/fleet_api_integration/apis/setup.ts diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md new file mode 100644 index 00000000000000..5e397d11b0a89e --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPattern](./kibana-plugin-plugins-data-public.indexpattern.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpattern.allownoindex.md) + +## IndexPattern.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +readonly allowNoIndex: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md index a370341000960e..b318427012c0ac 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.getassavedobjectbody.md @@ -19,6 +19,7 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; ``` Returns: @@ -33,5 +34,6 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md index 179148265e68d3..b640ef1b89606b 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-public.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpattern.fieldattrs.md) | | FieldAttrs | | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpattern.fieldformatmap.md) | | Record<string, any> | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md new file mode 100644 index 00000000000000..9438f38194493d --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-public.indexpatternattributes.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md) + +## IndexPatternAttributes.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md index c5ea38278e8208..1bbede5658942a 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternattributes.md @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternattributes.allownoindex.md) | boolean | prevents errors when index pattern exists before indices | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldattrs.md) | string | | | [fieldFormatMap](./kibana-plugin-plugins-data-public.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-public.indexpatternattributes.fields.md) | string | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md new file mode 100644 index 00000000000000..50adef8268694b --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternSpec](./kibana-plugin-plugins-data-public.indexpatternspec.md) > [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md) + +## IndexPatternSpec.allowNoIndex property + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md index 06917fcac1b4dc..9357ad7d5077e9 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternspec.md @@ -14,6 +14,7 @@ export interface IndexPatternSpec | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-public.indexpatternspec.allownoindex.md) | boolean | | | [fieldAttrs](./kibana-plugin-plugins-data-public.indexpatternspec.fieldattrs.md) | FieldAttrs | | | [fieldFormats](./kibana-plugin-plugins-data-public.indexpatternspec.fieldformats.md) | Record<string, SerializedFieldFormat> | | | [fields](./kibana-plugin-plugins-data-public.indexpatternspec.fields.md) | IndexPatternFieldMap | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md new file mode 100644 index 00000000000000..fe7bec70196c85 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPattern](./kibana-plugin-plugins-data-server.indexpattern.md) > [allowNoIndex](./kibana-plugin-plugins-data-server.indexpattern.allownoindex.md) + +## IndexPattern.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +readonly allowNoIndex: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md index 274a475872b0bc..7d70af4b535fea 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.getassavedobjectbody.md @@ -19,6 +19,7 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; ``` Returns: @@ -33,5 +34,6 @@ getAsSavedObjectBody(): { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md index b2cb217fecaa20..54f020e57cf4a6 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpattern.md @@ -20,6 +20,7 @@ export declare class IndexPattern implements IIndexPattern | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-server.indexpattern.allownoindex.md) | | boolean | prevents errors when index pattern exists before indices | | [deleteFieldFormat](./kibana-plugin-plugins-data-server.indexpattern.deletefieldformat.md) | | (fieldName: string) => void | | | [fieldAttrs](./kibana-plugin-plugins-data-server.indexpattern.fieldattrs.md) | | FieldAttrs | | | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpattern.fieldformatmap.md) | | Record<string, any> | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md new file mode 100644 index 00000000000000..1255a6fe9f0cae --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) > [allowNoIndex](./kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md) + +## IndexPatternAttributes.allowNoIndex property + +prevents errors when index pattern exists before indices + +Signature: + +```typescript +allowNoIndex?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md index 6559b4d7110bea..b9b9f955c7ab59 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.indexpatternattributes.md @@ -14,6 +14,7 @@ export interface IndexPatternAttributes | Property | Type | Description | | --- | --- | --- | +| [allowNoIndex](./kibana-plugin-plugins-data-server.indexpatternattributes.allownoindex.md) | boolean | prevents errors when index pattern exists before indices | | [fieldAttrs](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldattrs.md) | string | | | [fieldFormatMap](./kibana-plugin-plugins-data-server.indexpatternattributes.fieldformatmap.md) | string | | | [fields](./kibana-plugin-plugins-data-server.indexpatternattributes.fields.md) | string | | diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap index 19ec286307a09d..76de2b2662bb02 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_pattern.test.ts.snap @@ -2,6 +2,7 @@ exports[`IndexPattern toSpec should match snapshot 1`] = ` Object { + "allowNoIndex": false, "fieldAttrs": Object {}, "fieldFormats": Object {}, "fields": Object { diff --git a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap index c020e7595c5657..bad74430b89668 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap +++ b/src/plugins/data/common/index_patterns/index_patterns/__snapshots__/index_patterns.test.ts.snap @@ -2,6 +2,7 @@ exports[`IndexPatterns savedObjectToSpec 1`] = ` Object { + "allowNoIndex": undefined, "fieldAttrs": Object {}, "fieldFormats": Object { "field": Object {}, diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts index 4c89cbeb446a07..590ff872f3bf93 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts @@ -74,6 +74,10 @@ export class IndexPattern implements IIndexPattern { private fieldFormats: FieldFormatsStartCommon; // make private once manual field refresh is removed public fieldAttrs: FieldAttrs; + /** + * prevents errors when index pattern exists before indices + */ + public readonly allowNoIndex: boolean = false; constructor({ spec = {}, @@ -110,6 +114,7 @@ export class IndexPattern implements IIndexPattern { this.typeMeta = spec.typeMeta; this.fieldAttrs = spec.fieldAttrs || {}; this.intervalName = spec.intervalName; + this.allowNoIndex = spec.allowNoIndex || false; } /** @@ -204,6 +209,7 @@ export class IndexPattern implements IIndexPattern { fieldFormats: this.fieldFormatMap, fieldAttrs: this.fieldAttrs, intervalName: this.intervalName, + allowNoIndex: this.allowNoIndex, }; } @@ -309,6 +315,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap, type: this.type, typeMeta: this.typeMeta ? JSON.stringify(this.typeMeta) : undefined, + allowNoIndex: this.allowNoIndex ? this.allowNoIndex : undefined, }; } diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts index 2a203b57d201b2..3d32742c168ad7 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.test.ts @@ -114,6 +114,21 @@ describe('IndexPatterns', () => { SOClientGetDelay = 0; }); + test('allowNoIndex flag preserves existing fields when index is missing', async () => { + const id = '2'; + setDocsourcePayload(id, { + id: 'foo', + version: 'foo', + attributes: { + title: 'something', + allowNoIndex: true, + fields: '[{"name":"field"}]', + }, + }); + + expect((await indexPatterns.get(id)).fields.length).toBe(1); + }); + test('savedObjectCache pre-fetches only title', async () => { expect(await indexPatterns.getIds()).toEqual(['id']); expect(savedObjectsClient.find).toHaveBeenCalledWith({ diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts index 0235f748ec1e04..3333dba36fe69c 100644 --- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts +++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts @@ -222,6 +222,7 @@ export class IndexPatternsService { metaFields, type: options.type, rollupIndex: options.rollupIndex, + allowNoIndex: options.allowNoIndex, }); }; @@ -281,10 +282,21 @@ export class IndexPatternsService { options: GetFieldsOptions, fieldAttrs: FieldAttrs = {} ) => { - const scriptedFields = Object.values(fields).filter((field) => field.scripted); + const fieldsAsArr = Object.values(fields); + const scriptedFields = fieldsAsArr.filter((field) => field.scripted); try { + let updatedFieldList: FieldSpec[]; const newFields = (await this.getFieldsForWildcard(options)) as FieldSpec[]; - return this.fieldArrayToMap([...newFields, ...scriptedFields], fieldAttrs); + + // If allowNoIndex, only update field list if field caps finds fields. To support + // beats creating index pattern and dashboard before docs + if (!options.allowNoIndex || (newFields && newFields.length > 5)) { + updatedFieldList = [...newFields, ...scriptedFields]; + } else { + updatedFieldList = fieldsAsArr; + } + + return this.fieldArrayToMap(updatedFieldList, fieldAttrs); } catch (err) { if (err instanceof IndexPatternMissingIndices) { this.onNotification({ title: (err as any).message, color: 'danger', iconType: 'alert' }); @@ -334,6 +346,7 @@ export class IndexPatternsService { typeMeta, type, fieldAttrs, + allowNoIndex, }, } = savedObject; @@ -355,6 +368,7 @@ export class IndexPatternsService { type, fieldFormats: parsedFieldFormatMap, fieldAttrs: parsedFieldAttrs, + allowNoIndex, }; }; @@ -384,6 +398,7 @@ export class IndexPatternsService { metaFields: await this.config.get(UI_SETTINGS.META_FIELDS), type, rollupIndex: typeMeta?.params?.rollup_index, + allowNoIndex: spec.allowNoIndex, }, spec.fieldAttrs ); diff --git a/src/plugins/data/common/index_patterns/types.ts b/src/plugins/data/common/index_patterns/types.ts index 8d9b29175162e8..12496b07d34820 100644 --- a/src/plugins/data/common/index_patterns/types.ts +++ b/src/plugins/data/common/index_patterns/types.ts @@ -49,6 +49,10 @@ export interface IndexPatternAttributes { sourceFilters?: string; fieldFormatMap?: string; fieldAttrs?: string; + /** + * prevents errors when index pattern exists before indices + */ + allowNoIndex?: boolean; } export interface FieldAttrs { @@ -101,6 +105,7 @@ export interface GetFieldsOptions { lookBack?: boolean; metaFields?: string[]; rollupIndex?: string; + allowNoIndex?: boolean; } export interface GetFieldsOptionsTimePattern { @@ -193,6 +198,7 @@ export interface IndexPatternSpec { type?: string; fieldFormats?: Record; fieldAttrs?: FieldAttrs; + allowNoIndex?: boolean; } export interface SourceFilter { diff --git a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts index ca0f35d6612b2e..36a193a4f6f94b 100644 --- a/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/public/index_patterns/index_patterns/index_patterns_api_client.ts @@ -64,12 +64,13 @@ export class IndexPatternsApiClient implements IIndexPatternsApiClient { }).then((resp: any) => resp.fields); } - getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { + getFieldsForWildcard({ pattern, metaFields, type, rollupIndex, allowNoIndex }: GetFieldsOptions) { return this._request(this._getUrl(['_fields_for_wildcard']), { pattern, meta_fields: metaFields, type, rollup_index: rollupIndex, - }).then((resp: any) => resp.fields); + allow_no_index: allowNoIndex, + }).then((resp: any) => resp.fields || []); } } diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 120540ddb92ecf..3493844a71ac15 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1256,6 +1256,7 @@ export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; + readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts @@ -1296,6 +1297,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; // (undocumented) getComputedFields(): { @@ -1388,6 +1390,7 @@ export type IndexPatternAggRestrictions = Record, 'isLo // // @public (undocumented) export interface IndexPatternSpec { + // (undocumented) + allowNoIndex?: boolean; // (undocumented) fieldAttrs?: FieldAttrs; // (undocumented) @@ -2564,7 +2569,7 @@ export const UI_SETTINGS: { // src/plugins/data/common/es_query/filters/phrase_filter.ts:33:3 - (ae-forgotten-export) The symbol "PhraseFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/es_query/filters/phrases_filter.ts:31:3 - (ae-forgotten-export) The symbol "PhrasesFilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:128:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:150:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/search_source/search_source.ts:197:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:67:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts index 21a3bf6e73e611..9023044184df31 100644 --- a/src/plugins/data/server/index_patterns/index_patterns_api_client.ts +++ b/src/plugins/data/server/index_patterns/index_patterns_api_client.ts @@ -30,8 +30,14 @@ export class IndexPatternsApiServer implements IIndexPatternsApiClient { constructor(elasticsearchClient: ElasticsearchClient) { this.esClient = elasticsearchClient; } - async getFieldsForWildcard({ pattern, metaFields, type, rollupIndex }: GetFieldsOptions) { - const indexPatterns = new IndexPatternsFetcher(this.esClient); + async getFieldsForWildcard({ + pattern, + metaFields, + type, + rollupIndex, + allowNoIndex, + }: GetFieldsOptions) { + const indexPatterns = new IndexPatternsFetcher(this.esClient, allowNoIndex); return await indexPatterns.getFieldsForWildcard({ pattern, metaFields, diff --git a/src/plugins/data/server/index_patterns/routes.ts b/src/plugins/data/server/index_patterns/routes.ts index e9dbc2e972c688..f0b51e456337f5 100644 --- a/src/plugins/data/server/index_patterns/routes.ts +++ b/src/plugins/data/server/index_patterns/routes.ts @@ -75,13 +75,20 @@ export function registerRoutes( }), type: schema.maybe(schema.string()), rollup_index: schema.maybe(schema.string()), + allow_no_index: schema.maybe(schema.boolean()), }), }, }, async (context, request, response) => { const { asCurrentUser } = context.core.elasticsearch.client; const indexPatterns = new IndexPatternsFetcher(asCurrentUser); - const { pattern, meta_fields: metaFields, type, rollup_index: rollupIndex } = request.query; + const { + pattern, + meta_fields: metaFields, + type, + rollup_index: rollupIndex, + allow_no_index: allowNoIndex, + } = request.query; let parsedFields: string[] = []; try { @@ -96,6 +103,9 @@ export function registerRoutes( metaFields: parsedFields, type, rollupIndex, + fieldCapsOptions: { + allow_no_indices: allowNoIndex || false, + }, }); return response.ok({ diff --git a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts index 57a745b19748da..1163fd2dc9953e 100644 --- a/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/create_index_pattern.ts @@ -50,6 +50,7 @@ const indexPatternSpecSchema = schema.object({ }) ) ), + allowNoIndex: schema.maybe(schema.boolean()), }); export const registerCreateIndexPatternRoute = ( diff --git a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts index 10567544af6ea2..8bd59e47730fd4 100644 --- a/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts +++ b/src/plugins/data/server/index_patterns/routes/update_index_pattern.ts @@ -38,6 +38,7 @@ const indexPatternUpdateSchema = schema.object({ ), fieldFormats: schema.maybe(schema.recordOf(schema.string(), serializedFieldFormatSchema)), fields: schema.maybe(schema.recordOf(schema.string(), fieldSpecSchema)), + allowNoIndex: schema.maybe(schema.boolean()), }); export const registerUpdateIndexPatternRoute = ( diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts index b1410e24986676..3b223e6fdb9b21 100644 --- a/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.test.ts @@ -94,4 +94,55 @@ Object { expect(migrationFn(input, savedObjectMigrationContext)).toEqual(expected); }); }); + + describe('7.11.0', () => { + const migrationFn = indexPatternSavedObjectTypeMigrations['7.11.0']; + + test('should set allowNoIndex', () => { + const input = { + type: 'index-pattern', + id: 'logs-*', + attributes: {}, + }; + const expected = { + type: 'index-pattern', + id: 'logs-*', + attributes: { + allowNoIndex: true, + }, + }; + + expect(migrationFn(input, savedObjectMigrationContext)).toEqual(expected); + + const input2 = { + type: 'index-pattern', + id: 'metrics-*', + attributes: {}, + }; + const expected2 = { + type: 'index-pattern', + id: 'metrics-*', + attributes: { + allowNoIndex: true, + }, + }; + + expect(migrationFn(input2, savedObjectMigrationContext)).toEqual(expected2); + + const input3 = { + type: 'index-pattern', + id: 'xxx', + attributes: {}, + }; + const expected3 = { + type: 'index-pattern', + id: 'xxx', + attributes: { + allowNoIndex: undefined, + }, + }; + + expect(migrationFn(input3, savedObjectMigrationContext)).toEqual(expected3); + }); + }); }); diff --git a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts index 768041a376ad1d..4650aeefba0565 100644 --- a/src/plugins/data/server/saved_objects/index_pattern_migrations.ts +++ b/src/plugins/data/server/saved_objects/index_pattern_migrations.ts @@ -54,7 +54,16 @@ const migrateSubTypeAndParentFieldProperties: SavedObjectMigrationFn = }; }; +const addAllowNoIndex: SavedObjectMigrationFn = (doc) => ({ + ...doc, + attributes: { + ...doc.attributes, + allowNoIndex: doc.id === 'logs-*' || doc.id === 'metrics-*' || undefined, + }, +}); + export const indexPatternSavedObjectTypeMigrations = { '6.5.0': flow(migrateAttributeTypeAndAttributeTypeMeta), '7.6.0': flow(migrateSubTypeAndParentFieldProperties), + '7.11.0': flow(addAllowNoIndex), }; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 60d5e167921cc7..cd3527d5ad7ab2 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -689,6 +689,7 @@ export class IndexPattern implements IIndexPattern { // Warning: (ae-forgotten-export) The symbol "IndexPatternDeps" needs to be exported by the entry point index.d.ts constructor({ spec, fieldFormats, shortDotsEnable, metaFields, }: IndexPatternDeps); addScriptedField(name: string, script: string, fieldType?: string): Promise; + readonly allowNoIndex: boolean; // (undocumented) readonly deleteFieldFormat: (fieldName: string) => void; // Warning: (ae-forgotten-export) The symbol "FieldAttrs" needs to be exported by the entry point index.d.ts @@ -731,6 +732,7 @@ export class IndexPattern implements IIndexPattern { fieldFormatMap: string | undefined; type: string | undefined; typeMeta: string | undefined; + allowNoIndex: true | undefined; }; // (undocumented) getComputedFields(): { @@ -819,6 +821,7 @@ export class IndexPattern implements IIndexPattern { // // @public (undocumented) export interface IndexPatternAttributes { + allowNoIndex?: boolean; // (undocumented) fieldAttrs?: string; // (undocumented) @@ -1388,7 +1391,7 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/common/es_query/filters/meta_filter.ts:54:3 - (ae-forgotten-export) The symbol "FilterMeta" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:58:45 - (ae-forgotten-export) The symbol "IndexPatternFieldMap" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:64:5 - (ae-forgotten-export) The symbol "FormatFieldFn" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:128:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts +// src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:133:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildCustomFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:40:23 - (ae-forgotten-export) The symbol "buildFilter" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:57:23 - (ae-forgotten-export) The symbol "datatableToCSV" needs to be exported by the entry point index.d.ts diff --git a/x-pack/plugins/fleet/common/constants/epm.ts b/x-pack/plugins/fleet/common/constants/epm.ts index 287b7ccdb88e04..b94c2cd12cd5f4 100644 --- a/x-pack/plugins/fleet/common/constants/epm.ts +++ b/x-pack/plugins/fleet/common/constants/epm.ts @@ -6,7 +6,6 @@ export const PACKAGES_SAVED_OBJECT_TYPE = 'epm-packages'; export const INDEX_PATTERN_SAVED_OBJECT_TYPE = 'index-pattern'; -export const INDEX_PATTERN_PLACEHOLDER_SUFFIX = '-index_pattern_placeholder'; export const MAX_TIME_COMPLETE_INSTALL = 60000; export const requiredPackages = { diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index b1d7318ff51070..5d00b966342142 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -14,7 +14,6 @@ export { AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL, AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS, AGENT_UPDATE_ACTIONS_INTERVAL_MS, - INDEX_PATTERN_PLACEHOLDER_SUFFIX, MAX_TIME_COMPLETE_INSTALL, // Routes LIMITED_CONCURRENCY_ROUTE_TAG, diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap index d8495840453f3d..862e82589b9bc9 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/__snapshots__/install.test.ts.snap @@ -41,7 +41,8 @@ exports[`creating index patterns from yaml fields createIndexPattern function cr "title": "logs-*", "timeFieldName": "@timestamp", "fields": "[{\\"name\\":\\"coredns.id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.allParams\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.length\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.size\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.query.class\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.query.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.query.type\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.flags\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"coredns.response.size\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"coredns.dnssec_ok\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"boolean\\"},{\\"name\\":\\"@timestamp\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"date\\"},{\\"name\\":\\"labels\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"message\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"tags\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.ephemeral_id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.id\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.type\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"agent.version\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"as.number\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"number\\"},{\\"name\\":\\"as.organization.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"nginx.access.remote_ip_list\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.body_sent.bytes\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.method\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.url\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.http_version\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.response_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.referrer\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.agent\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.device\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.os_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.user_agent.original\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.continent_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"nginx.access.geoip.country_iso_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.location\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.city_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"nginx.access.geoip.region_iso_code\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true},{\\"name\\":\\"source.geo.continent_name\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country.keyword\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":true,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"},{\\"name\\":\\"country.text\\",\\"count\\":0,\\"scripted\\":false,\\"indexed\\":true,\\"analyzed\\":false,\\"searchable\\":true,\\"aggregatable\\":false,\\"doc_values\\":true,\\"readFromDocValues\\":true,\\"type\\":\\"string\\"}]", - "fieldFormatMap": "{\\"coredns.allParams\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQueryWeight\\",\\"inputFormat\\":\\"inputFormatVal,\\",\\"outputFormat\\":\\"outputFormalVal,\\",\\"outputPrecision\\":\\"3,\\",\\"labelTemplate\\":\\"labelTemplateVal,\\",\\"urlTemplate\\":\\"urlTemplateVal,\\"}},\\"coredns.query.length\\":{\\"params\\":{\\"pattern\\":\\"patternValQueryLength\\"}},\\"coredns.query.size\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQuerySize\\"}},\\"coredns.response.size\\":{\\"id\\":\\"bytes\\"}}" + "fieldFormatMap": "{\\"coredns.allParams\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQueryWeight\\",\\"inputFormat\\":\\"inputFormatVal,\\",\\"outputFormat\\":\\"outputFormalVal,\\",\\"outputPrecision\\":\\"3,\\",\\"labelTemplate\\":\\"labelTemplateVal,\\",\\"urlTemplate\\":\\"urlTemplateVal,\\"}},\\"coredns.query.length\\":{\\"params\\":{\\"pattern\\":\\"patternValQueryLength\\"}},\\"coredns.query.size\\":{\\"id\\":\\"bytes\\",\\"params\\":{\\"pattern\\":\\"patternValQuerySize\\"}},\\"coredns.response.size\\":{\\"id\\":\\"bytes\\"}}", + "allowNoIndex": true } `; diff --git a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts index bdb6744745c97a..d5077308a53016 100644 --- a/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts +++ b/x-pack/plugins/fleet/server/services/epm/kibana/index_pattern/install.ts @@ -5,15 +5,11 @@ */ import { SavedObjectsClientContract } from 'src/core/server'; -import { - INDEX_PATTERN_SAVED_OBJECT_TYPE, - INDEX_PATTERN_PLACEHOLDER_SUFFIX, -} from '../../../../constants'; +import { INDEX_PATTERN_SAVED_OBJECT_TYPE } from '../../../../constants'; import { loadFieldsFromYaml, Fields, Field } from '../../fields/field'; import { dataTypes, installationStatuses } from '../../../../../common/constants'; import { ArchivePackage, InstallSource, ValueOf } from '../../../../../common/types'; -import { RegistryPackage, CallESAsCurrentUser, DataType } from '../../../../types'; -import { appContextService } from '../../../../services'; +import { RegistryPackage, DataType } from '../../../../types'; import { getPackageFromSource, getPackageSavedObjects } from '../../packages/get'; interface FieldFormatMap { @@ -172,6 +168,7 @@ export const createIndexPattern = (indexPatternType: string, fields: Fields) => timeFieldName: '@timestamp', fields: JSON.stringify(indexPatternFields), fieldFormatMap: JSON.stringify(fieldFormatMap), + allowNoIndex: true, }; }; @@ -382,31 +379,3 @@ const getFieldFormatParams = (field: Field): FieldFormatParams => { if (field.open_link_in_current_tab) params.openLinkInCurrentTab = field.open_link_in_current_tab; return params; }; - -export const ensureDefaultIndices = async (callCluster: CallESAsCurrentUser) => { - // create placeholder indices to supress errors in the kibana Dashboards app - // that no matching indices exist https://github.com/elastic/kibana/issues/62343 - const logger = appContextService.getLogger(); - return Promise.all( - Object.values(dataTypes).map(async (indexPattern) => { - const defaultIndexPatternName = indexPattern + INDEX_PATTERN_PLACEHOLDER_SUFFIX; - const indexExists = await callCluster('indices.exists', { index: defaultIndexPatternName }); - if (!indexExists) { - try { - await callCluster('indices.create', { - index: defaultIndexPatternName, - body: { - mappings: { - properties: { - '@timestamp': { type: 'date' }, - }, - }, - }, - }); - } catch (putErr) { - logger.error(`${defaultIndexPatternName} could not be created`); - } - } - }) - ); -}; diff --git a/x-pack/plugins/fleet/server/services/setup.ts b/x-pack/plugins/fleet/server/services/setup.ts index a3df7bc3dcdc44..f514f1ecb9ae6e 100644 --- a/x-pack/plugins/fleet/server/services/setup.ts +++ b/x-pack/plugins/fleet/server/services/setup.ts @@ -13,7 +13,6 @@ import { ensureInstalledDefaultPackages, ensurePackagesCompletedInstall, } from './epm/packages/install'; -import { ensureDefaultIndices } from './epm/kibana/index_pattern/install'; import { packageToPackagePolicy, PackagePolicy, @@ -58,7 +57,6 @@ async function createSetupSideEffects( ensureInstalledDefaultPackages(soClient, callCluster), outputService.ensureDefaultOutput(soClient), agentPolicyService.ensureDefaultAgentPolicy(soClient), - ensureDefaultIndices(callCluster), settingsService.getSettings(soClient).catch((e: any) => { if (e.isBoom && e.output.statusCode === 404) { const defaultSettings = createDefaultSettings(); diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index 5b230e5a179a54..0d634f60e282f2 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -7,8 +7,6 @@ export default function ({ loadTestFile }) { describe('Fleet Endpoints', function () { this.tags('ciGroup10'); - // Fleet setup - loadTestFile(require.resolve('./setup')); // Agent setup loadTestFile(require.resolve('./agents_setup')); // Agents diff --git a/x-pack/test/fleet_api_integration/apis/setup.ts b/x-pack/test/fleet_api_integration/apis/setup.ts deleted file mode 100644 index 4d1562e703770a..00000000000000 --- a/x-pack/test/fleet_api_integration/apis/setup.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../api_integration/ftr_provider_context'; - -export default function (providerContext: FtrProviderContext) { - const { getService } = providerContext; - const supertest = getService('supertest'); - const es = getService('es'); - describe('Fleet setup', async () => { - before(async () => { - await supertest.post(`/api/fleet/setup`).set('kbn-xsrf', 'xxx').send(); - }); - - it('should have installed placeholder indices', async function () { - const resLogsIndexPatternPlaceholder = await es.transport.request({ - method: 'GET', - path: `/logs-index_pattern_placeholder`, - }); - expect(resLogsIndexPatternPlaceholder.statusCode).equal(200); - const resMetricsIndexPatternPlaceholder = await es.transport.request({ - method: 'GET', - path: `/metrics-index_pattern_placeholder`, - }); - expect(resMetricsIndexPatternPlaceholder.statusCode).equal(200); - }); - }); -} diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts index 7a30d7acf5f89b..1ccab64a585691 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/resolver.ts @@ -13,19 +13,32 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const browser = getService('browser'); - describe('Endpoint Event Resolver', function () { + /** + * Navigating to the hosts page must be done after data is loaded into ES otherwise + * the hosts page will display the empty default page and if we load data after that + * we'd have to set the source filter on the page. + */ + const navigateToHostsAndSetDate = async () => { + await pageObjects.hosts.navigateToSecurityHostsPage(); + await pageObjects.common.dismissBanner(); + const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; + const toTime = 'now'; + await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }; + + describe.skip('Endpoint Event Resolver', function () { before(async () => { - await pageObjects.hosts.navigateToSecurityHostsPage(); - await pageObjects.common.dismissBanner(); - const fromTime = 'Jan 1, 2018 @ 00:00:00.000'; - const toTime = 'now'; - await pageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await browser.setWindowSize(1800, 1200); }); - describe.skip('Endpoint Resolver Tree', function () { + after(async () => { + await pageObjects.hosts.deleteDataStreams(); + }); + + describe('Endpoint Resolver Tree', function () { before(async () => { await esArchiver.load('empty_kibana'); await esArchiver.load('endpoint/resolver_tree/functions', { useCreate: true }); + await navigateToHostsAndSetDate(); await pageObjects.hosts.executeQueryAndOpenResolver('event.dataset : endpoint.events.file'); }); after(async () => { @@ -213,6 +226,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { before(async () => { await esArchiver.load('empty_kibana'); await esArchiver.load('endpoint/resolver_tree/alert_events', { useCreate: true }); + await navigateToHostsAndSetDate(); }); after(async () => { await pageObjects.hosts.deleteDataStreams(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts index 220d932787fffd..3f27d1868461f4 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/events.ts @@ -277,7 +277,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filter: entityIDFilter, - indexPatterns: ['metrics-*'], + indexPatterns: ['doesnotexist-*'], timeRange: { from: tree.startTime, to: tree.endTime, diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts index 9a731f1d5aee0f..ab6cac7f357a06 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/resolver/tree.ts @@ -281,7 +281,7 @@ export default function ({ getService }: FtrProviderContext) { from: tree.startTime.toISOString(), to: tree.endTime.toISOString(), }, - indexPatterns: ['metrics-*'], + indexPatterns: ['doesnotexist-*'], }) .expect(200); expect(body).to.be.empty(); From cb704a29a64bb0f7c1694a3fe52656e33a6b4a29 Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Sun, 20 Dec 2020 10:21:07 +0100 Subject: [PATCH 31/40] Bump Node.js from version 14.15.2 to 14.15.3 (#86593) --- .ci/Dockerfile | 2 +- .node-version | 2 +- .nvmrc | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/Dockerfile b/.ci/Dockerfile index cf827fc0ed08fc..8a972c65f84125 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,7 +1,7 @@ # NOTE: This Dockerfile is ONLY used to run certain tasks in CI. It is not used to run Kibana or as a distributable. # If you're looking for the Kibana Docker image distributable, please see: src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts -ARG NODE_VERSION=14.15.2 +ARG NODE_VERSION=14.15.3 FROM node:${NODE_VERSION} AS base diff --git a/.node-version b/.node-version index 420568d75691b9..19c4c189d3640c 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.nvmrc b/.nvmrc index 420568d75691b9..19c4c189d3640c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/package.json b/package.json index 252c8130c38d63..6e8809063ca575 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "**/typescript": "4.1.2" }, "engines": { - "node": "14.15.2", + "node": "14.15.3", "yarn": "^1.21.1" }, "dependencies": { From 396018fd4dd4845eb5a0a62883339f55ebe41f5d Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko Date: Sun, 20 Dec 2020 08:52:54 -0800 Subject: [PATCH 32/40] Removed a possibility to define two different names for Alert types on API and UI level. (#86236) * Removed a possibility to define two different names for Alert types on API and UI level * fixed typechecks * fixed typechecks * fixed due to comments * fixed typechecks * fixed jest tests * fixed typechecks --- .../public/alert_types/always_firing.tsx | 1 - .../public/alert_types/astros.tsx | 1 - .../components/alerting/register_apm_alerts.ts | 12 ------------ .../infra/public/alerting/inventory/index.ts | 3 --- .../log_threshold/log_threshold_alert_type.ts | 3 --- .../public/alerting/metric_threshold/index.ts | 3 --- ...er_inventory_metric_threshold_alert_type.ts | 5 ++++- .../register_log_threshold_alert_type.ts | 4 +++- .../register_metric_threshold_alert_type.ts | 5 ++++- .../public/alerts/alert_form.test.tsx | 1 - .../alerts/ccr_read_exceptions_alert/index.tsx | 1 - .../alerts/cpu_usage_alert/cpu_usage_alert.tsx | 1 - .../public/alerts/disk_usage_alert/index.tsx | 1 - .../alerts/legacy_alert/legacy_alert.tsx | 1 - .../public/alerts/memory_usage_alert/index.tsx | 1 - .../missing_monitoring_data_alert.tsx | 1 - .../thread_pool_rejections_alert/index.tsx | 1 - .../alert_types/geo_containment/index.ts | 3 --- .../public/alert_types/geo_threshold/index.ts | 3 --- .../public/alert_types/threshold/index.ts | 3 --- .../alert_types/geo_containment/alert_type.ts | 2 +- .../geo_containment/tests/alert_type.test.ts | 2 +- .../alert_types/geo_threshold/alert_type.ts | 2 +- .../geo_threshold/tests/alert_type.test.ts | 2 +- .../plugins/stack_alerts/server/plugin.test.ts | 2 +- .../translations/translations/ja-JP.json | 10 ---------- .../translations/translations/zh-CN.json | 10 ---------- .../application/lib/alert_type_compare.test.ts | 7 ------- .../components/alert_details.test.tsx | 1 - .../sections/alert_form/alert_add.test.tsx | 1 - .../sections/alert_form/alert_edit.test.tsx | 1 - .../sections/alert_form/alert_form.test.tsx | 5 ----- .../sections/alert_form/alert_form.tsx | 18 +++++------------- .../components/alerts_list.test.tsx | 1 - .../public/application/type_registry.test.ts | 3 +-- .../triggers_actions_ui/public/types.ts | 1 - .../__tests__/monitor_status.test.ts | 5 ----- .../lib/alert_types/duration_anomaly.tsx | 3 +-- .../public/lib/alert_types/monitor_status.tsx | 7 ------- .../uptime/public/lib/alert_types/tls.tsx | 3 +-- .../fixtures/plugins/alerts/public/plugin.ts | 2 -- 41 files changed, 24 insertions(+), 118 deletions(-) diff --git a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx index 134cda6f541887..cee7ee62e3210e 100644 --- a/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/always_firing.tsx @@ -32,7 +32,6 @@ import { export function getAlertType(): AlertTypeModel { return { id: 'example.always-firing', - name: 'Always Fires', description: 'Alert when called', iconClass: 'bolt', documentationUrl: null, diff --git a/x-pack/examples/alerting_example/public/alert_types/astros.tsx b/x-pack/examples/alerting_example/public/alert_types/astros.tsx index 54f989b93e22f4..cb65c9f52ca3f5 100644 --- a/x-pack/examples/alerting_example/public/alert_types/astros.tsx +++ b/x-pack/examples/alerting_example/public/alert_types/astros.tsx @@ -44,7 +44,6 @@ function isValueInEnum(enumeratin: Record, value: any): boolean { export function getAlertType(): AlertTypeModel { return { id: 'example.people-in-space', - name: 'People Are In Space Right Now', description: 'Alert when people are in space right now', iconClass: 'globe', documentationUrl: null, diff --git a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts index 6dc2cb3163b1f9..78d771aec13e04 100644 --- a/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts +++ b/x-pack/plugins/apm/public/components/alerting/register_apm_alerts.ts @@ -14,9 +14,6 @@ export function registerApmAlerts( ) { alertTypeRegistry.register({ id: AlertType.ErrorCount, - name: i18n.translate('xpack.apm.alertTypes.errorCount', { - defaultMessage: 'Error count threshold', - }), description: i18n.translate('xpack.apm.alertTypes.errorCount.description', { defaultMessage: 'Alert when the number of errors in a service exceeds a defined threshold.', @@ -45,9 +42,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionDuration, - name: i18n.translate('xpack.apm.alertTypes.transactionDuration', { - defaultMessage: 'Transaction duration threshold', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionDuration.description', { @@ -82,9 +76,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionErrorRate, - name: i18n.translate('xpack.apm.alertTypes.transactionErrorRate', { - defaultMessage: 'Transaction error rate threshold', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionErrorRate.description', { @@ -119,9 +110,6 @@ export function registerApmAlerts( alertTypeRegistry.register({ id: AlertType.TransactionDurationAnomaly, - name: i18n.translate('xpack.apm.alertTypes.transactionDurationAnomaly', { - defaultMessage: 'Transaction duration anomaly', - }), description: i18n.translate( 'xpack.apm.alertTypes.transactionDurationAnomaly.description', { diff --git a/x-pack/plugins/infra/public/alerting/inventory/index.ts b/x-pack/plugins/infra/public/alerting/inventory/index.ts index d7afd73c0e3a71..13ce43f77c8b03 100644 --- a/x-pack/plugins/infra/public/alerting/inventory/index.ts +++ b/x-pack/plugins/infra/public/alerting/inventory/index.ts @@ -14,9 +14,6 @@ import { validateMetricThreshold } from './components/validation'; export function createInventoryMetricAlertType(): AlertTypeModel { return { id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - name: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertName', { - defaultMessage: 'Inventory', - }), description: i18n.translate('xpack.infra.metrics.inventory.alertFlyout.alertDescription', { defaultMessage: 'Alert when the inventory exceeds a defined threshold.', }), diff --git a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts index 60c22c42c00b6f..7154a77496b818 100644 --- a/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/public/alerting/log_threshold/log_threshold_alert_type.ts @@ -12,9 +12,6 @@ import { validateExpression } from './validation'; export function getAlertType(): AlertTypeModel { return { id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, - name: i18n.translate('xpack.infra.logs.alertFlyout.alertName', { - defaultMessage: 'Log threshold', - }), description: i18n.translate('xpack.infra.logs.alertFlyout.alertDescription', { defaultMessage: 'Alert when the log aggregation exceeds the threshold.', }), diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts index 05c69e5ccb78bd..cccd5fbc439d7a 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/index.ts @@ -14,9 +14,6 @@ import { METRIC_THRESHOLD_ALERT_TYPE_ID } from '../../../server/lib/alerting/met export function createMetricThresholdAlertType(): AlertTypeModel { return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, - name: i18n.translate('xpack.infra.metrics.alertFlyout.alertName', { - defaultMessage: 'Metric threshold', - }), description: i18n.translate('xpack.infra.metrics.alertFlyout.alertDescription', { defaultMessage: 'Alert when the metrics aggregation exceeds the threshold.', }), diff --git a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts index 6ec6210ecb3448..2d1df6e8cb4627 100644 --- a/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/inventory_metric_threshold/register_inventory_metric_threshold_alert_type.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; import { AlertType } from '../../../../../alerts/server'; import { createInventoryMetricThresholdExecutor, @@ -41,7 +42,9 @@ const condition = schema.object({ export const registerMetricInventoryThresholdAlertType = (libs: InfraBackendLibs): AlertType => ({ id: METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, - name: 'Inventory', + name: i18n.translate('xpack.infra.metrics.inventory.alertName', { + defaultMessage: 'Inventory', + }), validate: { params: schema.object( { diff --git a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts index 64bfad92a84582..4703371f5e0def 100644 --- a/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/log_threshold/register_log_threshold_alert_type.ts @@ -81,7 +81,9 @@ export async function registerLogThresholdAlertType( alertingPlugin.registerType({ id: LOG_DOCUMENT_COUNT_ALERT_TYPE_ID, - name: 'Log threshold', + name: i18n.translate('xpack.infra.logs.alertName', { + defaultMessage: 'Log threshold', + }), validate: { params: { validate: (params) => decodeOrThrow(AlertParamsRT)(params), diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts index 1a10765eaf734e..f04a1015bcbcde 100644 --- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts +++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/register_metric_threshold_alert_type.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; import { AlertType } from '../../../../../alerts/server'; import { METRIC_EXPLORER_AGGREGATIONS } from '../../../../common/http_api/metrics_explorer'; import { createMetricThresholdExecutor, FIRED_ACTIONS } from './metric_threshold_executor'; @@ -42,7 +43,9 @@ export function registerMetricThresholdAlertType(libs: InfraBackendLibs): AlertT return { id: METRIC_THRESHOLD_ALERT_TYPE_ID, - name: 'Metric threshold', + name: i18n.translate('xpack.infra.metrics.alertName', { + defaultMessage: 'Metric threshold', + }), validate: { params: schema.object( { diff --git a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx index 369f03ab8cb115..20d5097d8a556f 100644 --- a/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx +++ b/x-pack/plugins/monitoring/public/alerts/alert_form.test.tsx @@ -71,7 +71,6 @@ describe('alert_form', () => { const alertType = { id: 'alert-type', iconClass: 'test', - name: 'test-alert', description: 'Testing', documentationUrl: 'https://...', validate: validationMethod, diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index 2dafadf2726082..4d22d422ecda6f 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -33,7 +33,6 @@ const validate = (inputValues: ValidateOptions): ValidationResult => { export function createCCRReadExceptionsAlertType(): AlertTypeModel { return { id: ALERT_CCR_READ_EXCEPTIONS, - name: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].label, description: ALERT_DETAILS[ALERT_CCR_READ_EXCEPTIONS].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx index 5054c47245f0fc..1fe40fc8777f4b 100644 --- a/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/cpu_usage_alert/cpu_usage_alert.tsx @@ -13,7 +13,6 @@ import { Expression, Props } from '../components/duration/expression'; export function createCpuUsageAlertType(): AlertTypeModel { return { id: ALERT_CPU_USAGE, - name: ALERT_DETAILS[ALERT_CPU_USAGE].label, description: ALERT_DETAILS[ALERT_CPU_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index 00b70658e4289a..5579b8e1275a39 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -15,7 +15,6 @@ import { ALERT_DISK_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createDiskUsageAlertType(): AlertTypeModel { return { id: ALERT_DISK_USAGE, - name: ALERT_DETAILS[ALERT_DISK_USAGE].label, description: ALERT_DETAILS[ALERT_DISK_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx index c8d0a7a5d49f2a..d50e9c3a5c2828 100644 --- a/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/legacy_alert/legacy_alert.tsx @@ -15,7 +15,6 @@ export function createLegacyAlertTypes(): AlertTypeModel[] { return LEGACY_ALERTS.map((legacyAlert) => { return { id: legacyAlert, - name: LEGACY_ALERT_DETAILS[legacyAlert].label, description: LEGACY_ALERT_DETAILS[legacyAlert].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index 062c32c7587942..0400810a8c3790 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -15,7 +15,6 @@ import { ALERT_MEMORY_USAGE, ALERT_DETAILS } from '../../../common/constants'; export function createMemoryUsageAlertType(): AlertTypeModel { return { id: ALERT_MEMORY_USAGE, - name: ALERT_DETAILS[ALERT_MEMORY_USAGE].label, description: ALERT_DETAILS[ALERT_MEMORY_USAGE].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx index ec97a45a8a8005..fdb89033c4e2ca 100644 --- a/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx +++ b/x-pack/plugins/monitoring/public/alerts/missing_monitoring_data_alert/missing_monitoring_data_alert.tsx @@ -13,7 +13,6 @@ import { Expression } from './expression'; export function createMissingMonitoringDataAlertType(): AlertTypeModel { return { id: ALERT_MISSING_MONITORING_DATA, - name: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].label, description: ALERT_DETAILS[ALERT_MISSING_MONITORING_DATA].description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx index bd0e7f89bf535e..403a1e531258e2 100644 --- a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx @@ -28,7 +28,6 @@ export function createThreadPoolRejectionsAlertType( ): AlertTypeModel { return { id: alertId, - name: threadPoolAlertDetails.label, description: threadPoolAlertDetails.description, iconClass: 'bell', documentationUrl(docLinks) { diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts index 59effdbf8f512b..ba490b91fae103 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.geo-containment', - name: i18n.translate('xpack.stackAlerts.geoContainment.name.trackingContainment', { - defaultMessage: 'Tracking containment', - }), description: i18n.translate('xpack.stackAlerts.geoContainment.descriptionText', { defaultMessage: 'Alert when an entity is contained within a geo boundary.', }), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts index cc8d78b53137ea..8ba632633a3af0 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_threshold/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.geo-threshold', - name: i18n.translate('xpack.stackAlerts.geoThreshold.name.trackingThreshold', { - defaultMessage: 'Tracking threshold', - }), description: i18n.translate('xpack.stackAlerts.geoThreshold.descriptionText', { defaultMessage: 'Alert when an entity enters or leaves a geo boundary.', }), diff --git a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts index f09d1630cd6751..184277bae3da86 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/threshold/index.ts @@ -12,9 +12,6 @@ import { AlertTypeModel } from '../../../../triggers_actions_ui/public'; export function getAlertType(): AlertTypeModel { return { id: '.index-threshold', - name: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.nameText', { - defaultMessage: 'Index threshold', - }), description: i18n.translate('xpack.stackAlerts.threshold.ui.alertType.descriptionText', { defaultMessage: 'Alert when an aggregated query meets the threshold.', }), diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index 164ce993eebac0..51d7361bfe762b 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -114,7 +114,7 @@ export interface GeoContainmentParams { export function getAlertType(logger: Logger): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoContainment.alertTypeTitle', { - defaultMessage: 'Geo tracking containment', + defaultMessage: 'Tracking containment', }); const actionGroupName = i18n.translate( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts index f3dc3855eb91bf..0592c944de570e 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts @@ -14,7 +14,7 @@ describe('alertType', () => { it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.geo-containment'); - expect(alertType.name).toBe('Geo tracking containment'); + expect(alertType.name).toBe('Tracking containment'); expect(alertType.actionGroups).toEqual([ { id: 'Tracked entity contained', name: 'Tracking containment met' }, ]); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts index 93a6c0d29cf3cf..bf5e2fe2289db8 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/alert_type.ts @@ -174,7 +174,7 @@ export interface GeoThresholdParams { export function getAlertType(logger: Logger): AlertType { const alertTypeName = i18n.translate('xpack.stackAlerts.geoThreshold.alertTypeTitle', { - defaultMessage: 'Geo tracking threshold', + defaultMessage: 'Tracking threshold', }); const actionGroupName = i18n.translate( diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts index 49b56b5571b441..0cfce2d47f1898 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_threshold/tests/alert_type.test.ts @@ -14,7 +14,7 @@ describe('alertType', () => { it('alert type creation structure is the expected value', async () => { expect(alertType.id).toBe('.geo-threshold'); - expect(alertType.name).toBe('Geo tracking threshold'); + expect(alertType.name).toBe('Tracking threshold'); expect(alertType.actionGroups).toEqual([ { id: 'tracking threshold met', name: 'Tracking threshold met' }, ]); diff --git a/x-pack/plugins/stack_alerts/server/plugin.test.ts b/x-pack/plugins/stack_alerts/server/plugin.test.ts index 3037504ed3e39b..0f747e9c24eec1 100644 --- a/x-pack/plugins/stack_alerts/server/plugin.test.ts +++ b/x-pack/plugins/stack_alerts/server/plugin.test.ts @@ -63,7 +63,7 @@ describe('AlertingBuiltins Plugin', () => { }, ], "id": ".geo-threshold", - "name": "Geo tracking threshold", + "name": "Tracking threshold", } `); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e38058a312f3c0..e5b82a5d3fcbc7 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4742,13 +4742,9 @@ "xpack.apm.alerts.anomalySeverity.minor": "マイナー", "xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "スコア{value}以上", "xpack.apm.alerts.anomalySeverity.warningLabel": "警告", - "xpack.apm.alertTypes.errorCount": "エラー数しきい値", "xpack.apm.alertTypes.errorCount.defaultActionMessage": "次の条件のため、\\{\\{alertName\\}\\}アラートが実行されています。\n\n- サービス名:\\{\\{context.serviceName\\}\\}\n- 環境:\\{\\{context.environment\\}\\}\n- しきい値\\{\\{context.threshold\\}\\}エラー\n- トリガーされた値:過去\\{\\{context.interval\\}\\}に\\{\\{context.triggerValue\\}\\}件のエラー", - "xpack.apm.alertTypes.transactionDuration": "トランザクション期間のしきい値", "xpack.apm.alertTypes.transactionDuration.defaultActionMessage": "次の条件のため、\\{\\{alertName\\}\\}アラートが実行されています。\n\n- サービス名:\\{\\{context.serviceName\\}\\}\n- タイプ:\\{\\{context.transactionType\\}\\}\n- 環境:\\{\\{context.environment\\}\\}\n- しきい値:\\{\\{context.threshold\\}\\}ミリ秒\n- トリガーされた値:過去\\{\\{context.interval\\}\\}に\\{\\{context.triggerValue\\}\\}", - "xpack.apm.alertTypes.transactionDurationAnomaly": "トランザクション期間異常", "xpack.apm.alertTypes.transactionDurationAnomaly.defaultActionMessage": "次の条件のため、\\{\\{alertName\\}\\}アラートが実行されています。\n\n- サービス名:\\{\\{context.serviceName\\}\\}\n- タイプ:\\{\\{context.transactionType\\}\\}\n- 環境:\\{\\{context.environment\\}\\}\n- 重要度しきい値:\\{\\{context.threshold\\}\\}%\n- 重要度値:\\{\\{context.thresholdValue\\}\\}\n", - "xpack.apm.alertTypes.transactionErrorRate": "トランザクションエラー率しきい値", "xpack.apm.alertTypes.transactionErrorRate.defaultActionMessage": "次の条件のため、\\{\\{alertName\\}\\}アラートが実行されています。\n\n- サービス名:\\{\\{context.serviceName\\}\\}\n- タイプ:\\{\\{context.transactionType\\}\\}\n- 環境:\\{\\{context.environment\\}\\}\n- しきい値:\\{\\{context.threshold\\}\\}%\n- トリガーされた値:過去\\{\\{context.interval\\}\\}にエラーの\\{\\{context.triggerValue\\}\\}%", "xpack.apm.anomaly_detection.error.invalid_license": "異常検知を使用するには、Elastic Platinumライセンスのサブスクリプションが必要です。このライセンスがあれば、機械学習を活用して、サービスを監視できます。", "xpack.apm.anomaly_detection.error.missing_read_privileges": "異常検知ジョブを表示するには、機械学習およびAPMの「読み取り」権限が必要です", @@ -9280,7 +9276,6 @@ "xpack.infra.logFlyout.setFilterTooltip": "フィルターでイベントを表示", "xpack.infra.logFlyout.valueColumnLabel": "値", "xpack.infra.logs.alertFlyout.addCondition": "条件を追加", - "xpack.infra.logs.alertFlyout.alertName": "ログしきい値", "xpack.infra.logs.alertFlyout.criterionComparatorValueTitle": "比較:値", "xpack.infra.logs.alertFlyout.criterionFieldTitle": "フィールド", "xpack.infra.logs.alertFlyout.error.criterionComparatorRequired": "コンパレーターが必要です。", @@ -9611,7 +9606,6 @@ "xpack.infra.metrics.alertFlyout.aggregationText.p99": "99パーセンタイル", "xpack.infra.metrics.alertFlyout.aggregationText.rate": "レート", "xpack.infra.metrics.alertFlyout.aggregationText.sum": "合計", - "xpack.infra.metrics.alertFlyout.alertName": "メトリックしきい値", "xpack.infra.metrics.alertFlyout.alertOnNoData": "データがない場合に通知する", "xpack.infra.metrics.alertFlyout.alertPreviewError": "このアラート条件をプレビューするときにエラーが発生しました", "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "しばらくたってから再試行するか、詳細を確認してください。", @@ -9700,7 +9694,6 @@ "xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "閉じる", "xpack.infra.metrics.invalidNodeErrorDescription": "構成をよく確認してください", "xpack.infra.metrics.invalidNodeErrorTitle": "{nodeName} がメトリックデータを収集していないようです", - "xpack.infra.metrics.inventory.alertFlyout.alertName": "インベントリ", "xpack.infra.metrics.loadingNodeDataText": "データを読み込み中", "xpack.infra.metrics.missingTSVBModelError": "{nodeType}では{metricId}のTSVBモデルが存在しません", "xpack.infra.metrics.pluginTitle": "メトリック", @@ -19118,7 +19111,6 @@ "xpack.stackAlerts.geoThreshold.indexLabel": "インデックス", "xpack.stackAlerts.geoThreshold.indexPatternSelectLabel": "インデックスパターン", "xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder": "インデックスパターンを選択", - "xpack.stackAlerts.geoThreshold.name.trackingThreshold": "追跡しきい値", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisLinkTextDescription": "インデックスパターンを作成します", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisPrefixDescription": "次のことが必要です ", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisSuffixDescription": " 地理空間フィールドを含む", @@ -19493,7 +19485,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知間隔", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "アラートがアクティブな間にアクションを繰り返す頻度を定義します。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "アクションタイプを選択してください", - "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "デフォルトアクショングループの定義がないのでアクションを追加できません", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "コネクターを読み込めません", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionTypesMessage": "アクションタイプを読み込めません", @@ -20137,7 +20128,6 @@ "xpack.uptime.alerts.monitorStatus.timerangeValueField.ariaLabel": "アラートの範囲のための時間単位の数を入力してください", "xpack.uptime.alerts.monitorStatus.timerangeValueField.expression": "within", "xpack.uptime.alerts.monitorStatus.timerangeValueField.value": "最終{value}", - "xpack.uptime.alerts.monitorStatus.title.label": "稼働状況の監視ステータス", "xpack.uptime.alerts.settings.createConnector": "コネクターを作成", "xpack.uptime.alerts.timerangeUnitSelectable.daysOption.ariaLabel": "「日」の時間範囲選択項目", "xpack.uptime.alerts.timerangeUnitSelectable.hoursOption.ariaLabel": "「時間」の時間範囲選択項目", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 17bcd61898efe9..879418870b527f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4744,13 +4744,9 @@ "xpack.apm.alerts.anomalySeverity.minor": "轻微", "xpack.apm.alerts.anomalySeverity.scoreDetailsDescription": "{value} 及以上分数", "xpack.apm.alerts.anomalySeverity.warningLabel": "警告", - "xpack.apm.alertTypes.errorCount": "错误计数阈值", "xpack.apm.alertTypes.errorCount.defaultActionMessage": "由于以下条件 \\{\\{alertName\\}\\} 告警触发:\n\n- 服务名称:\\{\\{context.serviceName\\}\\}\n- 环境:\\{\\{context.environment\\}\\}\n- 阈值:\\{\\{context.threshold\\}\\} 个错误\n- 已触发的值:在过去 \\{\\{context.interval\\}\\}有 \\{\\{context.triggerValue\\}\\} 个错误", - "xpack.apm.alertTypes.transactionDuration": "事务持续时间阈值", "xpack.apm.alertTypes.transactionDuration.defaultActionMessage": "由于以下条件 \\{\\{alertName\\}\\} 告警触发:\n\n- 服务名称:\\{\\{context.serviceName\\}\\}\n- 类型:\\{\\{context.transactionType\\}\\}\n- 环境:\\{\\{context.environment\\}\\}\n- 阈值:\\{\\{context.threshold\\}\\}ms\n- 已触发的值:在过去 \\{\\{context.interval\\}\\}为 \\{\\{context.triggerValue\\}\\}", - "xpack.apm.alertTypes.transactionDurationAnomaly": "事务持续时间异常", "xpack.apm.alertTypes.transactionDurationAnomaly.defaultActionMessage": "由于以下条件 \\{\\{alertName\\}\\} 告警触发:\n\n- 服务名称:\\{\\{context.serviceName\\}\\}\n- 类型:\\{\\{context.transactionType\\}\\}\n- 环境:\\{\\{context.environment\\}\\}\n- 严重性阈值:\\{\\{context.threshold\\}\\}\n- 严重性值:\\{\\{context.thresholdValue\\}\\}\n", - "xpack.apm.alertTypes.transactionErrorRate": "事务错误率阈值", "xpack.apm.alertTypes.transactionErrorRate.defaultActionMessage": "由于以下条件 \\{\\{alertName\\}\\} 告警触发:\n\n- 服务名称:\\{\\{context.serviceName\\}\\}\n- 类型:\\{\\{context.transactionType\\}\\}\n- 环境:\\{\\{context.environment\\}\\}\n- 阈值:\\{\\{context.threshold\\}\\}%\n- 已触发的值:在过去 \\{\\{context.interval\\}\\}有 \\{\\{context.triggerValue\\}\\}% 的错误", "xpack.apm.anomaly_detection.error.invalid_license": "要使用异常检测,必须订阅 Elastic 白金级许可证。有了该许可证,您便可借助 Machine Learning 监测服务。", "xpack.apm.anomaly_detection.error.missing_read_privileges": "必须对 Machine Learning 和 APM 具有“读”权限,才能查看“异常检测”作业", @@ -9289,7 +9285,6 @@ "xpack.infra.logFlyout.setFilterTooltip": "使用筛选查看事件", "xpack.infra.logFlyout.valueColumnLabel": "值", "xpack.infra.logs.alertFlyout.addCondition": "添加条件", - "xpack.infra.logs.alertFlyout.alertName": "日志阈值", "xpack.infra.logs.alertFlyout.criterionComparatorValueTitle": "对比:值", "xpack.infra.logs.alertFlyout.criterionFieldTitle": "字段", "xpack.infra.logs.alertFlyout.error.criterionComparatorRequired": "比较运算符必填。", @@ -9621,7 +9616,6 @@ "xpack.infra.metrics.alertFlyout.aggregationText.p99": "第 99 个百分位", "xpack.infra.metrics.alertFlyout.aggregationText.rate": "比率", "xpack.infra.metrics.alertFlyout.aggregationText.sum": "求和", - "xpack.infra.metrics.alertFlyout.alertName": "指标阈值", "xpack.infra.metrics.alertFlyout.alertOnNoData": "没数据时提醒我", "xpack.infra.metrics.alertFlyout.alertPreviewError": "尝试预览此告警条件时发生错误", "xpack.infra.metrics.alertFlyout.alertPreviewErrorDesc": "请稍后重试或查看详情了解更多信息。", @@ -9713,7 +9707,6 @@ "xpack.infra.metrics.expressionItems.components.closablePopoverTitle.closeLabel": "关闭", "xpack.infra.metrics.invalidNodeErrorDescription": "反复检查您的配置", "xpack.infra.metrics.invalidNodeErrorTitle": "似乎 {nodeName} 未在收集任何指标数据", - "xpack.infra.metrics.inventory.alertFlyout.alertName": "库存", "xpack.infra.metrics.loadingNodeDataText": "正在加载数据", "xpack.infra.metrics.missingTSVBModelError": "{nodeType} 的 {metricId} TSVB 模型不存在", "xpack.infra.metrics.pluginTitle": "指标", @@ -19136,7 +19129,6 @@ "xpack.stackAlerts.geoThreshold.indexLabel": "索引", "xpack.stackAlerts.geoThreshold.indexPatternSelectLabel": "索引模式", "xpack.stackAlerts.geoThreshold.indexPatternSelectPlaceholder": "选择索引模式", - "xpack.stackAlerts.geoThreshold.name.trackingThreshold": "跟踪阈值", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisLinkTextDescription": "创建索引模式", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisPrefixDescription": "您将需要 ", "xpack.stackAlerts.geoThreshold.noIndexPattern.doThisSuffixDescription": " (包含地理空间字段)。", @@ -19512,7 +19504,6 @@ "xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel": "通知频率", "xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip": "定义告警处于活动状态时重复操作的频率。", "xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle": "选择操作类型", - "xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle": "{alertType}", "xpack.triggersActionsUI.sections.alertForm.unableToAddAction": "无法添加操作,因为未定义默认操作组", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage": "无法加载连接器", "xpack.triggersActionsUI.sections.alertForm.unableToLoadActionTypesMessage": "无法加载操作类型", @@ -20156,7 +20147,6 @@ "xpack.uptime.alerts.monitorStatus.timerangeValueField.ariaLabel": "输入告警范围的时间单位数目", "xpack.uptime.alerts.monitorStatus.timerangeValueField.expression": "之内", "xpack.uptime.alerts.monitorStatus.timerangeValueField.value": "上一 {value}", - "xpack.uptime.alerts.monitorStatus.title.label": "运行时间监测状态", "xpack.uptime.alerts.settings.createConnector": "创建连接器", "xpack.uptime.alerts.timerangeUnitSelectable.daysOption.ariaLabel": "“天”时间范围选择项", "xpack.uptime.alerts.timerangeUnitSelectable.hoursOption.ariaLabel": "“小时”时间范围选择项", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts index e364661361814f..0cd5118c5e316b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/alert_type_compare.test.ts @@ -30,7 +30,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -52,7 +51,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -69,7 +67,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'disabled-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -91,7 +88,6 @@ test('should sort groups by containing enabled alert types first and then by nam alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -130,7 +126,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -147,7 +142,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { @@ -164,7 +158,6 @@ test('should sort alert types by enabled first and then by name', async () => { alertTypeItem: { id: 'disabled-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx index e25e703de5f7ee..30ca2c620f1d7c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_details/components/alert_details.test.tsx @@ -657,7 +657,6 @@ describe('edit button', () => { const alertTypeR: AlertTypeModel = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx index 2790ea8aa6bfa4..6057d2669f04c9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_add.test.tsx @@ -94,7 +94,6 @@ describe('alert_add', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx index 25f830df58df53..e5a6a8977a8c8c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_edit.test.tsx @@ -52,7 +52,6 @@ describe('alert_edit', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx index d41ca915f34c11..ef8d17d8c4c282 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.test.tsx @@ -26,7 +26,6 @@ describe('alert_form', () => { const alertType = { id: 'my-alert-type', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: (): ValidationResult => { @@ -54,7 +53,6 @@ describe('alert_form', () => { const alertTypeNonEditable = { id: 'non-edit-alert-type', iconClass: 'test', - name: 'non edit alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { @@ -67,7 +65,6 @@ describe('alert_form', () => { const disabledByLicenseAlertType = { id: 'disabled-by-license', iconClass: 'test', - name: 'test-alert', description: 'Alert when testing', documentationUrl: 'https://localhost.local/docs', validate: (): ValidationResult => { @@ -306,7 +303,6 @@ describe('alert_form', () => { { id: 'same-consumer-producer-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { @@ -318,7 +314,6 @@ describe('alert_form', () => { { id: 'other-consumer-producer-alert-type', iconClass: 'test', - name: 'test-alert', description: 'test', documentationUrl: null, validate: (): ValidationResult => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx index cf3d0bf1544c25..a67fd218d55f34 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alert_form/alert_form.tsx @@ -289,10 +289,7 @@ export const AlertForm = ({ ) .filter((alertTypeItem) => searchValue - ? alertTypeItem.alertTypeModel.name - .toString() - .toLocaleLowerCase() - .includes(searchValue) || + ? alertTypeItem.alertType.name.toString().toLocaleLowerCase().includes(searchValue) || alertTypeItem.alertType!.producer.toLocaleLowerCase().includes(searchValue) || alertTypeItem.alertTypeModel.description.toLocaleLowerCase().includes(searchValue) : alertTypeItem @@ -378,10 +375,7 @@ export const AlertForm = ({ hasDisabledByLicenseAlertTypes = true; } (result[producer] = result[producer] || []).push({ - name: - typeof alertTypeValue.alertTypeModel.name === 'string' - ? alertTypeValue.alertTypeModel.name - : alertTypeValue.alertTypeModel.name.props.defaultMessage, + name: alertTypeValue.alertType.name, id: alertTypeValue.alertTypeModel.id, checkEnabledResult, alertTypeItem: alertTypeValue.alertTypeModel, @@ -475,11 +469,9 @@ export const AlertForm = ({
    - + {alert.alertTypeId && alertTypesIndex && alertTypesIndex.has(alert.alertTypeId) + ? alertTypesIndex.get(alert.alertTypeId)!.name + : ''}
    diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx index 7df5c6e1571068..875268bd931123 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -43,7 +43,6 @@ const alertTypeRegistry = alertTypeRegistryMock.create(); const alertType = { id: 'test_alert_type', - name: 'some alert type', description: 'test', iconClass: 'test', documentationUrl: null, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts index f875bcabdcde82..aa61fcde9e9c25 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/type_registry.test.ts @@ -11,10 +11,9 @@ export const ExpressionComponent: React.FunctionComponent = () => { return null; }; -const getTestAlertType = (id?: string, name?: string, iconClass?: string) => { +const getTestAlertType = (id?: string, iconClass?: string) => { return { id: id || 'test-alet-type', - name: name || 'Test alert type', description: 'Test description', iconClass: iconClass || 'icon', documentationUrl: null, diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index cd1ebe47a8c229..3fffe9fe230b4b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -187,7 +187,6 @@ export interface AlertTypeParamsExpressionProps< export interface AlertTypeModel { id: string; - name: string | JSX.Element; description: string; iconClass: string; documentationUrl: string | ((docLinks: DocLinksStart) => string) | null; diff --git a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts index 8da45276fa532f..7e297c1cb6d7bc 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts +++ b/x-pack/plugins/uptime/public/lib/alert_types/__tests__/monitor_status.test.ts @@ -207,11 +207,6 @@ describe('monitor status alert type', () => { "documentationUrl": [Function], "iconClass": "uptimeApp", "id": "xpack.uptime.alerts.monitorStatus", - "name": , "requiresAppContext": false, "validate": [Function], } diff --git a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx index e02cc11269e9cb..39a8a36a6d0a89 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/duration_anomaly.tsx @@ -10,7 +10,7 @@ import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { DurationAnomalyTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -const { name, defaultActionMessage, description } = DurationAnomalyTranslations; +const { defaultActionMessage, description } = DurationAnomalyTranslations; const DurationAnomalyAlert = React.lazy(() => import('./lazy_wrapper/duration_anomaly')); export const initDurationAnomalyAlertType: AlertTypeInitializer = ({ @@ -25,7 +25,6 @@ export const initDurationAnomalyAlertType: AlertTypeInitializer = ({ alertParamsExpression: (params: unknown) => ( ), - name, description, validate: () => ({ errors: {} }), defaultActionMessage, diff --git a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx index 43aaa26d86642a..6a00d2987f12b3 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/monitor_status.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { AlertTypeInitializer } from '.'; @@ -23,12 +22,6 @@ export const initMonitorStatusAlertType: AlertTypeInitializer = ({ plugins, }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.MONITOR_STATUS, - name: ( - - ), description, iconClass: 'uptimeApp', documentationUrl(docLinks) { diff --git a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx index 83c4792e26f597..43e5b75aa5f8b2 100644 --- a/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx +++ b/x-pack/plugins/uptime/public/lib/alert_types/tls.tsx @@ -10,7 +10,7 @@ import { CLIENT_ALERT_TYPES } from '../../../common/constants/alerts'; import { TlsTranslations } from './translations'; import { AlertTypeInitializer } from '.'; -const { name, defaultActionMessage, description } = TlsTranslations; +const { defaultActionMessage, description } = TlsTranslations; const TLSAlert = React.lazy(() => import('./lazy_wrapper/tls_alert')); export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): AlertTypeModel => ({ id: CLIENT_ALERT_TYPES.TLS, @@ -21,7 +21,6 @@ export const initTlsAlertType: AlertTypeInitializer = ({ core, plugins }): Alert alertParamsExpression: (params: any) => ( ), - name, description, validate: () => ({ errors: {} }), defaultActionMessage, diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts index af4aedda06ef75..50de66ac1c3baf 100644 --- a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/public/plugin.ts @@ -28,7 +28,6 @@ export class AlertingFixturePlugin implements Plugin Date: Sun, 20 Dec 2020 14:10:52 -0500 Subject: [PATCH 33/40] [Security Solution][Detections][Threshold Rules] Threshold Rule Bug Fixes (#84918) * Move threshold dupe detection logic to its own function * Minor fixup * Refactor and remove property injection for threshold signals * Only show aggregatable fields for threshold rule grouping * Add threshold rule kql filter to timeline * Remove outdated getThresholdSignalQueryFields tests * Filter aggregatable fields on client * Revert "Only show aggregatable fields for threshold rule grouping" This reverts commit 539fa49cc9ff14da131ff990247ff262f2d79702. * Fix bug with incorrect calculation of threshold signal dupes when no threshold field present * Revert "Add threshold rule kql filter to timeline" This reverts commit 64823744a39eda21cf3ee5222f4a023885e692a0. * Add test skeleton * Finish tests * Address comment --- .../rules/step_define_rule/index.tsx | 26 +- .../bulk_create_threshold_signals.test.ts | 364 +++--------------- .../signals/bulk_create_threshold_signals.ts | 94 +---- .../signals/signal_rule_alert_type.ts | 51 +-- ....ts => threshold_find_previous_signals.ts} | 0 .../signals/threshold_get_bucket_filters.ts | 96 +++++ .../detection_engine/signals/utils.test.ts | 15 + .../lib/detection_engine/signals/utils.ts | 18 + 8 files changed, 228 insertions(+), 436 deletions(-) rename x-pack/plugins/security_solution/server/lib/detection_engine/signals/{find_previous_threshold_signals.ts => threshold_find_previous_signals.ts} (100%) create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx index f06d4bdef74cb7..1fe1b809d4f30a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx @@ -52,7 +52,7 @@ import { } from '../../../../../common/detection_engine/utils'; import { EqlQueryBar } from '../eql_query_bar'; import { ThreatMatchInput } from '../threatmatch_input'; -import { useFetchIndex } from '../../../../common/containers/source'; +import { BrowserField, BrowserFields, useFetchIndex } from '../../../../common/containers/source'; import { PreviewQuery, Threshold } from '../query_preview'; const CommonUseField = getUseField({ component: Field }); @@ -168,6 +168,26 @@ const StepDefineRuleComponent: FC = ({ const queryBarQuery = formQuery != null ? formQuery.query.query : '' || initialState.queryBar.query.query; const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index); + const aggregatableFields = Object.entries(browserFields).reduce( + (groupAcc, [groupName, groupValue]) => { + return { + ...groupAcc, + [groupName]: { + fields: Object.entries(groupValue.fields ?? {}).reduce>( + (fieldAcc, [fieldName, fieldValue]) => { + if (fieldValue.aggregatable === true) { + return { ...fieldAcc, [fieldName]: fieldValue }; + } + return fieldAcc; + }, + {} + ), + } as Partial, + }; + }, + {} + ); + const [ threatIndexPatternsLoading, { browserFields: threatBrowserFields, indexPatterns: threatIndexPatterns }, @@ -262,12 +282,12 @@ const StepDefineRuleComponent: FC = ({ const ThresholdInputChildren = useCallback( ({ thresholdField, thresholdValue }) => ( ), - [browserFields] + [aggregatableFields] ); const ThreatMatchInputChildren = useCallback( diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts index 6a75d0655cf59d..022c07defc9c19 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.test.ts @@ -4,329 +4,77 @@ * you may not use this file except in compliance with the Elastic License. */ -import { sampleDocNoSortIdNoVersion } from './__mocks__/es_results'; -import { getThresholdSignalQueryFields } from './bulk_create_threshold_signals'; +import { loggingSystemMock } from '../../../../../../../src/core/server/mocks'; +import { sampleDocNoSortId, sampleDocSearchResultsNoSortId } from './__mocks__/es_results'; +import { transformThresholdResultsToEcs } from './bulk_create_threshold_signals'; +import { calculateThresholdSignalUuid } from './utils'; -describe('getThresholdSignalQueryFields', () => { - it('should return proper fields for match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - traefik: { - access: { - entryPointName: 'web-secure', - }, - }, - url: { - domain: 'kibana.siem.estc.dev', - }, - }, - }; - const mockFilters = { - bool: { - must: [], - filter: [ - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - match_phrase: { - 'traefik.access.entryPointName': 'web-secure', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - ], - }, - }, - { - match_phrase: { - 'url.domain': 'kibana.siem.estc.dev', - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, mockFilters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - 'traefik.access.entryPointName': 'web-secure', - 'url.domain': 'kibana.siem.estc.dev', - }); - }); - - it('should return proper fields object for nested match filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - url: { - domain: 'kibana.siem.estc.dev', - }, - }, - }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - match: { - 'event.dataset': 'traefik.*', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - }); - }); - - it('should return proper object for simple match filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - }, - }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - should: [ - { - match: { - 'event.module': 'traefik', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, - }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.dataset': 'traefik.access', - 'event.module': 'traefik', - }); - }); - - it('should return proper object for simple match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - dataset: 'traefik.access', - module: 'traefik', - }, - }, +describe('transformThresholdResultsToEcs', () => { + it('should return transformed threshold results', () => { + const threshold = { + field: 'source.ip', + value: 1, }; - const filters = { - bool: { - must: [], - filter: [ - { - bool: { - should: [ - { - match_phrase: { - 'event.module': 'traefik', + const startedAt = new Date('2020-12-17T16:27:00Z'); + const transformedResults = transformThresholdResultsToEcs( + { + ...sampleDocSearchResultsNoSortId('abcd'), + aggregations: { + threshold: { + buckets: [ + { + key: '127.0.0.1', + doc_count: 1, + top_threshold_hits: { + hits: { + hits: [sampleDocNoSortId('abcd')], }, }, - ], - minimum_should_match: 1, - }, - }, - { - match_phrase: { - 'event.dataset': 'traefik.access', - }, + }, + ], }, - ], - should: [], - must_not: [], - }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'event.module': 'traefik', - 'event.dataset': 'traefik.access', - }); - }); - - it('should return proper object for exists filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - event: { - module: 'traefik', }, }, - }; - const filters = { - bool: { - should: [ - { - bool: { - should: [ - { - exists: { - field: 'process.name', - }, - }, - ], - minimum_should_match: 1, - }, - }, - { - bool: { - should: [ - { - exists: { - field: 'event.type', - }, - }, - ], - minimum_should_match: 1, - }, - }, - ], - minimum_should_match: 1, + 'test', + startedAt, + undefined, + loggingSystemMock.createLogger(), + threshold, + '1234', + undefined + ); + const _id = calculateThresholdSignalUuid('1234', startedAt, 'source.ip', '127.0.0.1'); + expect(transformedResults).toEqual({ + took: 10, + timed_out: false, + _shards: { + total: 10, + successful: 10, + failed: 0, + skipped: 0, }, - }; - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({}); - }); - - it('should NOT add invalid characters from CIDR such as the "/" proper object for simple match_phrase filters', () => { - const mockHit = { - ...sampleDocNoSortIdNoVersion(), - _source: { - '@timestamp': '2020-11-03T02:31:47.431Z', - destination: { - ip: '192.168.0.16', - }, - event: { - module: 'traefik', + results: { + hits: { + total: 1, }, }, - }; - const filters = { - bool: { - must: [], - filter: [ + hits: { + total: 100, + max_score: 100, + hits: [ { - bool: { - should: [ - { - match: { - 'destination.ip': '192.168.0.0/16', - }, - }, - ], - minimum_should_match: 1, + _id, + _index: 'test', + _source: { + '@timestamp': '2020-04-20T21:27:45+0000', + threshold_result: { + count: 1, + value: '127.0.0.1', + }, }, }, ], - should: [], - must_not: [], }, - }; - - expect(getThresholdSignalQueryFields(mockHit, filters)).toEqual({ - 'destination.ip': '192.168.0.16', }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts index a98aae4ec8107b..3cad33b2787495 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/bulk_create_threshold_signals.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import uuidv5 from 'uuid/v5'; -import { reduce, get, isEmpty } from 'lodash/fp'; +import { get, isEmpty } from 'lodash/fp'; import set from 'set-value'; import { @@ -17,12 +16,10 @@ import { AlertServices } from '../../../../../alerts/server'; import { RuleAlertAction } from '../../../../common/detection_engine/types'; import { RuleTypeParams, RefreshTypes } from '../types'; import { singleBulkCreate, SingleBulkCreateResponse } from './single_bulk_create'; -import { SignalSearchResponse, SignalSourceHit, ThresholdAggregationBucket } from './types'; +import { SignalSearchResponse, ThresholdAggregationBucket } from './types'; +import { calculateThresholdSignalUuid } from './utils'; import { BuildRuleMessage } from './rule_messages'; -// used to generate constant Threshold Signals ID when run with the same params -const NAMESPACE_ID = '0684ec03-7201-4ee0-8ee0-3a3f6b2479b2'; - interface BulkCreateThresholdSignalsParams { actions: RuleAlertAction[]; someResult: SignalSearchResponse; @@ -48,81 +45,6 @@ interface BulkCreateThresholdSignalsParams { buildRuleMessage: BuildRuleMessage; } -interface FilterObject { - bool?: { - filter?: FilterObject | FilterObject[]; - should?: Array>>; - }; -} - -const injectFirstMatch = ( - hit: SignalSourceHit, - match: object | Record -): Record | undefined => { - if (match != null) { - for (const key of Object.keys(match)) { - return { [key]: get(key, hit._source) } as Record; - } - } -}; - -const getNestedQueryFilters = ( - hit: SignalSourceHit, - filtersObj: FilterObject -): Record => { - if (Array.isArray(filtersObj.bool?.filter)) { - return reduce( - (acc, filterItem) => { - const nestedFilter = getNestedQueryFilters(hit, filterItem); - - if (nestedFilter) { - return { ...acc, ...nestedFilter }; - } - - return acc; - }, - {}, - filtersObj.bool?.filter - ); - } else { - return ( - (filtersObj.bool?.should && - filtersObj.bool?.should[0] && - (injectFirstMatch(hit, filtersObj.bool.should[0].match) || - injectFirstMatch(hit, filtersObj.bool.should[0].match_phrase))) ?? - {} - ); - } -}; - -export const getThresholdSignalQueryFields = (hit: SignalSourceHit, filter: unknown) => { - const filters = get('bool.filter', filter); - - return reduce( - (acc, item) => { - if (item.match_phrase) { - return { ...acc, ...injectFirstMatch(hit, item.match_phrase) }; - } - - if (item.bool?.should && (item.bool.should[0].match || item.bool.should[0].match_phrase)) { - return { - ...acc, - ...(injectFirstMatch(hit, item.bool.should[0].match) || - injectFirstMatch(hit, item.bool.should[0].match_phrase)), - }; - } - - if (item.bool?.filter) { - return { ...acc, ...getNestedQueryFilters(hit, item) }; - } - - return acc; - }, - {}, - filters - ); -}; - const getTransformedHits = ( results: SignalSearchResponse, inputIndex: string, @@ -153,13 +75,12 @@ const getTransformedHits = ( count: totalResults, value: ruleId, }, - ...getThresholdSignalQueryFields(hit, filter), }; return [ { _index: inputIndex, - _id: uuidv5(`${ruleId}${startedAt}${threshold.field}`, NAMESPACE_ID), + _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field), _source: source, }, ]; @@ -183,14 +104,11 @@ const getTransformedHits = ( count: docCount, value: get(threshold.field, hit._source), }, - ...getThresholdSignalQueryFields(hit, filter), }; - set(source, threshold.field, key); - return { _index: inputIndex, - _id: uuidv5(`${ruleId}${startedAt}${threshold.field}${key}`, NAMESPACE_ID), + _id: calculateThresholdSignalUuid(ruleId, startedAt, threshold.field, key), _source: source, }; } @@ -226,6 +144,8 @@ export const transformThresholdResultsToEcs = ( }, }; + delete thresholdResults.aggregations; // no longer needed + set(thresholdResults, 'results.hits.total', transformedHits.length); return thresholdResults; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 7fd99a17598ae8..3928228357d4cb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -8,7 +8,6 @@ import { Logger, KibanaRequest } from 'src/core/server'; -import { Filter } from 'src/plugins/data/common'; import { SIGNALS_ID, DEFAULT_SEARCH_AFTER_PAGE_SIZE, @@ -29,7 +28,6 @@ import { SignalRuleAlertTypeDefinition, RuleAlertAttributes, EqlSignalSearchResponse, - ThresholdQueryBucket, WrappedSignalHit, } from './types'; import { @@ -48,9 +46,9 @@ import { signalParamsSchema } from './signal_params_schema'; import { siemRuleActionGroups } from './siem_rule_action_groups'; import { findMlSignals } from './find_ml_signals'; import { findThresholdSignals } from './find_threshold_signals'; -import { findPreviousThresholdSignals } from './find_previous_threshold_signals'; import { bulkCreateMlSignals } from './bulk_create_ml_signals'; import { bulkCreateThresholdSignals } from './bulk_create_threshold_signals'; +import { getThresholdBucketFilters } from './threshold_get_bucket_filters'; import { scheduleNotificationActions, NotificationRuleTypeParams, @@ -307,21 +305,11 @@ export const signalRulesAlertType = ({ ]); } else if (isThresholdRule(type) && threshold) { const inputIndex = await getInputIndex(services, version, index); - const esFilter = await getFilter({ - type, - filters, - language, - query, - savedId, - services, - index: inputIndex, - lists: exceptionItems ?? [], - }); const { - searchResult: previousSignals, + filters: bucketFilters, searchErrors: previousSearchErrors, - } = await findPreviousThresholdSignals({ + } = await getThresholdBucketFilters({ indexPattern: [outputIndex], from, to, @@ -333,29 +321,15 @@ export const signalRulesAlertType = ({ buildRuleMessage, }); - previousSignals.aggregations.threshold.buckets.forEach((bucket: ThresholdQueryBucket) => { - esFilter.bool.filter.push(({ - bool: { - must_not: { - bool: { - must: [ - { - term: { - [threshold.field || 'signal.rule.rule_id']: bucket.key, - }, - }, - { - range: { - [timestampOverride ?? '@timestamp']: { - lte: bucket.lastSignalTimestamp.value_as_string, - }, - }, - }, - ], - }, - }, - }, - } as unknown) as Filter); + const esFilter = await getFilter({ + type, + filters: filters ? filters.concat(bucketFilters) : bucketFilters, + language, + query, + savedId, + services, + index: inputIndex, + lists: exceptionItems ?? [], }); const { searchResult: thresholdResults, searchErrors } = await findThresholdSignals({ @@ -400,6 +374,7 @@ export const signalRulesAlertType = ({ tags, buildRuleMessage, }); + result = mergeReturns([ result, createSearchAfterReturnTypeFromResponse({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_previous_threshold_signals.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts similarity index 100% rename from x-pack/plugins/security_solution/server/lib/detection_engine/signals/find_previous_threshold_signals.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_find_previous_signals.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts new file mode 100644 index 00000000000000..bf060da1e76b8f --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/threshold_get_bucket_filters.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash'; + +import { Filter } from 'src/plugins/data/common'; +import { ESFilter } from '../../../../../../typings/elasticsearch'; + +import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; +import { AlertServices } from '../../../../../alerts/server'; +import { Logger } from '../../../../../../../src/core/server'; +import { ThresholdQueryBucket } from './types'; +import { BuildRuleMessage } from './rule_messages'; +import { findPreviousThresholdSignals } from './threshold_find_previous_signals'; + +interface GetThresholdBucketFiltersParams { + from: string; + to: string; + indexPattern: string[]; + services: AlertServices; + logger: Logger; + ruleId: string; + bucketByField: string; + timestampOverride: TimestampOverrideOrUndefined; + buildRuleMessage: BuildRuleMessage; +} + +export const getThresholdBucketFilters = async ({ + from, + to, + indexPattern, + services, + logger, + ruleId, + bucketByField, + timestampOverride, + buildRuleMessage, +}: GetThresholdBucketFiltersParams): Promise<{ + filters: Filter[]; + searchErrors: string[]; +}> => { + const { searchResult, searchErrors } = await findPreviousThresholdSignals({ + indexPattern, + from, + to, + services, + logger, + ruleId, + bucketByField, + timestampOverride, + buildRuleMessage, + }); + + const filters = searchResult.aggregations.threshold.buckets.reduce( + (acc: ESFilter[], bucket: ThresholdQueryBucket): ESFilter[] => { + const filter = { + bool: { + filter: [ + { + range: { + [timestampOverride ?? '@timestamp']: { + lte: bucket.lastSignalTimestamp.value_as_string, + }, + }, + }, + ], + }, + } as ESFilter; + + if (!isEmpty(bucketByField)) { + (filter.bool.filter as ESFilter[]).push({ + term: { + [bucketByField]: bucket.key, + }, + }); + } + + return [...acc, filter]; + }, + [] as ESFilter[] + ); + + return { + filters: [ + ({ + bool: { + must_not: filters, + }, + } as unknown) as Filter, + ], + searchErrors, + }; +}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index dd936776f691aa..073e30bbc6e26b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -36,6 +36,7 @@ import { mergeReturns, createTotalHitsFromSearchResult, lastValidDate, + calculateThresholdSignalUuid, } from './utils'; import { BulkResponseErrorAggregation, SearchAfterAndBulkCreateReturnType } from './types'; import { @@ -1303,4 +1304,18 @@ describe('utils', () => { expect(result).toEqual(4); }); }); + + describe('calculateThresholdSignalUuid', () => { + it('should generate a uuid without key', () => { + const startedAt = new Date('2020-12-17T16:27:00Z'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'agent.name'); + expect(signalUuid).toEqual('c0cbe4b7-48de-5734-ae81-d8de3e79839d'); + }); + + it('should generate a uuid with key', () => { + const startedAt = new Date('2019-11-18T13:32:00Z'); + const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'host.ip', '1.2.3.4'); + expect(signalUuid).toEqual('f568509e-b570-5d3c-a7ed-7c73fd29ddaf'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 2114f21d9cead6..18f6e8d127b1b3 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -5,6 +5,7 @@ */ import { createHash } from 'crypto'; import moment from 'moment'; +import uuidv5 from 'uuid/v5'; import dateMath from '@elastic/datemath'; import { TimestampOverrideOrUndefined } from '../../../../common/detection_engine/schemas/common/schemas'; @@ -661,3 +662,20 @@ export const createTotalHitsFromSearchResult = ({ : searchResult.hits.total.value; return totalHits; }; + +export const calculateThresholdSignalUuid = ( + ruleId: string, + startedAt: Date, + thresholdField: string, + key?: string +): string => { + // used to generate constant Threshold Signals ID when run with the same params + const NAMESPACE_ID = '0684ec03-7201-4ee0-8ee0-3a3f6b2479b2'; + + let baseString = `${ruleId}${startedAt}${thresholdField}`; + if (key != null) { + baseString = `${baseString}${key}`; + } + + return uuidv5(baseString, NAMESPACE_ID); +}; From f67f1ecc6288b743b52ce8fd69055d5de3461824 Mon Sep 17 00:00:00 2001 From: Daniil Date: Mon, 21 Dec 2020 13:42:25 +0300 Subject: [PATCH 34/40] Fix request with disabled aggregation (#85696) * Fix request with disabled aggregation * Update unit tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../search/expressions/esaggs/request_handler.test.ts | 9 ++++++++- .../common/search/expressions/esaggs/request_handler.ts | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index 78d169e8529c5a..9d0c63a8dc7aa4 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -150,7 +150,8 @@ describe('esaggs expression function - public', () => { }); }); - test('calls agg.postFlightRequest if it exiests', async () => { + test('calls agg.postFlightRequest if it exiests and agg is enabled', async () => { + mockParams.aggs.aggs[0].enabled = true; await handleRequest(mockParams); expect(mockParams.aggs.aggs[0].type.postFlightRequest).toHaveBeenCalledTimes(1); @@ -160,6 +161,12 @@ describe('esaggs expression function - public', () => { expect(async () => await handleRequest(mockParams)).not.toThrowError(); }); + test('should skip agg.postFlightRequest call if the agg is disabled', async () => { + mockParams.aggs.aggs[0].enabled = false; + await handleRequest(mockParams); + expect(mockParams.aggs.aggs[0].type.postFlightRequest).toHaveBeenCalledTimes(0); + }); + test('tabifies response data', async () => { await handleRequest(mockParams); expect(tabifyAggResponse).toHaveBeenCalledWith( diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index e4385526ee6e8f..b773aad67c3f89 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -170,7 +170,7 @@ export const handleRequest = async ({ // response data incorrectly in the inspector. let response = (searchSource as any).rawResponse; for (const agg of aggs.aggs) { - if (typeof agg.type.postFlightRequest === 'function') { + if (agg.enabled && typeof agg.type.postFlightRequest === 'function') { response = await agg.type.postFlightRequest( response, aggs, From 74d1e39ea490e8fea45fe8c67806c2dbc2adbcc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cau=C3=AA=20Marcondes?= <55978943+cauemarcondes@users.noreply.github.com> Date: Mon, 21 Dec 2020 12:36:20 +0100 Subject: [PATCH 35/40] [APM] Bug: Service overview: Link to Transactions list from the Overview page is broken (#86447) * fixing link and refactoring some stuff * addressing pr comments --- .../Waterfall/FlyoutTopLevelProperties.tsx | 8 +++-- .../SpanFlyout/StickySpanProperties.tsx | 8 +++-- .../service_details/service_detail_tabs.tsx | 4 +-- .../service_inventory/ServiceList/index.tsx | 4 +-- .../index.tsx | 33 ++++++++++--------- .../service_overview_errors_table/index.tsx | 9 +++-- .../index.tsx | 15 ++++----- .../service_overview/table_link_flex_item.tsx | 14 -------- ....tsx => service_transactions_overview.tsx} | 7 ++-- .../Links/apm/transaction_overview_ink.tsx | 31 +++++++++++++++++ 10 files changed, 78 insertions(+), 55 deletions(-) delete mode 100644 x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx rename x-pack/plugins/apm/public/components/shared/Links/apm/{TransactionOverviewLink.tsx => service_transactions_overview.tsx} (86%) create mode 100644 x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx index 0187ecd927e658..b0ef28fbb7b0d2 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/FlyoutTopLevelProperties.tsx @@ -13,7 +13,7 @@ import { import { Transaction } from '../../../../../../../typings/es_schemas/ui/transaction'; import { TransactionDetailLink } from '../../../../../shared/Links/apm/TransactionDetailLink'; import { StickyProperties } from '../../../../../shared/StickyProperties'; -import { TransactionOverviewLink } from '../../../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../../../shared/Links/apm/service_transactions_overview'; interface Props { transaction?: Transaction; @@ -31,9 +31,11 @@ export function FlyoutTopLevelProperties({ transaction }: Props) { }), fieldName: SERVICE_NAME, val: ( - + {transaction.service.name} - + ), width: '25%', }, diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx index c068fee3cd6c33..ca5b4938ff42ee 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/StickySpanProperties.tsx @@ -15,7 +15,7 @@ import { import { NOT_AVAILABLE_LABEL } from '../../../../../../../../common/i18n'; import { Span } from '../../../../../../../../typings/es_schemas/ui/span'; import { StickyProperties } from '../../../../../../shared/StickyProperties'; -import { TransactionOverviewLink } from '../../../../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../../../../shared/Links/apm/service_transactions_overview'; import { TransactionDetailLink } from '../../../../../../shared/Links/apm/TransactionDetailLink'; interface Props { @@ -33,9 +33,11 @@ export function StickySpanProperties({ span, transaction }: Props) { }), fieldName: SERVICE_NAME, val: ( - + {transaction.service.name} - + ), width: '25%', }, diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx index ae0dd85b6a8b55..961320baa6a4ec 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx @@ -15,7 +15,7 @@ import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; import { useServiceNodeOverviewHref } from '../../shared/Links/apm/ServiceNodeOverviewLink'; import { useServiceOverviewHref } from '../../shared/Links/apm/service_overview_link'; -import { useTransactionOverviewHref } from '../../shared/Links/apm/TransactionOverviewLink'; +import { useServiceOrTransactionsOverviewHref } from '../../shared/Links/apm/service_transactions_overview'; import { MainTabs } from '../../shared/main_tabs'; import { ErrorGroupOverview } from '../ErrorGroupOverview'; import { ServiceMap } from '../ServiceMap'; @@ -60,7 +60,7 @@ export function ServiceDetailTabs({ serviceName, tab }: Props) { const transactionsTab = { key: 'transactions', - href: useTransactionOverviewHref(serviceName), + href: useServiceOrTransactionsOverviewHref(serviceName), text: i18n.translate('xpack.apm.serviceDetails.transactionsTabLabel', { defaultMessage: 'Transactions', }), diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx index a4c93f95dc53d7..157d3ecc738a1a 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/index.tsx @@ -21,7 +21,7 @@ import { NOT_AVAILABLE_LABEL } from '../../../../../common/i18n'; import { fontSizes, px, truncate, unit } from '../../../../style/variables'; import { ManagedTable, ITableColumn } from '../../../shared/ManagedTable'; import { EnvironmentBadge } from '../../../shared/EnvironmentBadge'; -import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; +import { ServiceOrTransactionsOverviewLink } from '../../../shared/Links/apm/service_transactions_overview'; import { AgentIcon } from '../../../shared/AgentIcon'; import { HealthBadge } from './HealthBadge'; import { ServiceListMetric } from './ServiceListMetric'; @@ -39,7 +39,7 @@ function formatString(value?: string | null) { return value || NOT_AVAILABLE_LABEL; } -const AppLink = styled(TransactionOverviewLink)` +const AppLink = styled(ServiceOrTransactionsOverviewLink)` font-size: ${fontSizes.large}; ${truncate('100%')}; `; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index ed4f3277a4a045..ae297b840ebc88 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -4,13 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexItem } from '@elastic/eui'; -import { EuiInMemoryTable } from '@elastic/eui'; -import { EuiTitle } from '@elastic/eui'; -import { EuiBasicTableColumn } from '@elastic/eui'; -import { EuiFlexGroup } from '@elastic/eui'; +import { + EuiBasicTableColumn, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiTitle, +} from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; +import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; import { asDuration, asPercent, @@ -18,20 +21,18 @@ import { } from '../../../../../common/utils/formatters'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ServiceDependencyItem } from '../../../../../server/lib/services/get_service_dependencies'; -import { ENVIRONMENT_ALL } from '../../../../../common/environment_filter_values'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; -import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; -import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; -import { TableLinkFlexItem } from '../table_link_flex_item'; +import { px, unit } from '../../../../style/variables'; import { AgentIcon } from '../../../shared/AgentIcon'; -import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { SparkPlot } from '../../../shared/charts/spark_plot'; -import { px, unit } from '../../../../style/variables'; import { ImpactBar } from '../../../shared/ImpactBar'; +import { ServiceMapLink } from '../../../shared/Links/apm/ServiceMapLink'; import { ServiceOverviewLink } from '../../../shared/Links/apm/service_overview_link'; import { SpanIcon } from '../../../shared/span_icon'; +import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; +import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; interface Props { @@ -192,8 +193,8 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { return ( - - + +

    {i18n.translate( @@ -205,7 +206,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) {

    - + {i18n.translate( 'xpack.apm.serviceOverview.dependenciesTableLinkText', @@ -214,7 +215,7 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { } )} - +
    diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index da74a6fc0004d5..d14ef648c22d3d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -25,7 +25,6 @@ import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; -import { TableLinkFlexItem } from '../table_link_flex_item'; interface Props { serviceName: string; @@ -195,8 +194,8 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { return ( - - + +

    {i18n.translate('xpack.apm.serviceOverview.errorsTableTitle', { @@ -205,13 +204,13 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) {

    - + {i18n.translate('xpack.apm.serviceOverview.errorsTableLinkText', { defaultMessage: 'View errors', })} - +
    diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index 6345d546c716fd..4b262f1f51319a 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -20,6 +20,7 @@ import { asPercent, asTransactionRate, } from '../../../../../common/utils/formatters'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { useLatencyAggregationType } from '../../../../hooks/use_latency_Aggregation_type'; @@ -28,13 +29,11 @@ import { callApmApi, } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; -import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; -import { TransactionOverviewLink } from '../../../shared/Links/apm/TransactionOverviewLink'; -import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; -import { TableLinkFlexItem } from '../table_link_flex_item'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; -import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; +import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; +import { TransactionOverviewLink } from '../../../shared/Links/apm/transaction_overview_ink'; +import { TableFetchWrapper } from '../../../shared/table_fetch_wrapper'; import { TruncateWithTooltip } from '../../../shared/truncate_with_tooltip'; import { ServiceOverviewTableContainer } from '../service_overview_table_container'; @@ -270,7 +269,7 @@ export function ServiceOverviewTransactionsTable(props: Props) { - +

    {i18n.translate( @@ -282,7 +281,7 @@ export function ServiceOverviewTransactionsTable(props: Props) {

    - + {i18n.translate( 'xpack.apm.serviceOverview.transactionsTableLinkText', @@ -291,7 +290,7 @@ export function ServiceOverviewTransactionsTable(props: Props) { } )} - +
    diff --git a/x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx b/x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx deleted file mode 100644 index 35df003af380d4..00000000000000 --- a/x-pack/plugins/apm/public/components/app/service_overview/table_link_flex_item.tsx +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiFlexItem } from '@elastic/eui'; -import styled from 'styled-components'; - -export const TableLinkFlexItem = styled(EuiFlexItem)` - & > a { - text-align: right; - } -`; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx similarity index 86% rename from x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx rename to x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx index 1d99b82a67326a..24a78e5d64749a 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_transactions_overview.tsx @@ -18,7 +18,7 @@ const persistedFilters: Array = [ 'latencyAggregationType', ]; -export function useTransactionOverviewHref(serviceName: string) { +export function useServiceOrTransactionsOverviewHref(serviceName: string) { return useAPMHref(`/services/${serviceName}/transactions`, persistedFilters); } @@ -26,7 +26,10 @@ interface Props extends APMLinkExtendProps { serviceName: string; } -export function TransactionOverviewLink({ serviceName, ...rest }: Props) { +export function ServiceOrTransactionsOverviewLink({ + serviceName, + ...rest +}: Props) { const { urlParams } = useUrlParams(); return ( diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx new file mode 100644 index 00000000000000..d2978b3c02d532 --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/transaction_overview_ink.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { pickKeys } from '../../../../../common/utils/pick_keys'; +import { APMLink, APMLinkExtendProps } from './APMLink'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { APMQueryParams } from '../url_helpers'; + +interface Props extends APMLinkExtendProps { + serviceName: string; +} + +const persistedFilters: Array = [ + 'latencyAggregationType', +]; + +export function TransactionOverviewLink({ serviceName, ...rest }: Props) { + const { urlParams } = useUrlParams(); + + return ( + + ); +} From c05533ebbda8ea57cacb3d732c439178f72d6e4b Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Mon, 21 Dec 2020 11:42:51 +0000 Subject: [PATCH 36/40] Fix ECS HTTP scheme and improve docs (#86612) --- docs/user/security/audit-logging.asciidoc | 189 +++++++++++++++++- .../server/audit/audit_events.test.ts | 4 +- .../security/server/audit/audit_events.ts | 18 +- 3 files changed, 191 insertions(+), 20 deletions(-) diff --git a/docs/user/security/audit-logging.asciidoc b/docs/user/security/audit-logging.asciidoc index 7facde28e956f9..acb0f94cf878cb 100644 --- a/docs/user/security/audit-logging.asciidoc +++ b/docs/user/security/audit-logging.asciidoc @@ -47,9 +47,11 @@ For information on how to configure `xpack.security.audit.appender`, refer to Refer to the table of events that can be logged for auditing purposes. -Each event is broken down into `category`, `type`, `action` and `outcome` fields +Each event is broken down into <>, <>, <> and <> fields to make it easy to filter, query and aggregate the resulting logs. +Refer to <> for a table of fields that get logged with audit event. + [NOTE] ============================================================================ To ensure that a record of every operation is persisted even in case of an @@ -230,3 +232,188 @@ Refer to the corresponding {es} logs for potential write errors. | `http_request` | `unknown` | User is making an HTTP request. |====== + + +[[xpack-security-ecs-audit-schema]] +==== ECS audit schema + +Audit logs are written in JSON using https://www.elastic.co/guide/en/ecs/1.6/index.html[Elastic Common Schema (ECS)] specification. + +[cols="2*<"] +|====== + +2+a| ===== Base Fields + +| *Field* +| *Description* + +| `@timestamp` +| Time when the event was generated. + +Example: `2016-05-23T08:05:34.853Z` + +| `message` +| Human readable description of the event. + +2+a| ===== Event Fields + +| *Field* +| *Description* + +| [[field-event-action]] `event.action` +| The action captured by the event. + +Refer to <> for a table of possible actions. + +| [[field-event-category]] `event.category` +| High level category associated with the event. + +This field is closely related to `event.type`, which is used as a subcategory. + +Possible values: +`database`, +`web`, +`authentication` + +| [[field-event-type]] `event.type` +| Subcategory associated with the event. + +This field can be used along with the `event.category` field to enable filtering events down to a level appropriate for single visualization. + +Possible values: +`creation`, +`access`, +`change`, +`deletion` + +| [[field-event-outcome]] `event.outcome` +| Denotes whether the event represents a success or failure. + +Possible values: +`success`, +`failure`, +`unknown` + +2+a| ===== User Fields + +| *Field* +| *Description* + +| `user.name` +| Login name of the user. + +Example: `jdoe` + +| `user.roles[]` +| Set of user roles at the time of the event. + +Example: `[kibana_admin, reporting_user]` + +2+a| ===== Kibana Fields + +| *Field* +| *Description* + +| `kibana.space_id` +| ID of the space associated with the event. + +Example: `default` + +| `kibana.session_id` +| ID of the user session associated with the event. + +Each login attempt results in a unique session id. + +| `kibana.saved_object.type` +| Type of saved object associated with the event. + +Example: `dashboard` + +| `kibana.saved_object.id` +| ID of the saved object associated with the event. + +| `kibana.authentication_provider` +| Name of the authentication provider associated with the event. + +Example: `my-saml-provider` + +| `kibana.authentication_type` +| Type of the authentication provider associated with the event. + +Example: `saml` + +| `kibana.authentication_realm` +| Name of the Elasticsearch realm that has authenticated the user. + +Example: `native` + +| `kibana.lookup_realm` +| Name of the Elasticsearch realm where the user details were retrieved from. + +Example: `native` + +| `kibana.add_to_spaces[]` +| Set of space IDs that a saved object is being shared to as part of the event. + +Example: `[default, marketing]` + +| `kibana.delete_from_spaces[]` +| Set of space IDs that a saved object is being removed from as part of the event. + +Example: `[marketing]` + +2+a| ===== Error Fields + +| *Field* +| *Description* + +| `error.code` +| Error code describing the error. + +| `error.message` +| Error message. + +2+a| ===== HTTP and URL Fields + +| *Field* +| *Description* + +| `http.request.method` +| HTTP request method. + +Example: `get`, `post`, `put`, `delete` + +| `url.domain` +| Domain of the url. + +Example: `www.elastic.co` + +| `url.path` +| Path of the request. + +Example: `/search` + +| `url.port` +| Port of the request. + +Example: `443` + +| `url.query` +| The query field describes the query string of the request. + +Example: `q=elasticsearch` + +| `url.scheme` +| Scheme of the request. + +Example: `https` + +2+a| ===== Tracing Fields + +| *Field* +| *Description* + +| `trace.id` +| Unique identifier allowing events of the same transaction from {kib} and {es} to be be correlated. + +|====== diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index 9bda628df66dcb..f7e41bce674ee5 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -284,7 +284,7 @@ describe('#httpRequestEvent', () => { "path": "/path", "port": undefined, "query": undefined, - "scheme": "http:", + "scheme": "http", }, } `); @@ -321,7 +321,7 @@ describe('#httpRequestEvent', () => { "path": "/original/path", "port": undefined, "query": "query=param", - "scheme": "http:", + "scheme": "http", }, } `); diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 7f0dd39162adff..b6538af31bd608 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -28,14 +28,9 @@ export interface AuditEvent { category?: EventCategory; type?: EventType; outcome?: EventOutcome; - module?: string; - dataset?: string; }; user?: { name: string; - email?: string; - full_name?: string; - hash?: string; roles?: readonly string[]; }; kibana?: { @@ -87,17 +82,10 @@ export interface AuditEvent { http?: { request?: { method?: string; - body?: { - content: string; - }; - }; - response?: { - status_code?: number; }; }; url?: { domain?: string; - full?: string; path?: string; port?: number; query?: string; @@ -108,14 +96,10 @@ export interface AuditEvent { export enum EventCategory { DATABASE = 'database', WEB = 'web', - IAM = 'iam', AUTHENTICATION = 'authentication', - PROCESS = 'process', } export enum EventType { - USER = 'user', - GROUP = 'group', CREATION = 'creation', ACCESS = 'access', CHANGE = 'change', @@ -152,7 +136,7 @@ export function httpRequestEvent({ request }: HttpRequestParams): AuditEvent { path: url.pathname, port: url.port ? parseInt(url.port, 10) : undefined, query: url.search ? url.search.slice(1) : undefined, - scheme: url.protocol, + scheme: url.protocol ? url.protocol.substr(0, url.protocol.length - 1) : undefined, }, }; } From 6d72042ca4acf2e8bc4b92431ee6f93d7c14cf56 Mon Sep 17 00:00:00 2001 From: James Gowdy Date: Mon, 21 Dec 2020 12:17:53 +0000 Subject: [PATCH 37/40] [ML] Fixing endpoint schema for can_delete_job endpoint (#86436) * [ML] Fixing endpoint schema for can_delete_job endpoint * changing get to post in docs * renaming canUntag * fixing typo Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../plugins/ml/common/types/saved_objects.ts | 4 ++-- .../delete_job_check_modal.tsx | 20 +++++++++---------- x-pack/plugins/ml/server/routes/apidoc.json | 2 +- .../plugins/ml/server/routes/saved_objects.ts | 20 ++++++++++++++----- .../ml/server/routes/schemas/saved_objects.ts | 5 +++++ .../plugins/ml/server/saved_objects/checks.ts | 16 +++++++-------- .../apis/ml/saved_objects/can_delete_job.ts | 8 ++++---- 7 files changed, 45 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/ml/common/types/saved_objects.ts b/x-pack/plugins/ml/common/types/saved_objects.ts index 8783113502623f..259f9be1a1b26e 100644 --- a/x-pack/plugins/ml/common/types/saved_objects.ts +++ b/x-pack/plugins/ml/common/types/saved_objects.ts @@ -21,7 +21,7 @@ export interface SyncSavedObjectResponse { export interface CanDeleteJobResponse { [jobId: string]: { canDelete: boolean; - canUntag: boolean; + canRemoveFromSpace: boolean; }; } @@ -41,5 +41,5 @@ export interface DeleteJobCheckResponse { export interface DeleteJobPermission { canDelete: boolean; - canUntag: boolean; + canRemoveFromSpace: boolean; } diff --git a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx index 151946ab31fd98..4393d2b2fcf4dc 100644 --- a/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx +++ b/x-pack/plugins/ml/public/application/components/delete_job_check_modal/delete_job_check_modal.tsx @@ -36,31 +36,31 @@ interface ModalContentReturnType { interface JobCheckRespSummary { canDelete: boolean; - canUntag: boolean; + canRemoveFromSpace: boolean; canTakeAnyAction: boolean; } function getRespSummary(resp: CanDeleteJobResponse): JobCheckRespSummary { const jobsChecked = Object.keys(resp); // Default to first job's permissions - const { canDelete, canUntag } = resp[jobsChecked[0]]; + const { canDelete, canRemoveFromSpace } = resp[jobsChecked[0]]; let canTakeAnyAction = true; if (jobsChecked.length > 1) { // Check all jobs and make sure they have the same permissions - otherwise no action can be taken canTakeAnyAction = jobsChecked.every( - (id) => resp[id].canDelete === canDelete && resp[id].canUntag === canUntag + (id) => resp[id].canDelete === canDelete && resp[id].canRemoveFromSpace === canRemoveFromSpace ); } - return { canDelete, canUntag, canTakeAnyAction }; + return { canDelete, canRemoveFromSpace, canTakeAnyAction }; } function getModalContent( jobIds: string[], respSummary: JobCheckRespSummary ): ModalContentReturnType { - const { canDelete, canUntag, canTakeAnyAction } = respSummary; + const { canDelete, canRemoveFromSpace, canTakeAnyAction } = respSummary; if (canTakeAnyAction === false) { return { @@ -116,7 +116,7 @@ function getModalContent( ), }; - } else if (canUntag) { + } else if (canRemoveFromSpace) { return { buttonText: ( = ({ // Do the spaces check and set the content for the modal and buttons depending on results canDeleteJob(jobType, jobIds).then((resp) => { const respSummary = getRespSummary(resp); - const { canDelete, canUntag, canTakeAnyAction } = respSummary; - if (canTakeAnyAction && canDelete && !canUntag) { + const { canDelete, canRemoveFromSpace, canTakeAnyAction } = respSummary; + if (canTakeAnyAction && canDelete && !canRemoveFromSpace) { // Go straight to delete flow if that's the only action available canDeleteCallback(); return; @@ -260,7 +260,7 @@ export const DeleteJobCheckModal: FC = ({ {!hasUntagged && jobCheckRespSummary?.canTakeAnyAction && - jobCheckRespSummary?.canUntag && + jobCheckRespSummary?.canRemoveFromSpace && jobCheckRespSummary?.canDelete && ( = ({ size="s" onClick={ jobCheckRespSummary?.canTakeAnyAction && - jobCheckRespSummary?.canUntag && + jobCheckRespSummary?.canRemoveFromSpace && !jobCheckRespSummary?.canDelete ? onUntagClick : onClick diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index bf002907b1a434..015ec6e4ec9c0a 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -151,7 +151,7 @@ "RemoveJobsFromSpaces", "RemoveJobsFromCurrentSpace", "JobsSpaces", - "DeleteJobCheck", + "CanDeleteJob", "TrainedModels", "GetTrainedModel", diff --git a/x-pack/plugins/ml/server/routes/saved_objects.ts b/x-pack/plugins/ml/server/routes/saved_objects.ts index 29f9b218ea177a..a5144ab2a7af7e 100644 --- a/x-pack/plugins/ml/server/routes/saved_objects.ts +++ b/x-pack/plugins/ml/server/routes/saved_objects.ts @@ -12,8 +12,8 @@ import { jobsAndCurrentSpace, syncJobObjects, jobTypeSchema, + canDeleteJobSchema, } from './schemas/saved_objects'; -import { jobIdsSchema } from './schemas/job_service_schema'; import { spacesUtilsProvider } from '../lib/spaces_utils'; import { JobType } from '../../common/types/saved_objects'; @@ -284,13 +284,23 @@ export function savedObjectsRoutes( /** * @apiGroup JobSavedObjects * - * @api {get} /api/ml/saved_objects/delete_job_check Check whether user can delete a job - * @apiName DeleteJobCheck + * @api {post} /api/ml/saved_objects/can_delete_job Check whether user can delete a job + * @apiName CanDeleteJob * @apiDescription Check the user's ability to delete jobs. Returns whether they are able * to fully delete the job and whether they are able to remove it from * the current space. + * Note, this is only for enabling UI controls. A user calling endpoints + * directly will still be able to delete or remove the job from a space. * - * @apiSchema (body) jobIdsSchema (params) jobTypeSchema + * @apiSchema (params) jobTypeSchema + * @apiSchema (body) jobIdsSchema + * @apiSuccessExample {json} Error-Response: + * { + * "my_job": { + * "canDelete": false, + * "canRemoveFromSpace": true + * } + * } * */ router.post( @@ -298,7 +308,7 @@ export function savedObjectsRoutes( path: '/api/ml/saved_objects/can_delete_job/{jobType}', validate: { params: jobTypeSchema, - body: jobIdsSchema, + body: canDeleteJobSchema, }, options: { tags: ['access:ml:canGetJobs', 'access:ml:canGetDataFrameAnalytics'], diff --git a/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts b/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts index 147398694f191c..a64d38a9fda70c 100644 --- a/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts +++ b/x-pack/plugins/ml/server/routes/schemas/saved_objects.ts @@ -22,3 +22,8 @@ export const syncJobObjects = schema.object({ simulate: schema.maybe(schema.bool export const jobTypeSchema = schema.object({ jobType: schema.string(), }); + +export const canDeleteJobSchema = schema.object({ + /** List of job IDs. */ + jobIds: schema.arrayOf(schema.maybe(schema.string())), +}); diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index f682999cd59666..9258b143c9747b 100644 --- a/x-pack/plugins/ml/server/saved_objects/checks.ts +++ b/x-pack/plugins/ml/server/saved_objects/checks.ts @@ -180,7 +180,7 @@ export function checksFactory( return jobIds.reduce((results, jobId) => { results[jobId] = { canDelete: false, - canUntag: false, + canRemoveFromSpace: false, }; return results; }, {} as DeleteJobCheckResponse); @@ -191,7 +191,7 @@ export function checksFactory( return jobIds.reduce((results, jobId) => { results[jobId] = { canDelete: true, - canUntag: false, + canRemoveFromSpace: false, }; return results; }, {} as DeleteJobCheckResponse); @@ -208,7 +208,7 @@ export function checksFactory( // job saved object not found results[jobId] = { canDelete: false, - canUntag: false, + canRemoveFromSpace: false, }; return results; } @@ -220,7 +220,7 @@ export function checksFactory( if (canCreateGlobalJobs && isGlobalJob) { results[jobId] = { canDelete: true, - canUntag: false, + canRemoveFromSpace: false, }; return results; } @@ -229,20 +229,20 @@ export function checksFactory( if (isGlobalJob) { results[jobId] = { canDelete: false, - canUntag: false, + canRemoveFromSpace: false, }; return results; } // jobs with are in individual spaces can only be untagged // from current space if the job is in more than 1 space - const canUntag = namespaces.length > 1; + const canRemoveFromSpace = namespaces.length > 1; // job is in individual spaces, user cannot see all of them - untag only, no delete if (namespaces.includes('?')) { results[jobId] = { canDelete: false, - canUntag, + canRemoveFromSpace, }; return results; } @@ -250,7 +250,7 @@ export function checksFactory( // job is individual spaces, user can see all of them - delete and option to untag results[jobId] = { canDelete: true, - canUntag, + canRemoveFromSpace, }; return results; }, {} as DeleteJobCheckResponse); diff --git a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts index d95c90d417203a..e80d5a333bbdf0 100644 --- a/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts +++ b/x-pack/test/api_integration/apis/ml/saved_objects/can_delete_job.ts @@ -75,7 +75,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdSpace12]: { canDelete: false, canUntag: true } }); + expect(body).to.eql({ [adJobIdSpace12]: { canDelete: false, canRemoveFromSpace: true } }); }); it('job in individual spaces, all spaces user can delete and untag', async () => { @@ -87,7 +87,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdSpace12]: { canDelete: true, canUntag: true } }); + expect(body).to.eql({ [adJobIdSpace12]: { canDelete: true, canRemoveFromSpace: true } }); }); it('job in * space, single space user can not untag or delete', async () => { @@ -99,7 +99,7 @@ export default ({ getService }: FtrProviderContext) => { idSpace1 ); - expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: false, canUntag: false } }); + expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: false, canRemoveFromSpace: false } }); }); it('job in * space, all spaces user can delete but not untag', async () => { @@ -111,7 +111,7 @@ export default ({ getService }: FtrProviderContext) => { idStarSpace ); - expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: true, canUntag: false } }); + expect(body).to.eql({ [adJobIdStarSpace]: { canDelete: true, canRemoveFromSpace: false } }); }); }); }; From 388b2508b312d61f3fc480e2dc88379bc66ed9ce Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Dec 2020 14:26:14 +0100 Subject: [PATCH 38/40] [Lens] Make sure Lens does not reload unnecessarily (#86092) --- .../public/react_expression_renderer.test.tsx | 38 +++++++++ .../public/react_expression_renderer.tsx | 18 +++-- .../lens/public/app_plugin/mounter.tsx | 80 +++++++++++-------- .../editor_frame/suggestion_panel.tsx | 14 +++- 4 files changed, 108 insertions(+), 42 deletions(-) diff --git a/src/plugins/expressions/public/react_expression_renderer.test.tsx b/src/plugins/expressions/public/react_expression_renderer.test.tsx index e52d4d153882ff..4ebd626e70fc3e 100644 --- a/src/plugins/expressions/public/react_expression_renderer.test.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.test.tsx @@ -146,6 +146,44 @@ describe('ExpressionRenderer', () => { instance.unmount(); }); + it('waits for debounce period on other loader option change if specified', () => { + jest.useFakeTimers(); + + const refreshSubject = new Subject(); + const loaderUpdate = jest.fn(); + + (ExpressionLoader as jest.Mock).mockImplementation(() => { + return { + render$: new Subject(), + data$: new Subject(), + loading$: new Subject(), + update: loaderUpdate, + destroy: jest.fn(), + }; + }); + + const instance = mount( + + ); + + instance.setProps({ searchContext: { from: 'now-30m', to: 'now' } }); + + expect(loaderUpdate).toHaveBeenCalledTimes(1); + + act(() => { + jest.runAllTimers(); + }); + + expect(loaderUpdate).toHaveBeenCalledTimes(2); + + instance.unmount(); + }); + it('should display a custom error message if the user provides one and then remove it after successful render', () => { const dataSubject = new Subject(); const data$ = dataSubject.asObservable().pipe(share()); diff --git a/src/plugins/expressions/public/react_expression_renderer.tsx b/src/plugins/expressions/public/react_expression_renderer.tsx index 894325c8b65f7d..eac2371ec66d0c 100644 --- a/src/plugins/expressions/public/react_expression_renderer.tsx +++ b/src/plugins/expressions/public/react_expression_renderer.tsx @@ -90,21 +90,23 @@ export const ReactExpressionRenderer = ({ null ); const [debouncedExpression, setDebouncedExpression] = useState(expression); - useEffect(() => { + const [waitingForDebounceToComplete, setDebouncePending] = useState(false); + useShallowCompareEffect(() => { if (debounce === undefined) { return; } + setDebouncePending(true); const handler = setTimeout(() => { setDebouncedExpression(expression); + setDebouncePending(false); }, debounce); return () => { clearTimeout(handler); }; - }, [expression, debounce]); + }, [expression, expressionLoaderOptions, debounce]); const activeExpression = debounce !== undefined ? debouncedExpression : expression; - const waitingForDebounceToComplete = debounce !== undefined && expression !== debouncedExpression; /* eslint-disable react-hooks/exhaustive-deps */ // OK to ignore react-hooks/exhaustive-deps because options update is handled by calling .update() @@ -182,12 +184,16 @@ export const ReactExpressionRenderer = ({ // Re-fetch data automatically when the inputs change useShallowCompareEffect( () => { - if (expressionLoaderRef.current) { + // only update the loader if the debounce period is over + if (expressionLoaderRef.current && !waitingForDebounceToComplete) { expressionLoaderRef.current.update(activeExpression, expressionLoaderOptions); } }, - // when expression is changed by reference and when any other loaderOption is changed by reference - [{ activeExpression, ...expressionLoaderOptions }] + // when debounced, wait for debounce status to change to update loader. + // Otherwise, update when expression is changed by reference and when any other loaderOption is changed by reference + debounce === undefined + ? [{ activeExpression, ...expressionLoaderOptions }] + : [{ waitingForDebounceToComplete }] ); /* eslint-enable react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/lens/public/app_plugin/mounter.tsx b/x-pack/plugins/lens/public/app_plugin/mounter.tsx index b09ecfdcd5553a..fbfd9c57589480 100644 --- a/x-pack/plugins/lens/public/app_plugin/mounter.tsx +++ b/x-pack/plugins/lens/public/app_plugin/mounter.tsx @@ -3,11 +3,12 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useCallback } from 'react'; import { AppMountParameters, CoreSetup } from 'kibana/public'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { History } from 'history'; import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; @@ -86,25 +87,22 @@ export async function mountApp( }) ); - const getInitialInput = ( - routeProps: RouteComponentProps<{ id?: string }>, - editByValue?: boolean - ): LensEmbeddableInput | undefined => { + const getInitialInput = (id?: string, editByValue?: boolean): LensEmbeddableInput | undefined => { if (editByValue) { return embeddableEditorIncomingState?.valueInput as LensByValueInput; } - if (routeProps.match.params.id) { - return { savedObjectId: routeProps.match.params.id } as LensByReferenceInput; + if (id) { + return { savedObjectId: id } as LensByReferenceInput; } }; - const redirectTo = (routeProps: RouteComponentProps<{ id?: string }>, savedObjectId?: string) => { + const redirectTo = (history: History, savedObjectId?: string) => { if (!savedObjectId) { - routeProps.history.push({ pathname: '/', search: routeProps.history.location.search }); + history.push({ pathname: '/', search: history.location.search }); } else { - routeProps.history.push({ + history.push({ pathname: `/edit/${savedObjectId}`, - search: routeProps.history.location.search, + search: history.location.search, }); } }; @@ -144,27 +142,45 @@ export async function mountApp( } }; - const renderEditor = ( - routeProps: RouteComponentProps<{ id?: string }>, - editByValue?: boolean + // const featureFlagConfig = await getByValueFeatureFlag(); + const EditorRenderer = React.memo( + (props: { id?: string; history: History; editByValue?: boolean }) => { + const redirectCallback = useCallback( + (id?: string) => { + redirectTo(props.history, id); + }, + [props.history] + ); + trackUiEvent('loaded'); + return ( + + ); + } + ); + + const EditorRoute = ( + routeProps: RouteComponentProps<{ id?: string }> & { editByValue?: boolean } ) => { - trackUiEvent('loaded'); return ( - redirectTo(routeProps, savedObjectId)} - redirectToOrigin={redirectToOrigin} - redirectToDashboard={redirectToDashboard} - onAppLeave={params.onAppLeave} - setHeaderActionMenu={params.setHeaderActionMenu} + ); }; @@ -185,13 +201,13 @@ export async function mountApp( - + renderEditor(routeProps, true)} + render={(routeProps) => } /> - + diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx index e42d4daffbb66d..338a998b6b4dc8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/suggestion_panel.tsx @@ -7,7 +7,7 @@ import './suggestion_panel.scss'; import _, { camelCase } from 'lodash'; -import React, { useState, useEffect, useMemo } from 'react'; +import React, { useState, useEffect, useMemo, useRef } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiIcon, @@ -270,13 +270,19 @@ export function SuggestionPanel({ [frame.query, frame.dateRange.fromDate, frame.dateRange.toDate, frame.filters] ); + const contextRef = useRef(context); + contextRef.current = context; + const AutoRefreshExpressionRenderer = useMemo(() => { const autoRefreshFetch$ = plugins.data.query.timefilter.timefilter.getAutoRefreshFetch$(); return (props: ReactExpressionRendererProps) => ( - + ); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [plugins.data.query.timefilter.timefilter, context]); + }, [plugins.data.query.timefilter.timefilter, ExpressionRendererComponent]); const [lastSelectedSuggestion, setLastSelectedSuggestion] = useState(-1); From 36a8343064849c09fffd7a2d010cb38a028d82d6 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Dec 2020 15:02:51 +0100 Subject: [PATCH 39/40] fix time scaling bugs (#86444) --- .../indexpattern_datasource/indexpattern.test.ts | 3 +++ .../suffix_formatter.test.ts | 16 ++++++++++++++++ .../indexpattern_datasource/suffix_formatter.ts | 5 +++++ .../indexpattern_datasource/to_expression.ts | 1 + 4 files changed, 25 insertions(+) diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 042ea0353ac635..2e55abf4a429ad 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -530,6 +530,9 @@ describe('IndexPattern Data Source', () => { "outputColumnId": Array [ "col1", ], + "outputColumnName": Array [ + "Count of records", + ], "targetUnit": Array [ "h", ], diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts index ef1739e4424fa3..ade6ba099d70e9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.test.ts @@ -25,4 +25,20 @@ describe('suffix formatter', () => { expect(convertMock).toHaveBeenCalledWith(12345); expect(formatFactory).toHaveBeenCalledWith({ id: 'nestedFormatter', params: nestedParams }); }); + + it('should not add suffix to empty strings', () => { + const convertMock = jest.fn((x) => ''); + const formatFactory = jest.fn(() => ({ convert: convertMock })); + const SuffixFormatter = getSuffixFormatter((formatFactory as unknown) as FormatFactory); + const nestedParams = { abc: 123 }; + const formatterInstance = new SuffixFormatter({ + unit: 'h', + id: 'nestedFormatter', + params: nestedParams, + }); + + const result = formatterInstance.convert(12345); + + expect(result).toEqual(''); + }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts index f5d764acab0869..3d9f3be01a11bb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/suffix_formatter.ts @@ -49,6 +49,11 @@ export function getSuffixFormatter(formatFactory: FormatFactory) { val ); + // do not add suffixes to empty strings + if (formattedValue === '') { + return ''; + } + if (suffix) { return `${formattedValue}${suffix}`; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts index a85b8920366b55..a5ce4dfbea371f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/to_expression.ts @@ -121,6 +121,7 @@ function getExpressionForLayer( dateColumnId: [firstDateHistogramColumn![0]], inputColumnId: [id], outputColumnId: [id], + outputColumnName: [col.label], targetUnit: [col.timeScale!], }, }; From 34803ed98bbe6e04a11add288b1f9fae8bc3e201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 21 Dec 2020 15:13:07 +0100 Subject: [PATCH 40/40] [Logs UI] Toggle log entry context menu when user clicks the trigger button (#86307) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../logging/log_text_stream/log_entry_context_menu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx index c627d6eda22a57..fe57b9db0e8b7c 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_context_menu.tsx @@ -60,7 +60,7 @@ export const LogEntryContextMenu: React.FC = ({ size="s" fill aria-label={ariaLabel || DEFAULT_MENU_LABEL} - onClick={onOpen} + onClick={isOpen ? onClose : onOpen} minWidth="auto" >