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 3e8baf0af2834..b91f9d69e85e2 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 0000000000000..b465d916b2292 --- /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 0000000000000..e68db5cde940b --- /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 0000000000000..bb57755250ba2 --- /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 91c8259cb2c55..f4f0e07742521 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 45ccb4f2c539f..0d78bceeaf2fa 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 b6da21927e342..efc3b6044a804 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 d75f42f6e486b..87e165f8e0014 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 c9b8e671b7df8..0000000000000 --- 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 ed8b208864611..771b09cec2a35 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 0000000000000..ff0939fdcce1f --- /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 9c4c06c5f4e18..fb7a91d046313 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 d2ecdf61c882d..d1f8391ad082a 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 b8a37661fe9f7..89109af4cecb9 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 587e43b218f44..15817b425021e 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 ef1e35b8ceb4b..07f7391ca94d9 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 6b594fa60e0c7..0000000000000 --- 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 0000000000000..ba38b46aa0552 --- /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 bea69a86edebb..c487b584d8ace 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 cae3b9f33850a..2f0c3bf8fd5ba 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 5d22e22ee0eb6..5d3a2c105c4a4 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 9a30ca30616b7..93dc6898f0c2e 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 a7681e1766427..64f1088dc3392 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 e88d90fe5b24b..65cc3643a88cb 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 83cac1d456a3a..dee9ca4d32c5f 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 6c2e0a2c926ad..5dd39d172e1c2 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 fc77332c18fc9..2fa8b32f68291 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 62998da73d6f9..897e3b2761c74 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 090893964c881..46ae17171203c 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 e63e46af6e8ca..e1feea4b6cbe1 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 fc44a4db7cf5e..6ebbfcfb91336 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 5406a90a75a35..c4237da0d0e80 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 0000000000000..2dd46419528c1 --- /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 0000000000000..1dd57ffe8bc01 --- /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 0000000000000..b9a6f2b3e750f --- /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 0000000000000..cca179e8c0608 --- /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 0000000000000..ede1529c049d7 --- /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 0000000000000..614704701222b --- /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 0000000000000..eadcf6df473e5 --- /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 0000000000000..1f7c4db83ab06 --- /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 0000000000000..dae085617cad8 --- /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 0000000000000..d4c005d5cfa2b --- /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 ff38ab5add367..1145d7853cb1a 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 26f7a1f3d50ec..a5e015391d8fd 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 a00aed96a6fbc..a0bca62dc7419 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 95ce5456ef9a8..e6662a7c30407 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 2d424ab9c960a..9c093016f4a38 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 0000000000000..27d34f5cf418f --- /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 bda408cd00e75..573442de807ae 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 69c2afbd01960..70ffc1f8a9fc4 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 f039324b3af64..2f9f0d6723749 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 bcdc7ba2089c6..a6e94d3715ca3 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 77e975a46d37b..c52bef9a23b25 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 3926ad2220e35..889bcc15d8df0 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 f65dbaf1087d8..118ebdf56db90 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 209d7dd6dbcde..bc24b9ca51980 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 0000000000000..a45094ac55ba0 --- /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 eca363da89433..b3f02c831d977 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 ba38b46aa0552..084c3d1fb9c4f 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 c487b584d8ace..ec5cf541c2316 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 d611c4102e8f8..8e631e497e57b 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 22037c021701f..14941b019421b 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 1a5c99294c281..e344d4c3c27e4 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 0000000000000..58c0c0bb775ca --- /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 f84e9de87c734..6a5c4a83aa3ad 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 276499b435e1f..77e9c2d00b2dd 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 0000000000000..f00d015f316d2 --- /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 3676b744b8cc9..76b6708b93bd1 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 8eff48251b371..845d64c16500d 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 cbaeddf472d52..be034d125dcee 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 0b34bea810520..92900059668db 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 5118a1a8818c0..6f43d87bdcd53 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 03818fccda0bc..7563b66e58ae9 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 7183943591c88..25a36fed9c9c5 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 d8b8a6f91fe31..605ea26b76c6f 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 274a4355e26e2..c759edd638260 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 0b9e873f46151..bd6756835af31 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 6bf3dbbe47b1d..c9ecf42dbc8e8 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 d69a01ab6bb26..5a892bb4f6ca3 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 b35ef1e8f2f9a..22e1769145f88 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 0000000000000..2dec2dcb001d7 --- /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 0f20ade8187fd..66f7c1d36dfb2 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 563353793f991..98c20c5abcc2d 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 7d8949421126c..a83194d67a759 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 2df75436f5f96..bf6786d0d4e4c 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 48360647e24ee..7a12c43427a91 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 fc3e05fbfaed0..ae729dd4f0095 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 e3fe9cd86356a..dfaed32ff72ae 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 e1e0866d886a3..a122d59959156 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 3210d53841993..cf3d0bf1544c2 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 937e6737d2716..915f245fbcd19 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 8bd473c003eb1..c76cc4b45fc19 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 edeae5205b646..ce7cae5678338 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 0d2ce654d4a0a..2e419168f2e1b 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 35389bbe4b3ba..0fed7cd0fc8fc 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 c4237da0d0e80..27c3410767d8a 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 2dd46419528c1..ddce27789b82c 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 1dd57ffe8bc01..9ebe404659ca2 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 ede1529c049d7..50e4d473e5f78 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 614704701222b..ad83e0eb1a286 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 eadcf6df473e5..d02545625345d 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 1f7c4db83ab06..f923661a57bcc 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 dae085617cad8..72a245df817ba 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 d4c005d5cfa2b..6c5b1de79c320 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 c76cc4b45fc19..93aff04b3f7c0 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 a0bca62dc7419..f2799cde41e97 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 e6662a7c30407..ca52d14befb38 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 a5e015391d8fd..5b85e7f2ab54e 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 0f4eaaeda0e1a..d443b02393c05 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 17e6e2538f044..52fa0d03a9719 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 6d211cf45ca9f..3e4039bafcac7 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 ad7874c01655b..6c46c849c79bc 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 d65ca4868d282..83dd396e5e080 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 2c3b8fd9606a9..99497d61c716e 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 0000000000000..9ad19653a6c12 --- /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 634e3cfec3a0b..c1e4da0bab54d 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 0000000000000..1dbd31897d307 --- /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 6721f7a03584c..1244a0e229cdb 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 b143afd1988e6..ff408ba431ed9 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 b2aa3a05d7eb0..4dec1f75ba322 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 a676878382865..51ea5f997e859 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 0000000000000..dd52f6b8227ba --- /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 0000000000000..533adbaf5bab9 --- /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 49cc573b686bc..1501efc589fc0 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 455e237848a4b..a46ec560a13e0 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 9194a3a1db5e4..55a8377261dd9 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 049a3ad1bed66..91334f312623d 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 52b845a1aee2d..543c63b334940 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 66c0cc165fc05..1b222cfaacf7c 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 ed8ce512a2eb8..8342061ee00c3 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 ade26551039fa..5b598a0b8565e 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 4f343e64b12ae..11415f5512380 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 0000000000000..2dafadf272608 --- /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 82a1a1f841a22..bbea32e4d2d04 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 c48706f4edcb9..735b9c3637cdd 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 b8ac69cbae68a..0ddda96a1100d 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 139010a3d2446..2d319a81dd063 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 ded309ce64e2e..8849fb05fcf3c 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 d54612b6f4f29..794982a0b6193 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 ab26b6a9cc0bb..8b7c386a4dcc6 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 e35d2ba6108f5..81398c1d8e836 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 52de0659ed527..657301d6e1cb3 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 0439b47569e72..a0de3a7663a12 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 6569340785736..9e26d453d76a3 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 33a2d27f39856..6c1c4218568e3 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 b43a56562a2aa..64b7148d87d9e 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 ebff72a255777..a3bcc310b8084 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 0000000000000..6034f32a8c659 --- /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 63195621fb9c8..4622f73b9feb0 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 5fa718dfb34cd..b58476a01dc14 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 6ba4333309f00..65205738f82c3 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 0000000000000..c8933a7cd14a9 --- /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 72e2f51c37e4c..19af93b67aca0 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 5e9c2f62ceef8..bb217fbeed304 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 b4b3cbe22b715..8e99b5c693cec 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 1e00fd27e3d1b..3660039f2513c 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 4dbfa610be6c3..25cbe964c19e1 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 adb884ca064ba..fc57811bb2077 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 3f7daef6d0473..751ec5a03ee7b 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 0000000000000..387ec0c126785 --- /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 0000000000000..f2a4a20568215 --- /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 0000000000000..6703ffffb5651 --- /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 0000000000000..5acc4b2ae351b --- /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 5d1ecbcbd48ee..3570bf01e49c4 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 f662d36247a2f..982d129dae2a6 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 5fa068d0a92e0..e6ec1f1c043c2 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 ec1b1c6eb94ef..28108d019327b 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 0000000000000..557cce80d0f55 --- /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 120b333d43e72..2942a6385f13f 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 1228ea4d94f4c..37336316c4c91 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 7dbe9cd0ba84c..2c3b0d348591e 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 6f1d45598c92e..4f39283149e73 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 c778814af536c..4c0307e9eca55 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 ca58b628cbd22..a04512fb2aba5 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 d1b5898d1a5f5..9506d98cbe50e 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 5b1d2541480ad..a294fce9599c3 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 20c30eedf5b91..1878f49debe8c 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 762218b72ab10..1f7f364600e21 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 117c30ddb86e3..83fe4fdaa1edd 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 677effec5be65..311c15a1da7cb 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 9a87d4c9d886a..99fadb240335a 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 bef8ad26fb12c..36a184d3da507 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 bf498229a1b54..edee1ea3219db 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 a9aa2bf24601b..7265e70a791a3 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 c8812b07e5949..7425c5f7248ac 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 2abe3e9b8cf71..84f1fd9187f4f 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 51d741f9374fe..b5999535064aa 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 48fec4efee5db..728787543927c 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 f2dba4785ea05..6fb5f874022a0 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 fa7d932aca1e8..09fdff9977b5e 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 4cef16a47571e..b832c797adac6 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 ad69ad5ab41cd..94b0c5b6c8a27 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 2ad7c77a58ca9..41af5a4c47e78 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 d6134883332e3..18573c5084618 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 e5bd6a0f10d82..d8329f492fe80 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 0000000000000..aaed18b3b8890 --- /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 027ae4209b77f..dbeeeb9979aae 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 62817cd0a1e33..2f844b6844645 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 8de3821161ab4..2040043d4351b 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 b90018c3d9cdd..bd90f23b4ab59 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 8a3dbe5a6350c..88f85eb7a7d05 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 06e51958a2d1e..92926d10a543c 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 a2c2d9245eabe..753a3ff2ec6ec 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 f36f7b4ee77a4..0f14215ff1309 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 d3a62657372ac..0000000000000 --- 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 fdd31ca75be2a..957e3f279ff60 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 0595609b11e49..0000000000000 --- 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 5a1b5d1e06861..342163ed2e413 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 62610624655a1..2f5966f9ba940 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 a6aeba23cd280..b875b1fce4288 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 0000000000000..175e3fe947a0f --- /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 0000000000000..a85be3142d0f2 --- /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 931e474a41006..c22c8bc6b6245 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 8d74c8e555fee..461bf861d4d5e 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 273703cacca06..547608f40e6aa 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 b09433c6454ad..ca1c8eec8c2f7 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 94e66bf404f5c..0000000000000 --- 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 e1fb6d342457e..0000000000000 --- 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 fd1ea7df4fb74..76e347bddd168 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 e8baf44ff9cbc..a75637e8ea9d3 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 74b55d85f10e3..b69734cfc3233 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 fd784ff17fa84..0000000000000 --- 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 0d9fa2d83ee57..0000000000000 --- 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 426f17f9a0352..5603c852ad39d 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 b69bba892f475..ed76cfea97684 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 0ccf8aa3d7415..0000000000000 --- 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 2f0d22cf6dd74..da7a7a8bfe645 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 c5ef6843390b3..0000000000000 --- 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 129144a66cee5..0000000000000 --- 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 ca999322b7a56..f29d487d774e0 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 e95e7e1eb38b6..1ebb30c49c0b3 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 8e7fb8b8bbf29..b20f08520c43d 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 d540de7637441..300c46a47c47f 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 b996620686a28..95b737a8d6cae 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 f94b34ecc2d90..27c1b1eb48f16 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 dff958608ef9e..edb7d2d3a1551 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 e35eb503ab62b..4fe8431770dea 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 6f03777e14552..dee5f75f7c074 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 bd8d4dc50b8fd..0000000000000 --- 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 5c4fc284d83b1..0000000000000 --- 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 f4662820a1fb0..1a087e2a01fb2 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 944da72bd11d1..84ce5b0382624 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 2d7aeff79a689..2b0911b72abd5 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 23d2ab080d75e..04801a5ee1cea 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 ce7ecf434056a..30fef7e3a7c66 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 1781da980dc30..3b84523d782f6 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 81e62380eacfb..8f207e94e8fbe 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 3dda04d738c96..7b15e2576e704 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 eb3a053b78a2d..c60a1efabf987 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 e5df6d860b404..120540ddb92ec 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 aa54706476a8f..816c1737608da 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 f1aff6322522a..36a38ae76bc0e 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 f4d1a8988da78..f842568859fc2 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 4d24e6d1afd49..60d5e167921cc 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 ff408ba431ed9..d0c3907d31242 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 8965446cc85fa..d893724f616d2 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 5c95214ef591b..efaff42c19e2f 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 0fc7c7965010b..d537ef2bd0c5c 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 c7c71656bceb2..c0e13a84066ca 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 0361939fd07e6..cb78fac5471a9 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 eb83641448986..b784a46127305 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 dea4a88bda082..ce6a1cc20fc4d 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 db71b94ac855f..23696612fd82a 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 fd6ea3b9aa2b2..99d21198dd151 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 c3b1496b8eca8..d9fb063a5bb56 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 df24d9c0393fe..c41ecaabe8479 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 6f43d87bdcd53..a417fb3938b8a 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 7563b66e58ae9..a401795c498b3 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 24785dd50a032..531fbcee94db6 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 2c298b437a118..74bb70e913bcc 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 88aca4c07ee31..fca1694747ce2 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 bb1f5dd9270d5..404df2db019a1 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 7c1ab11f75027..8b8678371dd83 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 d223c0abcccb7..7890e4bab44a3 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 fdb75e9a426e9..84a7ae45fc7b8 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 2384dfab13c8c..808cb1f3fbca0 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 ecbf4d1f7b988..6bba87e85eb95 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 87bca03f8c3ba..0fc261b3e1fb3 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 312e75314bd92..0000000000000 --- 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 2c019b09881d1..1b1231c132dde 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 04a75cb3a53d0..7e7fba0ba80d3 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 0266a755be926..62fac245514cd 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 2d38acc57519f..a1e4fad719dc4 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 68bad16972ec2..cc43fc6bcb582 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 67ed487d29378..dec169c59c6ef 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 1804d0d52ae7a..2a32d19874c22 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 280a957396c34..b8dbd0f945c32 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 d87500218975a..dcf9f69cc291d 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 4ac7c23c30893..16463abb07631 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 41e52c3ac1327..7065d758bbc49 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 f7592977858d2..5661acc26fdb6 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 48b64a1c84588..27c63aba791dd 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 a7324f5530130..50ad350cd90b9 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 24385bd6baa42..4e5b3187af42b 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 9cda534a340d6..2f161efe6f388 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 f0497780430d4..7c8915c3f143f 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 fcd0c9b94c988..e58ab5b8ea9d2 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 b24c0b6983f40..d3a1f29f0c7ff 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 7f5137812ee32..d2d3c37a69287 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 e1d8a2b3671a2..ff79cda1bb215 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 921c2aed00624..c2bf48188c313 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 52946b3dca7a1..2f983ba247625 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 fdd096e718339..fda07fb47ab0d 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 f1273ab00bdd4..78f3464484ccf 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 e0627c521bb79..23e73bea5dfec 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 24406cefce7a2..e3730084d7020 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 8dfb2c54c5ab0..bfeab263d20e3 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 3e5fc0a968d39..12b74d475e932 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 9856d3a558e24..8361b002c8206 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 190470e6c4235..b00760e9664f3 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 d5283822107e4..57308f9c18b40 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 7aaabc427790a..5e5d431c7c212 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 a401cafcff8ea..417991ea2d367 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 1023c7ac7d889..e38058a312f3c 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 f803ff48df76d..17bcd61898efe 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 9bb506b3ebf14..77362752f6960 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 40fc1a8e05a68..7c53356615ee9 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 8e99b5c693cec..252c8130c38d6 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 e557e7395fe56..adc1fa0728784 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 71040598d34b1..cbb60480c4cf1 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 43f5264ff22e3..42e89b66d9c51 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 42179374ec672..9efcf46148e1f 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 5a4b7e9f77582..7d141e81ddf36 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 63acd2207ac3a..d80c21bde8de8 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 b1e092ba5786a..ebc41a793f3b3 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 e8836dbd8f7a1..c6c8eee003e4e 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 5f07a4b523056..cef5f33726ed5 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 ef445617e9295..94711cf76c145 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 c8753772648c2..917afb487b1f4 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 4f8eff6c6c75a..98139e9802fec 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 fecfcf145ca99..e0b05d972bd38 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 8b018f4a2a906..ceedc87275393 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 29300818288b4..48dd589dd0b8f 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 43c56af7f71bc..3de3c6c7187c6 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 3455b833c404a..e87e29a36084b 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 166264ebddee1..6f07c0150630d 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 667e6e34a5640..b6d43d8e0cf92 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 e2e97e3a87696..fb41d30e34fae 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 9478e24c9560f..22ea6c31dbe69 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 ba69b97bee7f1..064cdf0b28eb9 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 d81702691a3a1..244cf1d0a8f51 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 9bf419c7dacaa..b7abed979164e 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 36131c2e2844d..32c1d8d3cdf56 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 764ae5a87ec0e..3980eef7caac2 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 13a5c2440877a..0f6bf0f1d56b4 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 4986eb718dc2c..356158913eb92 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 c099a1413a88f..0000000000000 --- 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 0000000000000..48db4b1f261b6 --- /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 d8603c9d02fcb..8fc535c680b26 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 0000000000000..773fc3eeff483 --- /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 807953c51a42c..ab6f90c8fec81 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 1cf03225cec03..9ce5778fb72e5 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 2d3fdb9cb9429..adbb38f20c028 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 515758965d6d1..7d38e3b732fc0 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 37cc8f4ac3b93..30a7685a193b2 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 afec2055140d3..febbbb23db1ef 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 99cf8740809da..9b307690cf12c 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 2a1d0d2ad11cf..fc05e61442e83 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 9857e55e36570..d38dee8a41504 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 8f514ca49e848..d112a665d77c0 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 00b3a10bba538..d7931b563c777 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 6dad9851e5adb..c497d4f459f00 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 80199e0026ac3..63086d56d0753 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 9bb8a695454d7..0525767e616be 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 6aee6f9d4fdfa..ae8d2a47c7dc7 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 bce5f1293e66b..92ae01b185f7a 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 9802e4532b05b..e3f5a744e8b7d 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 1628824b46a08..4000ebcfd767a 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 3470dba636aa8..0295d44b646d7 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 f6190b39214e9..4a33d0d3af33e 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 b4fdc427d9db3..f3a914ff4be29 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 779d54216e26c..1662cf4037cac 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 8e95fc3ad238a..f4498b10e4c8d 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 45c190c42605c..a0d2ca57f90b3 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 f6d6654d7fece..c0840d58174b3 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 7f0809cf9b9d8..c97571fbbd6f3 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 0000000000000..5e397d11b0a89 --- /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 a370341000960..b318427012c0a 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 179148265e68d..b640ef1b89606 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 0000000000000..9438f38194493 --- /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 c5ea38278e820..1bbede5658942 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 0000000000000..50adef8268694 --- /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 06917fcac1b4d..9357ad7d5077e 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 0000000000000..fe7bec70196c8 --- /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 274a475872b0b..7d70af4b535fe 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 b2cb217fecaa2..54f020e57cf4a 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 0000000000000..1255a6fe9f0ca --- /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 6559b4d7110be..b9b9f955c7ab5 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 19ec286307a09..76de2b2662bb0 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 c020e7595c565..bad74430b8966 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 4c89cbeb446a0..590ff872f3bf9 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 2a203b57d201b..3d32742c168ad 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 0235f748ec1e0..3333dba36fe69 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 8d9b29175162e..12496b07d3482 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 ca0f35d6612b2..36a193a4f6f94 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 120540ddb92ec..3493844a71ac1 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 21a3bf6e73e61..9023044184df3 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 e9dbc2e972c68..f0b51e456337f 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 57a745b19748d..1163fd2dc9953 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 10567544af6ea..8bd59e47730fd 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 b1410e2498667..3b223e6fdb9b2 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 768041a376ad1..4650aeefba056 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 60d5e167921cc..cd3527d5ad7ab 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 287b7ccdb88e0..b94c2cd12cd5f 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 b1d7318ff5107..5d00b96634214 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 d8495840453f3..862e82589b9bc 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 bdb6744745c97..d5077308a5301 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 a3df7bc3dcdc4..f514f1ecb9ae6 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 5b230e5a179a5..0d634f60e282f 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 4d1562e703770..0000000000000 --- 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 7a30d7acf5f89..1ccab64a58569 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 220d932787fff..3f27d1868461f 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 9a731f1d5aee0..ab6cac7f357a0 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 cf827fc0ed08f..8a972c65f8412 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 420568d75691b..19c4c189d3640 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/.nvmrc b/.nvmrc index 420568d75691b..19c4c189d3640 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -14.15.2 +14.15.3 diff --git a/package.json b/package.json index 252c8130c38d6..6e8809063ca57 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 134cda6f54188..cee7ee62e3210 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 54f989b93e22f..cb65c9f52ca3f 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 6dc2cb3163b1f..78d771aec13e0 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 d7afd73c0e3a7..13ce43f77c8b0 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 60c22c42c00b6..7154a77496b81 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 05c69e5ccb78b..cccd5fbc439d7 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 6ec6210ecb344..2d1df6e8cb462 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 64bfad92a8458..4703371f5e0de 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 1a10765eaf734..f04a1015bcbcd 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 369f03ab8cb11..20d5097d8a556 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 2dafadf272608..4d22d422ecda6 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 5054c47245f0f..1fe40fc8777f4 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 00b70658e4289..5579b8e1275a3 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 c8d0a7a5d49f2..d50e9c3a5c282 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 062c32c758794..0400810a8c379 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 ec97a45a8a800..fdb89033c4e2c 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 bd0e7f89bf535..403a1e531258e 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 59effdbf8f512..ba490b91fae10 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 cc8d78b53137e..8ba632633a3af 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 f09d1630cd675..184277bae3da8 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 164ce993eebac..51d7361bfe762 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 f3dc3855eb91b..0592c944de570 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 93a6c0d29cf3c..bf5e2fe2289db 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 49b56b5571b44..0cfce2d47f189 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 3037504ed3e39..0f747e9c24eec 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 e38058a312f3c..e5b82a5d3fcbc 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 17bcd61898efe..879418870b527 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 e364661361814..0cd5118c5e316 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 e25e703de5f7e..30ca2c620f1d7 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 2790ea8aa6bfa..6057d2669f04c 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 25f830df58df5..e5a6a8977a8c8 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 d41ca915f34c1..ef8d17d8c4c28 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 cf3d0bf1544c2..a67fd218d55f3 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 7df5c6e157106..875268bd93112 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 f875bcabdcde8..aa61fcde9e9c2 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 cd1ebe47a8c22..3fffe9fe230b4 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 8da45276fa532..7e297c1cb6d7b 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 e02cc11269e9c..39a8a36a6d0a8 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 43aaa26d86642..6a00d2987f12b 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 83c4792e26f59..43e5b75aa5f8b 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 af4aedda06ef7..50de66ac1c3ba 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 f06d4bdef74cb..1fe1b809d4f30 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 6a75d0655cf59..022c07defc9c1 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 a98aae4ec8107..3cad33b278749 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 7fd99a17598ae..3928228357d4c 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 0000000000000..bf060da1e76b8 --- /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 dd936776f691a..073e30bbc6e26 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 2114f21d9cead..18f6e8d127b1b 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 78d169e8529c5..9d0c63a8dc7aa 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 e4385526ee6e8..b773aad67c3f8 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 0187ecd927e65..b0ef28fbb7b0d 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 c068fee3cd6c3..ca5b4938ff42e 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 ae0dd85b6a8b5..961320baa6a4e 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 a4c93f95dc53d..157d3ecc738a1 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 ed4f3277a4a04..ae297b840ebc8 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 da74a6fc0004d..d14ef648c22d3 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 6345d546c716f..4b262f1f51319 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 35df003af380d..0000000000000 --- 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 1d99b82a67326..24a78e5d64749 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 0000000000000..d2978b3c02d53 --- /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 7facde28e956f..acb0f94cf878c 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 9bda628df66dc..f7e41bce674ee 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 7f0dd39162adf..b6538af31bd60 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 8783113502623..259f9be1a1b26 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 151946ab31fd9..4393d2b2fcf4d 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 bf002907b1a43..015ec6e4ec9c0 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 29f9b218ea177..a5144ab2a7af7 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 147398694f191..a64d38a9fda70 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 f682999cd5966..9258b143c9747 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 d95c90d417203..e80d5a333bbdf 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 e52d4d153882f..4ebd626e70fc3 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 894325c8b65f7..eac2371ec66d0 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 b09ecfdcd5553..fbfd9c5758948 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 e42d4daffbb66..338a998b6b4dc 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 042ea0353ac63..2e55abf4a429a 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 ef1739e4424fa..ade6ba099d70e 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 f5d764acab086..3d9f3be01a11b 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 a85b8920366b5..a5ce4dfbea371 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 c627d6eda22a5..fe57b9db0e8b7 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" >