From 8def60e1dac63ba7d7900bbffca6e1519ff1dfb7 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin Date: Mon, 3 Feb 2020 14:43:10 +0100 Subject: [PATCH 01/60] Unify Security and EncryptedSavedObjects public contract names according to NP migration guide. (#56597) --- .../plugins/alerting/server/alerts_client.ts | 6 +++--- .../alerting/server/alerts_client_factory.ts | 6 +++--- x-pack/legacy/plugins/alerting/server/shim.ts | 18 +++++++++--------- .../server/task_runner/task_runner_factory.ts | 4 ++-- .../plugins/encrypted_saved_objects/index.ts | 4 ++-- x-pack/legacy/plugins/reporting/index.ts | 2 +- .../legacy/plugins/reporting/server/plugin.ts | 2 +- x-pack/legacy/plugins/siem/server/plugin.ts | 4 ++-- .../actions/server/lib/action_executor.ts | 4 ++-- .../actions/server/lib/task_runner_factory.ts | 4 ++-- x-pack/plugins/actions/server/plugin.ts | 8 ++++---- x-pack/plugins/case/server/plugin.ts | 2 +- x-pack/plugins/case/server/services/index.ts | 5 +---- .../encrypted_saved_objects/server/index.ts | 2 +- .../encrypted_saved_objects/server/mocks.ts | 6 +++--- .../encrypted_saved_objects/server/plugin.ts | 6 +++--- x-pack/plugins/security/server/index.ts | 6 +++--- x-pack/plugins/security/server/mocks.ts | 4 ++-- x-pack/plugins/security/server/plugin.ts | 4 ++-- .../lib/spaces_client/spaces_client.test.ts | 8 ++++---- .../server/lib/spaces_client/spaces_client.ts | 6 +++--- x-pack/plugins/spaces/server/plugin.ts | 2 +- .../server/spaces_service/spaces_service.ts | 2 +- .../common/fixtures/plugins/aad/index.ts | 5 +++-- .../plugins/encrypted_saved_objects/index.ts | 9 +++++---- 25 files changed, 64 insertions(+), 65 deletions(-) diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index a6ba936b76570..1346d403edda1 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -29,7 +29,7 @@ import { CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '../../../../plugins/security/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; type NormalizedAlertAction = Omit; @@ -45,7 +45,7 @@ interface ConstructorOptions { taskManager: TaskManagerStartContract; savedObjectsClient: SavedObjectsClientContract; alertTypeRegistry: AlertTypeRegistry; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; spaceId?: string; namespace?: string; getUserName: () => Promise; @@ -120,7 +120,7 @@ export class AlertsClient { private readonly invalidateAPIKey: ( params: InvalidateAPIKeyParams ) => Promise; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; constructor({ alertTypeRegistry, diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts index eab1cc3ce627b..de789fba0ac38 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts @@ -11,7 +11,7 @@ import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { SecurityPluginStartContract } from './shim'; import { KibanaRequest, Logger } from '../../../../../src/core/server'; import { InvalidateAPIKeyParams } from '../../../../plugins/security/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; export interface ConstructorOpts { @@ -21,7 +21,7 @@ export interface ConstructorOpts { securityPluginSetup?: SecurityPluginStartContract; getSpaceId: (request: Hapi.Request) => string | undefined; spaceIdToNamespace: SpaceIdToNamespaceFunction; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; } export class AlertsClientFactory { @@ -31,7 +31,7 @@ export class AlertsClientFactory { private readonly securityPluginSetup?: SecurityPluginStartContract; private readonly getSpaceId: (request: Hapi.Request) => string | undefined; private readonly spaceIdToNamespace: SpaceIdToNamespaceFunction; - private readonly encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + private readonly encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; constructor(options: ConstructorOpts) { this.logger = options.logger; diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index 80d01ea722926..bc8b0eb863634 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -15,10 +15,10 @@ import { getTaskManagerSetup, getTaskManagerStart } from '../../task_manager/ser import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { - PluginSetupContract as EncryptedSavedObjectsSetupContract, - PluginStartContract as EncryptedSavedObjectsStartContract, + EncryptedSavedObjectsPluginSetup, + EncryptedSavedObjectsPluginStart, } from '../../../../plugins/encrypted_saved_objects/server'; -import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { CoreSetup, LoggerFactory, @@ -44,8 +44,8 @@ export interface Server extends Legacy.Server { /** * Shim what we're thinking setup and start contracts will look like */ -export type SecurityPluginSetupContract = Pick; -export type SecurityPluginStartContract = Pick; +export type SecurityPluginSetupContract = Pick; +export type SecurityPluginStartContract = Pick; export type XPackMainPluginSetupContract = Pick; /** @@ -71,14 +71,14 @@ export interface AlertingPluginsSetup { taskManager: TaskManagerSetupContract; actions: ActionsPluginSetupContract; xpack_main: XPackMainPluginSetupContract; - encryptedSavedObjects: EncryptedSavedObjectsSetupContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; licensing: LicensingPluginSetup; } export interface AlertingPluginsStart { actions: ActionsPluginStartContract; security?: SecurityPluginStartContract; spaces: () => SpacesPluginStartContract | undefined; - encryptedSavedObjects: EncryptedSavedObjectsStartContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; taskManager: TaskManagerStartContract; } @@ -120,7 +120,7 @@ export function shim( actions: newPlatform.setup.plugins.actions as ActionsPluginSetupContract, xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins - .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, + .encryptedSavedObjects as EncryptedSavedObjectsPluginSetup, licensing: newPlatform.setup.plugins.licensing as LicensingPluginSetup, }; @@ -131,7 +131,7 @@ export function shim( // initializes after this function is called spaces: () => server.plugins.spaces, encryptedSavedObjects: newPlatform.start.plugins - .encryptedSavedObjects as EncryptedSavedObjectsStartContract, + .encryptedSavedObjects as EncryptedSavedObjectsPluginStart, taskManager: getTaskManagerStart(server)!, }; diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts index 67fef33b69c6d..d2ecfb64c8a81 100644 --- a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -5,7 +5,7 @@ */ import { Logger } from '../../../../../../src/core/server'; import { RunContext } from '../../../../../plugins/task_manager/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../../../plugins/encrypted_saved_objects/server'; import { PluginStartContract as ActionsPluginStartContract } from '../../../../../plugins/actions/server'; import { AlertType, @@ -19,7 +19,7 @@ export interface TaskRunnerContext { logger: Logger; getServices: GetServicesFunction; executeAction: ActionsPluginStartContract['execute']; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; } diff --git a/x-pack/legacy/plugins/encrypted_saved_objects/index.ts b/x-pack/legacy/plugins/encrypted_saved_objects/index.ts index 69058a7a33f59..ce343dba006cf 100644 --- a/x-pack/legacy/plugins/encrypted_saved_objects/index.ts +++ b/x-pack/legacy/plugins/encrypted_saved_objects/index.ts @@ -6,7 +6,7 @@ import { Root } from 'joi'; import { Legacy } from 'kibana'; -import { PluginSetupContract } from '../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginSetup } from '../../../plugins/encrypted_saved_objects/server'; // @ts-ignore import { AuditLogger } from '../../server/lib/audit_logger'; @@ -29,7 +29,7 @@ export const encryptedSavedObjects = (kibana: { init(server: Legacy.Server) { const encryptedSavedObjectsPlugin = (server.newPlatform.setup.plugins - .encryptedSavedObjects as unknown) as PluginSetupContract; + .encryptedSavedObjects as unknown) as EncryptedSavedObjectsPluginSetup; if (!encryptedSavedObjectsPlugin) { throw new Error('New Platform XPack EncryptedSavedObjects plugin is not available.'); } diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index d2a68e309a4b3..966e4ff209ad6 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -9,7 +9,7 @@ import { Legacy } from 'kibana'; import { IUiSettingsClient } from 'kibana/server'; import { resolve } from 'path'; import { PluginStart as DataPluginStart } from '../../../../src/plugins/data/server'; -import { PluginSetupContract as SecurityPluginSetup } from '../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../plugins/security/server'; import { PLUGIN_ID, UI_SETTINGS_CUSTOM_PDF_LOGO } from './common/constants'; import { config as reportingConfig } from './config'; import { LegacySetup, ReportingPlugin, reportingPluginFactory } from './server/plugin'; diff --git a/x-pack/legacy/plugins/reporting/server/plugin.ts b/x-pack/legacy/plugins/reporting/server/plugin.ts index a2938d442f7df..e618d23e8ed1f 100644 --- a/x-pack/legacy/plugins/reporting/server/plugin.ts +++ b/x-pack/legacy/plugins/reporting/server/plugin.ts @@ -14,7 +14,7 @@ import { } from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { PluginStart as DataPluginStart } from '../../../../../src/plugins/data/server'; -import { PluginSetupContract as SecurityPluginSetup } from '../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../plugins/security/server'; // @ts-ignore import { mirrorPluginStatus } from '../../../server/lib/mirror_plugin_status'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 96eef2f44e5a0..94314367be59c 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup, PluginInitializerContext, Logger } from 'src/core/server'; -import { PluginSetupContract as SecurityPlugin } from '../../../../plugins/security/server'; +import { SecurityPluginSetup } from '../../../../plugins/security/server'; import { PluginSetupContract as FeaturesSetupContract } from '../../../../plugins/features/server'; import { initServer } from './init_server'; import { compose } from './lib/compose/kibana'; @@ -17,7 +17,7 @@ import { ruleStatusSavedObjectType, } from './saved_objects'; -export type SiemPluginSecurity = Pick; +export type SiemPluginSecurity = Pick; export interface PluginsSetup { features: FeaturesSetupContract; diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index be6916a74fe88..03a892a42792e 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -12,7 +12,7 @@ import { GetServicesFunction, RawAction, } from '../types'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; import { SpacesServiceSetup } from '../../../spaces/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { IEvent, IEventLogger } from '../../../event_log/server'; @@ -21,7 +21,7 @@ export interface ActionExecutorContext { logger: Logger; spaces?: SpacesServiceSetup; getServices: GetServicesFunction; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; actionTypeRegistry: ActionTypeRegistryContract; eventLogger: IEventLogger; } diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index c3e89e0c16efc..c78b43f4ef3ba 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -8,12 +8,12 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; import { Logger, CoreStart } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; import { ActionTaskParams, GetBasePathFunction, SpaceIdToNamespaceFunction } from '../types'; export interface TaskRunnerContext { logger: Logger; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; getScopedSavedObjectsClient: CoreStart['savedObjects']['getScopedClient']; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index cb0e3347541fd..dab09fc455ecf 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -20,8 +20,8 @@ import { } from '../../../../src/core/server'; import { - PluginSetupContract as EncryptedSavedObjectsSetupContract, - PluginStartContract as EncryptedSavedObjectsStartContract, + EncryptedSavedObjectsPluginSetup, + EncryptedSavedObjectsPluginStart, } from '../../encrypted_saved_objects/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { LicensingPluginSetup } from '../../licensing/server'; @@ -67,13 +67,13 @@ export interface PluginStartContract { export interface ActionsPluginsSetup { taskManager: TaskManagerSetupContract; - encryptedSavedObjects: EncryptedSavedObjectsSetupContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; licensing: LicensingPluginSetup; spaces?: SpacesPluginSetup; event_log: IEventLogService; } export interface ActionsPluginsStart { - encryptedSavedObjects: EncryptedSavedObjectsStartContract; + encryptedSavedObjects: EncryptedSavedObjectsPluginStart; taskManager: TaskManagerStartContract; } diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index c52461cade058..37d087433a2ed 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -9,7 +9,7 @@ import { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server'; import { ConfigType } from './config'; import { initCaseApi } from './routes/api'; import { CaseService } from './services'; -import { PluginSetupContract as SecurityPluginSetup } from '../../security/server'; +import { SecurityPluginSetup } from '../../security/server'; function createConfig$(context: PluginInitializerContext) { return context.config.create().pipe(map(config => config)); diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts index 684d905a5c71f..531d5fa5b87e5 100644 --- a/x-pack/plugins/case/server/services/index.ts +++ b/x-pack/plugins/case/server/services/index.ts @@ -21,10 +21,7 @@ import { UpdatedCaseType, UpdatedCommentType, } from '../routes/api/types'; -import { - AuthenticatedUser, - PluginSetupContract as SecurityPluginSetup, -} from '../../../security/server'; +import { AuthenticatedUser, SecurityPluginSetup } from '../../../security/server'; interface ClientArgs { client: SavedObjectsClientContract; diff --git a/x-pack/plugins/encrypted_saved_objects/server/index.ts b/x-pack/plugins/encrypted_saved_objects/server/index.ts index 5e6edb95ec37a..3b4b91de355c7 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/index.ts @@ -9,7 +9,7 @@ import { ConfigSchema } from './config'; import { Plugin } from './plugin'; export { EncryptedSavedObjectTypeRegistration, EncryptionError } from './crypto'; -export { PluginSetupContract, PluginStartContract } from './plugin'; +export { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index 7f53f47760f12..13d7127db7835 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -4,21 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginSetupContract, PluginStartContract } from './plugin'; +import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; function createEncryptedSavedObjectsSetupMock() { return { registerType: jest.fn(), __legacyCompat: { registerLegacyAPI: jest.fn() }, usingEphemeralEncryptionKey: true, - } as jest.Mocked; + } as jest.Mocked; } function createEncryptedSavedObjectsStartMock() { return { isEncryptionError: jest.fn(), getDecryptedAsInternalUser: jest.fn(), - } as jest.Mocked; + } as jest.Mocked; } export const encryptedSavedObjectsMock = { diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index d9185251ca466..a0218c51c2723 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -20,13 +20,13 @@ import { import { EncryptedSavedObjectsAuditLogger } from './audit'; import { SavedObjectsSetup, setupSavedObjects } from './saved_objects'; -export interface PluginSetupContract { +export interface EncryptedSavedObjectsPluginSetup { registerType: (typeRegistration: EncryptedSavedObjectTypeRegistration) => void; __legacyCompat: { registerLegacyAPI: (legacyAPI: LegacyAPI) => void }; usingEphemeralEncryptionKey: boolean; } -export interface PluginStartContract extends SavedObjectsSetup { +export interface EncryptedSavedObjectsPluginStart extends SavedObjectsSetup { isEncryptionError: (error: Error) => boolean; } @@ -59,7 +59,7 @@ export class Plugin { this.logger = this.initializerContext.logger.get(); } - public async setup(core: CoreSetup): Promise { + public async setup(core: CoreSetup): Promise { const { config, usingEphemeralEncryptionKey } = await createConfig$(this.initializerContext) .pipe(first()) .toPromise(); diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index 17e49b8cf40d3..c0e86b289fe54 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -12,7 +12,7 @@ import { RecursiveReadonly, } from '../../../../src/core/server'; import { ConfigSchema } from './config'; -import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin'; +import { Plugin, SecurityPluginSetup, PluginSetupDependencies } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported // functions or removal of exports should be considered as a breaking change. @@ -24,7 +24,7 @@ export { InvalidateAPIKeyParams, InvalidateAPIKeyResult, } from './authentication'; -export { PluginSetupContract }; +export { SecurityPluginSetup }; export { AuthenticatedUser } from '../common/model'; export const config: PluginConfigDescriptor> = { @@ -35,7 +35,7 @@ export const config: PluginConfigDescriptor> = { ], }; export const plugin: PluginInitializer< - RecursiveReadonly, + RecursiveReadonly, void, PluginSetupDependencies > = (initializerContext: PluginInitializerContext) => new Plugin(initializerContext); diff --git a/x-pack/plugins/security/server/mocks.ts b/x-pack/plugins/security/server/mocks.ts index d5c08d5ab1ab9..ababf12c2be60 100644 --- a/x-pack/plugins/security/server/mocks.ts +++ b/x-pack/plugins/security/server/mocks.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginSetupContract } from './plugin'; +import { SecurityPluginSetup } from './plugin'; import { authenticationMock } from './authentication/index.mock'; import { authorizationMock } from './authorization/index.mock'; @@ -19,7 +19,7 @@ function createSetupMock() { mode: mockAuthz.mode, }, registerSpacesService: jest.fn(), - __legacyCompat: {} as PluginSetupContract['__legacyCompat'], + __legacyCompat: {} as SecurityPluginSetup['__legacyCompat'], }; } diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index ce682d8b30eb7..5764418234739 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -49,7 +49,7 @@ export interface LegacyAPI { /** * Describes public Security plugin contract returned at the `setup` stage. */ -export interface PluginSetupContract { +export interface SecurityPluginSetup { authc: Authentication; authz: Pick; @@ -166,7 +166,7 @@ export class Plugin { csp: core.http.csp, }); - return deepFreeze({ + return deepFreeze({ authc, authz: { diff --git a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.test.ts b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.test.ts index 24a994e836e87..74e75fb8f12c7 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.test.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginSetupContract as SecuritySetupContract } from '../../../../security/server'; +import { SecurityPluginSetup } from '../../../../security/server'; import { SpacesClient } from './spaces_client'; import { ConfigType, ConfigSchema } from '../../config'; import { GetSpacePurpose } from '../../../common/model/types'; @@ -224,17 +224,17 @@ describe('#getAll', () => { [ { purpose: undefined, - expectedPrivilege: (mockAuthorization: SecuritySetupContract['authz']) => + expectedPrivilege: (mockAuthorization: SecurityPluginSetup['authz']) => mockAuthorization.actions.login, }, { purpose: 'any', - expectedPrivilege: (mockAuthorization: SecuritySetupContract['authz']) => + expectedPrivilege: (mockAuthorization: SecurityPluginSetup['authz']) => mockAuthorization.actions.login, }, { purpose: 'copySavedObjectsIntoSpace', - expectedPrivilege: (mockAuthorization: SecuritySetupContract['authz']) => + expectedPrivilege: (mockAuthorization: SecurityPluginSetup['authz']) => mockAuthorization.actions.ui.get('savedObjectsManagement', 'copyIntoSpace'), }, ].forEach(scenario => { diff --git a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts index f964ae7d7ac32..22c34c03368e3 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_client/spaces_client.ts @@ -6,7 +6,7 @@ import Boom from 'boom'; import { omit } from 'lodash'; import { KibanaRequest } from 'src/core/server'; -import { PluginSetupContract as SecurityPluginSetupContract } from '../../../../security/server'; +import { SecurityPluginSetup } from '../../../../security/server'; import { isReservedSpace } from '../../../common/is_reserved_space'; import { Space } from '../../../common/model/space'; import { SpacesAuditLogger } from '../audit_logger'; @@ -17,7 +17,7 @@ const SUPPORTED_GET_SPACE_PURPOSES: GetSpacePurpose[] = ['any', 'copySavedObject const PURPOSE_PRIVILEGE_MAP: Record< GetSpacePurpose, - (authorization: SecurityPluginSetupContract['authz']) => string + (authorization: SecurityPluginSetup['authz']) => string > = { any: authorization => authorization.actions.login, copySavedObjectsIntoSpace: authorization => @@ -28,7 +28,7 @@ export class SpacesClient { constructor( private readonly auditLogger: SpacesAuditLogger, private readonly debugLogger: (message: string) => void, - private readonly authorization: SecurityPluginSetupContract['authz'] | null, + private readonly authorization: SecurityPluginSetup['authz'] | null, private readonly callWithRequestSavedObjectRepository: any, private readonly config: ConfigType, private readonly internalSavedObjectRepository: any, diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index b8ef81c05f7aa..52ff7eaee3d68 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -14,7 +14,7 @@ import { PluginInitializerContext, } from '../../../../src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; -import { PluginSetupContract as SecurityPluginSetup } from '../../security/server'; +import { SecurityPluginSetup } from '../../security/server'; import { LicensingPluginSetup } from '../../licensing/server'; import { XPackMainPlugin } from '../../../legacy/plugins/xpack_main/server/xpack_main'; import { createDefaultSpace } from './lib/create_default_space'; diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts index f8ed58fa57551..95bda96d89461 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts @@ -8,7 +8,7 @@ import { map, take } from 'rxjs/operators'; import { Observable, Subscription } from 'rxjs'; import { Legacy } from 'kibana'; import { Logger, KibanaRequest, CoreSetup } from '../../../../../src/core/server'; -import { PluginSetupContract as SecurityPluginSetup } from '../../../security/server'; +import { SecurityPluginSetup } from '../../../security/server'; import { LegacyAPI } from '../plugin'; import { SpacesClient } from '../lib/spaces_client'; import { ConfigType } from '../config'; diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts index d7bee93f5c94b..7194c642e7015 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/index.ts @@ -8,7 +8,7 @@ import Joi from 'joi'; import Hapi from 'hapi'; import { Legacy } from 'kibana'; import KbnServer from '../../../../../../../src/legacy/server/kbn_server'; -import { PluginStartContract } from '../../../../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsPluginStart } from '../../../../../../plugins/encrypted_saved_objects/server'; interface CheckAADRequest extends Hapi.Request { payload: { @@ -25,7 +25,8 @@ export default function(kibana: any) { name: 'aad-fixtures', init(server: Legacy.Server) { const newPlatform = ((server as unknown) as KbnServer).newPlatform; - const esoPlugin = newPlatform.start.plugins.encryptedSavedObjects as PluginStartContract; + const esoPlugin = newPlatform.start.plugins + .encryptedSavedObjects as EncryptedSavedObjectsPluginStart; server.route({ method: 'POST', diff --git a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts b/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts index a194e477da755..e61b8f24a1f69 100644 --- a/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts +++ b/x-pack/test/plugin_api_integration/plugins/encrypted_saved_objects/index.ts @@ -8,8 +8,8 @@ import { Request } from 'hapi'; import { boomify, badRequest } from 'boom'; import { Legacy } from 'kibana'; import { - PluginSetupContract, - PluginStartContract, + EncryptedSavedObjectsPluginSetup, + EncryptedSavedObjectsPluginStart, } from '../../../../plugins/encrypted_saved_objects/server'; const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret'; @@ -26,7 +26,7 @@ export default function esoPlugin(kibana: any) { path: '/api/saved_objects/get-decrypted-as-internal-user/{id}', async handler(request: Request) { const encryptedSavedObjectsStart = server.newPlatform.start.plugins - .encryptedSavedObjects as PluginStartContract; + .encryptedSavedObjects as EncryptedSavedObjectsPluginStart; const namespace = server.plugins.spaces && server.plugins.spaces.getSpaceId(request); try { return await encryptedSavedObjectsStart.getDecryptedAsInternalUser( @@ -44,7 +44,8 @@ export default function esoPlugin(kibana: any) { }, }); - (server.newPlatform.setup.plugins.encryptedSavedObjects as PluginSetupContract).registerType({ + (server.newPlatform.setup.plugins + .encryptedSavedObjects as EncryptedSavedObjectsPluginSetup).registerType({ type: SAVED_OBJECT_WITH_SECRET_TYPE, attributesToEncrypt: new Set(['privateProperty']), attributesToExcludeFromAAD: new Set(['publicPropertyExcludedFromAAD']), From f2713659f63fda25881e1c99087dc084cf4c85a1 Mon Sep 17 00:00:00 2001 From: Peter Pisljar Date: Mon, 3 Feb 2020 09:01:34 -0500 Subject: [PATCH 02/60] filtering (making it work with expressions) (#55351) * adding meta information to KibanaDatatable * updating filtering functions to use new information * moving filter creation to APPLY_FILTER_ACTION * adding SELECT_RANGE_ACTION and TRIGGER * making _meta optional * inlining legacy code for inspector * fixing jest tests * keeping apply_filter_action and adding value_click_action and trigger * utilities for serializing/unserializing aggConfigs * renaming prop to indexPatternId * cleanup * updating interpreter functional baselines * trying to fix tests * Fix legend tests * reverting update to multi metric screenshot * updating based on review * updating tests Co-authored-by: Nick Partridge Co-authored-by: Elastic Machine --- .../public/actions}/filters/brush_event.js | 14 +- .../actions}/filters/brush_event.test.js | 78 +++++++---- .../filters/brush_event.test.mocks.ts | 2 +- .../filters/create_filters_from_event.js} | 51 ++++--- .../public/actions/select_range_action.ts | 91 +++++++++++++ .../data/public/actions/value_click_action.ts | 126 ++++++++++++++++++ src/legacy/core_plugins/data/public/legacy.ts | 6 +- src/legacy/core_plugins/data/public/plugin.ts | 28 +++- .../build_tabular_inspector_data.ts | 4 +- .../search/expressions/create_filter.js | 66 +++++++++ .../data/public/search/expressions/esaggs.ts | 2 + .../data/public/search/expressions/utils.ts | 51 +++++++ .../core_plugins/data/public/search/index.ts | 1 + .../vislib/components/legend/legend.test.tsx | 58 ++++---- .../vislib/components/legend/legend.tsx | 60 +++++---- .../public/embeddable/visualize_embeddable.ts | 36 ++--- .../public/np_ready/public/filters/index.ts | 21 --- .../public/np_ready/public/index.ts | 3 - .../np_ready/public/types/base_vis_type.js | 10 -- src/legacy/ui/public/agg_types/agg_config.ts | 2 +- src/legacy/ui/public/agg_types/agg_types.ts | 92 +++++++++++++ src/legacy/ui/public/agg_types/index.ts | 80 +---------- .../ui/public/agg_types/param_types/field.ts | 4 +- src/legacy/ui/public/agg_types/utils.ts | 2 +- .../public/vis/lib/least_common_interval.ts | 2 +- src/plugins/embeddable/public/bootstrap.ts | 16 +++ src/plugins/embeddable/public/index.ts | 2 + .../embeddable/public/lib/triggers/index.ts | 2 + .../expression_types/kibana_datatable.ts | 7 + .../screenshots/baseline/combined_test.png | Bin 22801 -> 16994 bytes .../baseline/final_screenshot_test.png | Bin 22852 -> 17033 bytes .../screenshots/baseline/metric_all_data.png | Bin 32900 -> 24240 bytes .../baseline/metric_invalid_data.png | Bin 1920 -> 1806 bytes .../baseline/metric_percentage_mode.png | Bin 32587 -> 23705 bytes .../baseline/metric_single_metric_data.png | Bin 29643 -> 22004 bytes .../screenshots/baseline/partial_test_1.png | Bin 15140 -> 11228 bytes .../screenshots/baseline/partial_test_2.png | Bin 22801 -> 16994 bytes .../screenshots/baseline/partial_test_3.png | Bin 7049 -> 7054 bytes .../baseline/tagcloud_all_data.png | Bin 15701 -> 11827 bytes .../baseline/tagcloud_fontsize.png | Bin 13808 -> 10314 bytes .../baseline/tagcloud_invalid_data.png | Bin 1920 -> 1806 bytes .../baseline/tagcloud_metric_data.png | Bin 9163 -> 6712 bytes .../screenshots/baseline/tagcloud_options.png | Bin 18998 -> 15100 bytes .../snapshots/baseline/combined_test2.json | 2 +- .../snapshots/baseline/combined_test3.json | 2 +- .../snapshots/baseline/final_output_test.json | 2 +- .../snapshots/baseline/metric_all_data.json | 2 +- .../baseline/metric_multi_metric_data.json | 2 +- .../baseline/metric_percentage_mode.json | 2 +- .../baseline/metric_single_metric_data.json | 2 +- .../snapshots/baseline/partial_test_1.json | 2 +- .../snapshots/baseline/partial_test_2.json | 2 +- .../snapshots/baseline/partial_test_3.json | 2 +- .../snapshots/baseline/step_output_test2.json | 2 +- .../snapshots/baseline/step_output_test3.json | 2 +- .../snapshots/baseline/tagcloud_all_data.json | 2 +- .../snapshots/baseline/tagcloud_fontsize.json | 2 +- .../baseline/tagcloud_metric_data.json | 2 +- .../snapshots/baseline/tagcloud_options.json | 2 +- .../snapshots/session/combined_test2.json | 2 +- .../snapshots/session/combined_test3.json | 2 +- .../snapshots/session/final_output_test.json | 2 +- .../snapshots/session/metric_all_data.json | 2 +- .../session/metric_multi_metric_data.json | 2 +- .../session/metric_percentage_mode.json | 2 +- .../session/metric_single_metric_data.json | 2 +- .../snapshots/session/partial_test_1.json | 2 +- .../snapshots/session/partial_test_2.json | 2 +- .../snapshots/session/partial_test_3.json | 2 +- .../snapshots/session/step_output_test2.json | 2 +- .../snapshots/session/step_output_test3.json | 2 +- .../snapshots/session/tagcloud_all_data.json | 2 +- .../snapshots/session/tagcloud_fontsize.json | 2 +- .../session/tagcloud_metric_data.json | 2 +- .../snapshots/session/tagcloud_options.json | 2 +- 75 files changed, 700 insertions(+), 281 deletions(-) rename src/legacy/core_plugins/{visualizations/public/np_ready/public => data/public/actions}/filters/brush_event.js (81%) rename src/legacy/core_plugins/{visualizations/public/np_ready/public => data/public/actions}/filters/brush_event.test.js (71%) rename src/legacy/core_plugins/{visualizations/public/np_ready/public => data/public/actions}/filters/brush_event.test.mocks.ts (92%) rename src/legacy/core_plugins/{visualizations/public/np_ready/public/filters/vis_filters.js => data/public/actions/filters/create_filters_from_event.js} (72%) create mode 100644 src/legacy/core_plugins/data/public/actions/select_range_action.ts create mode 100644 src/legacy/core_plugins/data/public/actions/value_click_action.ts create mode 100644 src/legacy/core_plugins/data/public/search/expressions/create_filter.js create mode 100644 src/legacy/core_plugins/data/public/search/expressions/utils.ts delete mode 100644 src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts create mode 100644 src/legacy/ui/public/agg_types/agg_types.ts diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.js b/src/legacy/core_plugins/data/public/actions/filters/brush_event.js similarity index 81% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.js rename to src/legacy/core_plugins/data/public/actions/filters/brush_event.js index e0854205b132e..67711bd4599a2 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.js +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.js @@ -19,9 +19,10 @@ import _ from 'lodash'; import moment from 'moment'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../plugins/data/public'; +import { deserializeAggConfig } from '../../search/expressions/utils'; -export function onBrushEvent(event) { +export async function onBrushEvent(event, getIndexPatterns) { const isNumber = event.data.ordered; const isDate = isNumber && event.data.ordered.date; @@ -29,9 +30,12 @@ export function onBrushEvent(event) { if (!xRaw) return []; const column = xRaw.table.columns[xRaw.column]; if (!column) return []; - const aggConfig = event.aggConfigs[xRaw.column]; - if (!aggConfig) return []; - const indexPattern = aggConfig.getIndexPattern(); + if (!column.meta) return []; + const indexPattern = await getIndexPatterns().get(column.meta.indexPatternId); + const aggConfig = deserializeAggConfig({ + ...column.meta, + indexPattern, + }); const field = aggConfig.params.field; if (!field) return []; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.js b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js similarity index 71% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.js rename to src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js index 215d440edd9d0..a6fe58503cd02 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.js +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js @@ -20,21 +20,36 @@ import _ from 'lodash'; import moment from 'moment'; import expect from '@kbn/expect'; + +jest.mock('../../../../../ui/public/agg_types/agg_configs', () => ({ + AggConfigs: function AggConfigs() { + return { + createAggConfig: ({ params }) => ({ + params, + getIndexPattern: () => ({ + timeFieldName: 'time', + }), + }), + }; + }, +})); + import { onBrushEvent } from './brush_event'; describe('brushEvent', () => { const DAY_IN_MS = 24 * 60 * 60 * 1000; const JAN_01_2014 = 1388559600000; + const aggConfigs = [ + { + params: {}, + getIndexPattern: () => ({ + timeFieldName: 'time', + }), + }, + ]; + const baseEvent = { - aggConfigs: [ - { - params: {}, - getIndexPattern: () => ({ - timeFieldName: 'time', - }), - }, - ], data: { fieldFormatter: _.constant({}), series: [ @@ -47,6 +62,11 @@ describe('brushEvent', () => { columns: [ { id: '1', + meta: { + type: 'histogram', + indexPatternId: 'indexPatternId', + aggConfigParams: aggConfigs[0].params, + }, }, ], }, @@ -69,9 +89,11 @@ describe('brushEvent', () => { expect(onBrushEvent).to.be.a(Function); }); - test('ignores event when data.xAxisField not provided', () => { + test('ignores event when data.xAxisField not provided', async () => { const event = _.cloneDeep(baseEvent); - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: () => baseEvent.data.indexPattern, + })); expect(filters.length).to.equal(0); }); @@ -84,22 +106,26 @@ describe('brushEvent', () => { }; beforeEach(() => { + aggConfigs[0].params.field = dateField; dateEvent = _.cloneDeep(baseEvent); - dateEvent.aggConfigs[0].params.field = dateField; dateEvent.data.ordered = { date: true }; }); - test('by ignoring the event when range spans zero time', () => { + test('by ignoring the event when range spans zero time', async () => { const event = _.cloneDeep(dateEvent); event.range = [JAN_01_2014, JAN_01_2014]; - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: () => dateEvent.data.indexPattern, + })); expect(filters.length).to.equal(0); }); - test('by updating the timefilter', () => { + test('by updating the timefilter', async () => { const event = _.cloneDeep(dateEvent); event.range = [JAN_01_2014, JAN_01_2014 + DAY_IN_MS]; - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: async () => dateEvent.data.indexPattern, + })); expect(filters[0].range.time.gte).to.be(new Date(JAN_01_2014).toISOString()); // Set to a baseline timezone for comparison. expect(filters[0].range.time.lt).to.be(new Date(JAN_01_2014 + DAY_IN_MS).toISOString()); @@ -114,17 +140,19 @@ describe('brushEvent', () => { }; beforeEach(() => { + aggConfigs[0].params.field = dateField; dateEvent = _.cloneDeep(baseEvent); - dateEvent.aggConfigs[0].params.field = dateField; dateEvent.data.ordered = { date: true }; }); - test('creates a new range filter', () => { + test('creates a new range filter', async () => { const event = _.cloneDeep(dateEvent); const rangeBegin = JAN_01_2014; const rangeEnd = rangeBegin + DAY_IN_MS; event.range = [rangeBegin, rangeEnd]; - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: () => dateEvent.data.indexPattern, + })); expect(filters.length).to.equal(1); expect(filters[0].range.anotherTimeField.gte).to.equal(moment(rangeBegin).toISOString()); expect(filters[0].range.anotherTimeField.lt).to.equal(moment(rangeEnd).toISOString()); @@ -142,22 +170,26 @@ describe('brushEvent', () => { }; beforeEach(() => { + aggConfigs[0].params.field = numberField; numberEvent = _.cloneDeep(baseEvent); - numberEvent.aggConfigs[0].params.field = numberField; numberEvent.data.ordered = { date: false }; }); - test('by ignoring the event when range does not span at least 2 values', () => { + test('by ignoring the event when range does not span at least 2 values', async () => { const event = _.cloneDeep(numberEvent); event.range = [1]; - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: () => numberEvent.data.indexPattern, + })); expect(filters.length).to.equal(0); }); - test('by creating a new filter', () => { + test('by creating a new filter', async () => { const event = _.cloneDeep(numberEvent); event.range = [1, 2, 3, 4]; - const filters = onBrushEvent(event); + const filters = await onBrushEvent(event, () => ({ + get: () => numberEvent.data.indexPattern, + })); expect(filters.length).to.equal(1); expect(filters[0].range.numberField.gte).to.equal(1); expect(filters[0].range.numberField.lt).to.equal(4); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.mocks.ts b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts similarity index 92% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.mocks.ts rename to src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts index f0de2f88dcb82..2cecfd0fe8b76 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/brush_event.test.mocks.ts +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.mocks.ts @@ -17,7 +17,7 @@ * under the License. */ -import { chromeServiceMock } from '../../../../../../../core/public/mocks'; +import { chromeServiceMock } from '../../../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => ({ npStart: { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js b/src/legacy/core_plugins/data/public/actions/filters/create_filters_from_event.js similarity index 72% rename from src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js rename to src/legacy/core_plugins/data/public/actions/filters/create_filters_from_event.js index 303dec690e62b..1037c718d0003 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/vis_filters.js +++ b/src/legacy/core_plugins/data/public/actions/filters/create_filters_from_event.js @@ -17,8 +17,10 @@ * under the License. */ -import { onBrushEvent } from './brush_event'; -import { esFilters } from '../../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../plugins/data/public'; +import { deserializeAggConfig } from '../../search/expressions/utils'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatterns } from '../../../../../../plugins/data/public/services'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter @@ -63,11 +65,16 @@ const getOtherBucketFilterTerms = (table, columnIndex, rowIndex) => { * @param {string} cellValue - value of the current cell * @return {array|string} - filter or list of filters to provide to queryFilter.addFilters() */ -const createFilter = (aggConfigs, table, columnIndex, rowIndex, cellValue) => { +const createFilter = async (table, columnIndex, rowIndex) => { + if (!table || !table.columns || !table.columns[columnIndex]) return; const column = table.columns[columnIndex]; - const aggConfig = aggConfigs[columnIndex]; + const aggConfig = deserializeAggConfig({ + type: column.meta.type, + aggConfigParams: column.meta.aggConfigParams, + indexPattern: await getIndexPatterns().get(column.meta.indexPatternId), + }); let filter = []; - const value = rowIndex > -1 ? table.rows[rowIndex][column.id] : cellValue; + const value = rowIndex > -1 ? table.rows[rowIndex][column.id] : null; if (value === null || value === undefined || !aggConfig.isFilterable()) { return; } @@ -85,26 +92,28 @@ const createFilter = (aggConfigs, table, columnIndex, rowIndex, cellValue) => { return filter; }; -const createFiltersFromEvent = event => { +const createFiltersFromEvent = async event => { const filters = []; const dataPoints = event.data || [event]; - dataPoints - .filter(point => point) - .forEach(val => { - const { table, column, row, value } = val; - const filter = createFilter(event.aggConfigs, table, column, row, value); - if (filter) { - filter.forEach(f => { - if (event.negate) { - f = esFilters.toggleFilterNegated(f); - } - filters.push(f); - }); - } - }); + await Promise.all( + dataPoints + .filter(point => point) + .map(async val => { + const { table, column, row } = val; + const filter = await createFilter(table, column, row); + if (filter) { + filter.forEach(f => { + if (event.negate) { + f = esFilters.toggleFilterNegated(f); + } + filters.push(f); + }); + } + }) + ); return filters; }; -export { createFilter, createFiltersFromEvent, onBrushEvent }; +export { createFilter, createFiltersFromEvent }; diff --git a/src/legacy/core_plugins/data/public/actions/select_range_action.ts b/src/legacy/core_plugins/data/public/actions/select_range_action.ts new file mode 100644 index 0000000000000..4ea5c78a9fd2b --- /dev/null +++ b/src/legacy/core_plugins/data/public/actions/select_range_action.ts @@ -0,0 +1,91 @@ +/* + * 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 { + IAction, + createAction, + IncompatibleActionError, +} from '../../../../../plugins/ui_actions/public'; +// @ts-ignore +import { onBrushEvent } from './filters/brush_event'; +import { + esFilters, + FilterManager, + TimefilterContract, + changeTimeFilter, + extractTimeFilter, + mapAndFlattenFilters, +} from '../../../../../plugins/data/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getIndexPatterns } from '../../../../../plugins/data/public/services'; + +export const SELECT_RANGE_ACTION = 'SELECT_RANGE_ACTION'; + +interface ActionContext { + data: any; + timeFieldName: string; +} + +async function isCompatible(context: ActionContext) { + try { + const filters: esFilters.Filter[] = (await onBrushEvent(context.data, getIndexPatterns)) || []; + return filters.length > 0; + } catch { + return false; + } +} + +export function selectRangeAction( + filterManager: FilterManager, + timeFilter: TimefilterContract +): IAction { + return createAction({ + type: SELECT_RANGE_ACTION, + id: SELECT_RANGE_ACTION, + getDisplayName: () => { + return i18n.translate('data.filter.applyFilterActionTitle', { + defaultMessage: 'Apply filter to current view', + }); + }, + isCompatible, + execute: async ({ timeFieldName, data }: ActionContext) => { + if (!(await isCompatible({ timeFieldName, data }))) { + throw new IncompatibleActionError(); + } + + const filters: esFilters.Filter[] = (await onBrushEvent(data, getIndexPatterns)) || []; + + const selectedFilters: esFilters.Filter[] = mapAndFlattenFilters(filters); + + if (timeFieldName) { + const { timeRangeFilter, restOfFilters } = extractTimeFilter( + timeFieldName, + selectedFilters + ); + filterManager.addFilters(restOfFilters); + if (timeRangeFilter) { + changeTimeFilter(timeFilter, timeRangeFilter); + } + } else { + filterManager.addFilters(selectedFilters); + } + }, + }); +} diff --git a/src/legacy/core_plugins/data/public/actions/value_click_action.ts b/src/legacy/core_plugins/data/public/actions/value_click_action.ts new file mode 100644 index 0000000000000..2f622eb1eb669 --- /dev/null +++ b/src/legacy/core_plugins/data/public/actions/value_click_action.ts @@ -0,0 +1,126 @@ +/* + * 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 { toMountPoint } from '../../../../../plugins/kibana_react/public'; +import { + IAction, + createAction, + IncompatibleActionError, +} from '../../../../../plugins/ui_actions/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { getOverlays, getIndexPatterns } from '../../../../../plugins/data/public/services'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { applyFiltersPopover } from '../../../../../plugins/data/public/ui/apply_filters'; +// @ts-ignore +import { createFiltersFromEvent } from './filters/create_filters_from_event'; +import { + esFilters, + FilterManager, + TimefilterContract, + changeTimeFilter, + extractTimeFilter, + mapAndFlattenFilters, +} from '../../../../../plugins/data/public'; + +export const VALUE_CLICK_ACTION = 'VALUE_CLICK_ACTION'; + +interface ActionContext { + data: any; + timeFieldName: string; +} + +async function isCompatible(context: ActionContext) { + try { + const filters: esFilters.Filter[] = (await createFiltersFromEvent(context.data)) || []; + return filters.length > 0; + } catch { + return false; + } +} + +export function valueClickAction( + filterManager: FilterManager, + timeFilter: TimefilterContract +): IAction { + return createAction({ + type: VALUE_CLICK_ACTION, + id: VALUE_CLICK_ACTION, + getDisplayName: () => { + return i18n.translate('data.filter.applyFilterActionTitle', { + defaultMessage: 'Apply filter to current view', + }); + }, + isCompatible, + execute: async ({ timeFieldName, data }: ActionContext) => { + if (!(await isCompatible({ timeFieldName, data }))) { + throw new IncompatibleActionError(); + } + + const filters: esFilters.Filter[] = (await createFiltersFromEvent(data)) || []; + + let selectedFilters: esFilters.Filter[] = mapAndFlattenFilters(filters); + + if (selectedFilters.length > 1) { + const indexPatterns = await Promise.all( + filters.map(filter => { + return getIndexPatterns().get(filter.meta.index!); + }) + ); + + const filterSelectionPromise: Promise = new Promise(resolve => { + const overlay = getOverlays().openModal( + toMountPoint( + applyFiltersPopover( + filters, + indexPatterns, + () => { + overlay.close(); + resolve([]); + }, + (filterSelection: esFilters.Filter[]) => { + overlay.close(); + resolve(filterSelection); + } + ) + ), + { + 'data-test-subj': 'selectFilterOverlay', + } + ); + }); + + selectedFilters = await filterSelectionPromise; + } + + if (timeFieldName) { + const { timeRangeFilter, restOfFilters } = extractTimeFilter( + timeFieldName, + selectedFilters + ); + filterManager.addFilters(restOfFilters); + if (timeRangeFilter) { + changeTimeFilter(timeFilter, timeRangeFilter); + } + } else { + filterManager.addFilters(selectedFilters); + } + }, + }); +} diff --git a/src/legacy/core_plugins/data/public/legacy.ts b/src/legacy/core_plugins/data/public/legacy.ts index a6646ea338c93..d37c17c224072 100644 --- a/src/legacy/core_plugins/data/public/legacy.ts +++ b/src/legacy/core_plugins/data/public/legacy.ts @@ -39,8 +39,6 @@ import { plugin } from '.'; const dataPlugin = plugin(); -export const setup = dataPlugin.setup(npSetup.core); +export const setup = dataPlugin.setup(npSetup.core, npSetup.plugins); -export const start = dataPlugin.start(npStart.core, { - data: npStart.plugins.data, -}); +export const start = dataPlugin.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index 6bd85ef020f16..da35366cdff31 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -22,6 +22,7 @@ import { DataPublicPluginStart, addSearchStrategy, defaultSearchStrategy, + DataPublicPluginSetup, } from '../../../../plugins/data/public'; import { ExpressionsSetup } from '../../../../plugins/expressions/public'; @@ -32,15 +33,27 @@ import { setInjectedMetadata, setFieldFormats, setSearchService, + setOverlays, // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../../../plugins/data/public/services'; +import { SELECT_RANGE_ACTION, selectRangeAction } from './actions/select_range_action'; +import { VALUE_CLICK_ACTION, valueClickAction } from './actions/value_click_action'; +import { + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../plugins/embeddable/public/lib/triggers'; +import { IUiActionsSetup, IUiActionsStart } from '../../../../plugins/ui_actions/public'; export interface DataPluginSetupDependencies { + data: DataPublicPluginSetup; expressions: ExpressionsSetup; + uiActions: IUiActionsSetup; } export interface DataPluginStartDependencies { data: DataPublicPluginStart; + uiActions: IUiActionsStart; } /** @@ -64,19 +77,30 @@ export interface DataStart {} // eslint-disable-line @typescript-eslint/no-empty export class DataPlugin implements Plugin { - public setup(core: CoreSetup) { + public setup(core: CoreSetup, { data, uiActions }: DataPluginSetupDependencies) { setInjectedMetadata(core.injectedMetadata); // This is to be deprecated once we switch to the new search service fully addSearchStrategy(defaultSearchStrategy); + + uiActions.registerAction( + selectRangeAction(data.query.filterManager, data.query.timefilter.timefilter) + ); + uiActions.registerAction( + valueClickAction(data.query.filterManager, data.query.timefilter.timefilter) + ); } - public start(core: CoreStart, { data }: DataPluginStartDependencies): DataStart { + public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { setUiSettings(core.uiSettings); setQueryService(data.query); setIndexPatterns(data.indexPatterns); setFieldFormats(data.fieldFormats); setSearchService(data.search); + setOverlays(core.overlays); + + uiActions.attachAction(SELECT_RANGE_TRIGGER, SELECT_RANGE_ACTION); + uiActions.attachAction(VALUE_CLICK_TRIGGER, VALUE_CLICK_ACTION); return {}; } diff --git a/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts index 6e6d2a15fa2ac..8f7953c408a97 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/build_tabular_inspector_data.ts @@ -19,9 +19,9 @@ import { set } from 'lodash'; // @ts-ignore -import { createFilter } from '../../../../visualizations/public'; import { FormattedData } from '../../../../../../plugins/inspector/public'; - +// @ts-ignore +import { createFilter } from './create_filter'; interface Column { id: string; name: string; diff --git a/src/legacy/core_plugins/data/public/search/expressions/create_filter.js b/src/legacy/core_plugins/data/public/search/expressions/create_filter.js new file mode 100644 index 0000000000000..3f4028a9b5525 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/expressions/create_filter.js @@ -0,0 +1,66 @@ +/* + * 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. + */ + +const getOtherBucketFilterTerms = (table, columnIndex, rowIndex) => { + if (rowIndex === -1) { + return []; + } + + // get only rows where cell value matches current row for all the fields before columnIndex + const rows = table.rows.filter(row => { + return table.columns.every((column, i) => { + return row[column.id] === table.rows[rowIndex][column.id] || i >= columnIndex; + }); + }); + const terms = rows.map(row => row[table.columns[columnIndex].id]); + + return [ + ...new Set( + terms.filter(term => { + const notOther = term !== '__other__'; + const notMissing = term !== '__missing__'; + return notOther && notMissing; + }) + ), + ]; +}; + +const createFilter = (aggConfigs, table, columnIndex, rowIndex, cellValue) => { + const column = table.columns[columnIndex]; + const aggConfig = aggConfigs[columnIndex]; + let filter = []; + const value = rowIndex > -1 ? table.rows[rowIndex][column.id] : cellValue; + if (value === null || value === undefined || !aggConfig.isFilterable()) { + return; + } + if (aggConfig.type.name === 'terms' && aggConfig.params.otherBucket) { + const terms = getOtherBucketFilterTerms(table, columnIndex, rowIndex); + filter = aggConfig.createFilter(value, { terms }); + } else { + filter = aggConfig.createFilter(value); + } + + if (!Array.isArray(filter)) { + filter = [filter]; + } + + return filter; +}; + +export { createFilter }; diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index 143283152d104..b4ea2cd378d61 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -46,6 +46,7 @@ import { Adapters } from '../../../../../../plugins/inspector/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { getQueryService, getIndexPatterns } from '../../../../../../plugins/data/public/services'; import { getRequestInspectorStats, getResponseInspectorStats } from '../..'; +import { serializeAggConfig } from './utils'; export interface RequestHandlerParams { searchSource: ISearchSource; @@ -289,6 +290,7 @@ export const esaggs = (): ExpressionFunction { + return { + type: aggConfig.type.name, + indexPatternId: aggConfig.getIndexPattern().id, + aggConfigParams: aggConfig.toJSON().params, + }; +}; + +interface DeserializeAggConfigParams { + type: string; + aggConfigParams: Record; + indexPattern: IndexPattern; +} + +export const deserializeAggConfig = ({ + type, + aggConfigParams, + indexPattern, +}: DeserializeAggConfigParams) => { + const aggConfigs = new AggConfigs(indexPattern); + const aggConfig = aggConfigs.createAggConfig({ + enabled: true, + type, + params: aggConfigParams, + }); + return aggConfig; +}; diff --git a/src/legacy/core_plugins/data/public/search/index.ts b/src/legacy/core_plugins/data/public/search/index.ts index e1c93ec0e3b1c..c975d5772e0a8 100644 --- a/src/legacy/core_plugins/data/public/search/index.ts +++ b/src/legacy/core_plugins/data/public/search/index.ts @@ -18,3 +18,4 @@ */ export { getRequestInspectorStats, getResponseInspectorStats } from './utils'; +export { serializeAggConfig } from './expressions/utils'; diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx index 6f0a5a3784b07..e66dff01b6bf2 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.test.tsx @@ -34,8 +34,8 @@ jest.mock('@elastic/eui', () => ({ jest.mock('../../../legacy_imports', () => ({ getTableAggs: jest.fn(), })); -jest.mock('../../../../../visualizations/public', () => ({ - createFiltersFromEvent: jest.fn().mockReturnValue(['yes']), +jest.mock('../../../../../data/public/actions/filters/create_filters_from_event', () => ({ + createFiltersFromEvent: jest.fn().mockResolvedValue(['yes']), })); const vis = { @@ -95,8 +95,8 @@ const uiState = { setSilent: jest.fn(), }; -const getWrapper = (props?: Partial) => - mount( +const getWrapper = async (props?: Partial) => { + const wrapper = mount( ) => ); + await (wrapper.find(VisLegend).instance() as VisLegend).refresh(); + wrapper.update(); + return wrapper; +}; + const getLegendItems = (wrapper: ReactWrapper) => wrapper.find('.visLegend__button'); describe('VisLegend Component', () => { @@ -120,9 +125,9 @@ describe('VisLegend Component', () => { }); describe('Legend open', () => { - beforeEach(() => { + beforeEach(async () => { mockState.set('vis.legendOpen', true); - wrapper = getWrapper(); + wrapper = await getWrapper(); }); it('should match the snapshot', () => { @@ -131,9 +136,9 @@ describe('VisLegend Component', () => { }); describe('Legend closed', () => { - beforeEach(() => { + beforeEach(async () => { mockState.set('vis.legendOpen', false); - wrapper = getWrapper(); + wrapper = await getWrapper(); }); it('should match the snapshot', () => { @@ -142,25 +147,26 @@ describe('VisLegend Component', () => { }); describe('Highlighting', () => { - beforeEach(() => { - wrapper = getWrapper(); + beforeEach(async () => { + wrapper = await getWrapper(); }); - it('should call highlight handler when legend item is focused', () => { + it('should call highlight handler when legend item is focused', async () => { const first = getLegendItems(wrapper).first(); + first.simulate('focus'); expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); }); - it('should call highlight handler when legend item is hovered', () => { + it('should call highlight handler when legend item is hovered', async () => { const first = getLegendItems(wrapper).first(); first.simulate('mouseEnter'); expect(vislibVis.handler.highlight).toHaveBeenCalledTimes(1); }); - it('should call unHighlight handler when legend item is blurred', () => { + it('should call unHighlight handler when legend item is blurred', async () => { let first = getLegendItems(wrapper).first(); first.simulate('focus'); first = getLegendItems(wrapper).first(); @@ -169,7 +175,7 @@ describe('VisLegend Component', () => { expect(vislibVis.handler.unHighlight).toHaveBeenCalledTimes(1); }); - it('should call unHighlight handler when legend item is unhovered', () => { + it('should call unHighlight handler when legend item is unhovered', async () => { const first = getLegendItems(wrapper).first(); first.simulate('mouseEnter'); @@ -187,8 +193,8 @@ describe('VisLegend Component', () => { }, }; - expect(() => { - wrapper = getWrapper({ vis: newVis }); + expect(async () => { + wrapper = await getWrapper({ vis: newVis }); const first = getLegendItems(wrapper).first(); first.simulate('focus'); first.simulate('blur'); @@ -197,8 +203,8 @@ describe('VisLegend Component', () => { }); describe('Filtering', () => { - beforeEach(() => { - wrapper = getWrapper(); + beforeEach(async () => { + wrapper = await getWrapper(); }); it('should filter out when clicked', () => { @@ -223,8 +229,8 @@ describe('VisLegend Component', () => { }); describe('Toggles details', () => { - beforeEach(() => { - wrapper = getWrapper(); + beforeEach(async () => { + wrapper = await getWrapper(); }); it('should show details when clicked', () => { @@ -236,8 +242,8 @@ describe('VisLegend Component', () => { }); describe('setColor', () => { - beforeEach(() => { - wrapper = getWrapper(); + beforeEach(async () => { + wrapper = await getWrapper(); }); it('sets the color in the UI state', () => { @@ -255,18 +261,18 @@ describe('VisLegend Component', () => { }); describe('toggleLegend function', () => { - it('click should show legend once toggled from hidden', () => { + it('click should show legend once toggled from hidden', async () => { mockState.set('vis.legendOpen', false); - wrapper = getWrapper(); + wrapper = await getWrapper(); const toggleButton = wrapper.find('.visLegend__toggle').first(); toggleButton.simulate('click'); expect(wrapper.exists('.visLegend__list')).toBe(true); }); - it('click should hide legend once toggled from shown', () => { + it('click should hide legend once toggled from shown', async () => { mockState.set('vis.legendOpen', true); - wrapper = getWrapper(); + wrapper = await getWrapper(); const toggleButton = wrapper.find('.visLegend__toggle').first(); toggleButton.simulate('click'); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx index 0eec557dd334e..a170af33583df 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/vislib/components/legend/legend.tsx @@ -24,7 +24,8 @@ import { i18n } from '@kbn/i18n'; import { EuiPopoverProps, EuiIcon, keyCodes, htmlIdGenerator } from '@elastic/eui'; // @ts-ignore -import { createFiltersFromEvent } from '../../../../../visualizations/public'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { createFiltersFromEvent } from '../../../../../data/public/actions/filters/create_filters_from_event'; import { CUSTOM_LEGEND_VIS_TYPES, LegendItem } from './models'; import { VisLegendItem } from './legend_item'; import { getPieNames } from './pie_utils'; @@ -94,11 +95,11 @@ export class VisLegend extends PureComponent { this.props.vis.API.events.filter({ data, negate }); }; - canFilter = (item: LegendItem): boolean => { + canFilter = async (item: LegendItem): Promise => { if (CUSTOM_LEGEND_VIS_TYPES.includes(this.props.vislibVis.visConfigArgs.type)) { return false; } - const filters = createFiltersFromEvent({ aggConfigs: this.state.tableAggs, data: item.values }); + const filters = await createFiltersFromEvent({ data: item.values }); return Boolean(filters.length); }; @@ -123,16 +124,39 @@ export class VisLegend extends PureComponent { }; // Most of these functions were moved directly from the old Legend class. Not a fan of this. - getLabels = (data: any, type: string) => { - if (!data) return []; - data = data.columns || data.rows || [data]; + setLabels = (data: any, type: string): Promise => + new Promise(async resolve => { + let labels = []; + if (CUSTOM_LEGEND_VIS_TYPES.includes(type)) { + const legendLabels = this.props.vislibVis.getLegendLabels(); + if (legendLabels) { + labels = map(legendLabels, label => { + return { label }; + }); + } + } else { + if (!data) return []; + data = data.columns || data.rows || [data]; - if (type === 'pie') return getPieNames(data); + labels = type === 'pie' ? getPieNames(data) : this.getSeriesLabels(data); + } - return this.getSeriesLabels(data); - }; + const labelsConfig = await Promise.all( + labels.map(async label => ({ + ...label, + canFilter: await this.canFilter(label), + })) + ); + + this.setState( + { + labels: labelsConfig, + }, + resolve + ); + }); - refresh = () => { + refresh = async () => { const vislibVis = this.props.vislibVis; if (!vislibVis || !vislibVis.visConfig) { this.setState({ @@ -154,24 +178,12 @@ export class VisLegend extends PureComponent { this.setState({ open: this.props.vis.params.addLegend }); } - if (CUSTOM_LEGEND_VIS_TYPES.includes(vislibVis.visConfigArgs.type)) { - const legendLabels = this.props.vislibVis.getLegendLabels(); - if (legendLabels) { - this.setState({ - labels: map(legendLabels, label => { - return { label }; - }), - }); - } - } else { - this.setState({ labels: this.getLabels(this.props.visData, vislibVis.visConfigArgs.type) }); - } - if (vislibVis.visConfig) { this.getColor = this.props.vislibVis.visConfig.data.getColorFunc(); } this.setState({ tableAggs: getTableAggs(this.props.vis) }); + await this.setLabels(this.props.visData, vislibVis.visConfigArgs.type); }; highlight = (event: BaseSyntheticEvent) => { @@ -219,7 +231,7 @@ export class VisLegend extends PureComponent { key={item.label} anchorPosition={anchorPosition} selected={this.state.selectedLabel === item.label} - canFilter={this.canFilter(item)} + canFilter={item.canFilter} onFilter={this.filter} onSelect={this.toggleDetails} legendId={this.legendId} diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts index 2af468ff77de6..d3badcc6bdc3f 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/visualize_embeddable.ts @@ -17,13 +17,12 @@ * under the License. */ -import _, { forEach } from 'lodash'; +import _ from 'lodash'; import { PersistedState } from 'ui/persisted_state'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { buildPipeline } from 'ui/visualize/loader/pipeline_helpers'; import { SavedObject } from 'ui/saved_objects/types'; -import { getTableAggs } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { AppState } from 'ui/state_management/app_state'; import { npStart } from 'ui/new_platform'; import { IExpressionLoaderParams } from 'src/plugins/expressions/public'; @@ -34,7 +33,6 @@ import { Query, onlyDisabledFiltersChanged, esFilters, - mapAndFlattenFilters, ISearchSource, } from '../../../../../plugins/data/public'; import { @@ -42,7 +40,8 @@ import { EmbeddableOutput, Embeddable, Container, - APPLY_FILTER_TRIGGER, + VALUE_CLICK_TRIGGER, + SELECT_RANGE_TRIGGER, } from '../../../../../plugins/embeddable/public'; import { dispatchRenderComplete } from '../../../../../plugins/kibana_utils/public'; import { SavedSearch } from '../../../kibana/public/discover/np_ready/types'; @@ -105,7 +104,6 @@ export class VisualizeEmbeddable extends Embeddable { - if (event.disabled || !eventName) { - return; - } else { - this.actions[eventName] = event.defaultAction; - } - }); - // This is a hack to give maps visualizations access to data in the // globalState, since they can no longer access it via searchSource. // TODO: Remove this as a part of elastic/kibana#30593 @@ -301,18 +290,13 @@ export class VisualizeEmbeddable extends Embeddable { - if (this.actions[event.name]) { - event.data.aggConfigs = getTableAggs(this.vis); - const filters: esFilters.Filter[] = this.actions[event.name](event.data) || []; - const mappedFilters = mapAndFlattenFilters(filters); - const timeFieldName = this.vis.indexPattern.timeFieldName; - - npStart.plugins.uiActions.executeTriggerActions(APPLY_FILTER_TRIGGER, { - embeddable: this, - filters: mappedFilters, - timeFieldName, - }); - } + const eventName = event.name === 'brush' ? SELECT_RANGE_TRIGGER : VALUE_CLICK_TRIGGER; + + npStart.plugins.uiActions.executeTriggerActions(eventName, { + embeddable: this, + timeFieldName: this.vis.indexPattern.timeFieldName, + data: event.data, + }); }) ); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts deleted file mode 100644 index 4558621dc6615..0000000000000 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/filters/index.ts +++ /dev/null @@ -1,21 +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. - */ - -// @ts-ignore -export * from './vis_filters'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts index 4dffcb8ce995e..3c4a1c1449d47 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/index.ts @@ -44,7 +44,6 @@ export function plugin(initializerContext: PluginInitializerContext) { /** @public static code */ export { Vis, VisParams, VisState } from './vis'; -export * from './filters'; export { TypesService } from './types/types_service'; export { Status } from './legacy/update_status'; @@ -53,6 +52,4 @@ export { buildPipeline, buildVislibDimensions, SchemaConfig } from './legacy/bui // @ts-ignore export { updateOldState } from './legacy/vis_update_state'; export { calculateObjectHash } from './legacy/calculate_object_hash'; -// @ts-ignore -export { createFiltersFromEvent } from './filters/vis_filters'; export { createSavedVisLoader } from '../../saved_visualizations/saved_visualizations'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js index f62b3a0b393ac..351acc48e2676 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/types/base_vis_type.js @@ -19,7 +19,6 @@ import _ from 'lodash'; -import { createFiltersFromEvent, onBrushEvent } from '../filters'; import { DefaultEditorController } from '../../../../../vis_default_editor/public'; export class BaseVisType { @@ -60,15 +59,6 @@ export class BaseVisType { showIndexSelection: true, hierarchicalData: false, // we should get rid of this i guess ? }, - events: { - filterBucket: { - defaultAction: createFiltersFromEvent, - }, - brush: { - defaultAction: onBrushEvent, - disabled: true, - }, - }, stage: 'production', feedbackMessage: '', hidden: false, diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/ui/public/agg_types/agg_config.ts index 3f88c540be164..17a8b14b57d02 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/ui/public/agg_types/agg_config.ts @@ -63,7 +63,7 @@ const unknownSchema: Schema = { const getTypeFromRegistry = (type: string): AggType => { // We need to inline require here, since we're having a cyclic dependency // from somewhere inside agg_types back to AggConfig. - const aggTypes = require('../agg_types').aggTypes; + const aggTypes = require('./agg_types').aggTypes; const registeredType = aggTypes.metrics.find((agg: AggType) => agg.name === type) || aggTypes.buckets.find((agg: AggType) => agg.name === type); diff --git a/src/legacy/ui/public/agg_types/agg_types.ts b/src/legacy/ui/public/agg_types/agg_types.ts new file mode 100644 index 0000000000000..1b05f5926ebfc --- /dev/null +++ b/src/legacy/ui/public/agg_types/agg_types.ts @@ -0,0 +1,92 @@ +/* + * 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 { countMetricAgg } from './metrics/count'; +import { avgMetricAgg } from './metrics/avg'; +import { sumMetricAgg } from './metrics/sum'; +import { medianMetricAgg } from './metrics/median'; +import { minMetricAgg } from './metrics/min'; +import { maxMetricAgg } from './metrics/max'; +import { topHitMetricAgg } from './metrics/top_hit'; +import { stdDeviationMetricAgg } from './metrics/std_deviation'; +import { cardinalityMetricAgg } from './metrics/cardinality'; +import { percentilesMetricAgg } from './metrics/percentiles'; +import { geoBoundsMetricAgg } from './metrics/geo_bounds'; +import { geoCentroidMetricAgg } from './metrics/geo_centroid'; +import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; +import { derivativeMetricAgg } from './metrics/derivative'; +import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; +import { movingAvgMetricAgg } from './metrics/moving_avg'; +import { serialDiffMetricAgg } from './metrics/serial_diff'; +import { dateHistogramBucketAgg } from './buckets/date_histogram'; +import { histogramBucketAgg } from './buckets/histogram'; +import { rangeBucketAgg } from './buckets/range'; +import { dateRangeBucketAgg } from './buckets/date_range'; +import { ipRangeBucketAgg } from './buckets/ip_range'; +import { termsBucketAgg } from './buckets/terms'; +import { filterBucketAgg } from './buckets/filter'; +import { filtersBucketAgg } from './buckets/filters'; +import { significantTermsBucketAgg } from './buckets/significant_terms'; +import { geoHashBucketAgg } from './buckets/geo_hash'; +import { geoTileBucketAgg } from './buckets/geo_tile'; +import { bucketSumMetricAgg } from './metrics/bucket_sum'; +import { bucketAvgMetricAgg } from './metrics/bucket_avg'; +import { bucketMinMetricAgg } from './metrics/bucket_min'; +import { bucketMaxMetricAgg } from './metrics/bucket_max'; + +export { AggType } from './agg_type'; + +export const aggTypes = { + metrics: [ + countMetricAgg, + avgMetricAgg, + sumMetricAgg, + medianMetricAgg, + minMetricAgg, + maxMetricAgg, + stdDeviationMetricAgg, + cardinalityMetricAgg, + percentilesMetricAgg, + percentileRanksMetricAgg, + topHitMetricAgg, + derivativeMetricAgg, + cumulativeSumMetricAgg, + movingAvgMetricAgg, + serialDiffMetricAgg, + bucketAvgMetricAgg, + bucketSumMetricAgg, + bucketMinMetricAgg, + bucketMaxMetricAgg, + geoBoundsMetricAgg, + geoCentroidMetricAgg, + ], + buckets: [ + dateHistogramBucketAgg, + histogramBucketAgg, + rangeBucketAgg, + dateRangeBucketAgg, + ipRangeBucketAgg, + termsBucketAgg, + filterBucketAgg, + filtersBucketAgg, + significantTermsBucketAgg, + geoHashBucketAgg, + geoTileBucketAgg, + ], +}; diff --git a/src/legacy/ui/public/agg_types/index.ts b/src/legacy/ui/public/agg_types/index.ts index ca7c2f82023c9..cf2733b9a9f36 100644 --- a/src/legacy/ui/public/agg_types/index.ts +++ b/src/legacy/ui/public/agg_types/index.ts @@ -17,80 +17,7 @@ * under the License. */ -import { countMetricAgg } from './metrics/count'; -import { avgMetricAgg } from './metrics/avg'; -import { sumMetricAgg } from './metrics/sum'; -import { medianMetricAgg } from './metrics/median'; -import { minMetricAgg } from './metrics/min'; -import { maxMetricAgg } from './metrics/max'; -import { topHitMetricAgg } from './metrics/top_hit'; -import { stdDeviationMetricAgg } from './metrics/std_deviation'; -import { cardinalityMetricAgg } from './metrics/cardinality'; -import { percentilesMetricAgg } from './metrics/percentiles'; -import { geoBoundsMetricAgg } from './metrics/geo_bounds'; -import { geoCentroidMetricAgg } from './metrics/geo_centroid'; -import { percentileRanksMetricAgg } from './metrics/percentile_ranks'; -import { derivativeMetricAgg } from './metrics/derivative'; -import { cumulativeSumMetricAgg } from './metrics/cumulative_sum'; -import { movingAvgMetricAgg } from './metrics/moving_avg'; -import { serialDiffMetricAgg } from './metrics/serial_diff'; -import { dateHistogramBucketAgg, setBounds } from './buckets/date_histogram'; -import { histogramBucketAgg } from './buckets/histogram'; -import { rangeBucketAgg } from './buckets/range'; -import { dateRangeBucketAgg } from './buckets/date_range'; -import { ipRangeBucketAgg } from './buckets/ip_range'; -import { termsBucketAgg, termsAggFilter } from './buckets/terms'; -import { filterBucketAgg } from './buckets/filter'; -import { filtersBucketAgg } from './buckets/filters'; -import { significantTermsBucketAgg } from './buckets/significant_terms'; -import { geoHashBucketAgg } from './buckets/geo_hash'; -import { geoTileBucketAgg } from './buckets/geo_tile'; -import { bucketSumMetricAgg } from './metrics/bucket_sum'; -import { bucketAvgMetricAgg } from './metrics/bucket_avg'; -import { bucketMinMetricAgg } from './metrics/bucket_min'; -import { bucketMaxMetricAgg } from './metrics/bucket_max'; - -export { AggType } from './agg_type'; - -export const aggTypes = { - metrics: [ - countMetricAgg, - avgMetricAgg, - sumMetricAgg, - medianMetricAgg, - minMetricAgg, - maxMetricAgg, - stdDeviationMetricAgg, - cardinalityMetricAgg, - percentilesMetricAgg, - percentileRanksMetricAgg, - topHitMetricAgg, - derivativeMetricAgg, - cumulativeSumMetricAgg, - movingAvgMetricAgg, - serialDiffMetricAgg, - bucketAvgMetricAgg, - bucketSumMetricAgg, - bucketMinMetricAgg, - bucketMaxMetricAgg, - geoBoundsMetricAgg, - geoCentroidMetricAgg, - ], - buckets: [ - dateHistogramBucketAgg, - histogramBucketAgg, - rangeBucketAgg, - dateRangeBucketAgg, - ipRangeBucketAgg, - termsBucketAgg, - filterBucketAgg, - filtersBucketAgg, - significantTermsBucketAgg, - geoHashBucketAgg, - geoTileBucketAgg, - ], -}; - +export { aggTypes } from './agg_types'; export { AggParam } from './agg_params'; export { AggConfig } from './agg_config'; export { AggConfigs } from './agg_configs'; @@ -99,5 +26,6 @@ export { FieldParamType } from './param_types'; export { BUCKET_TYPES } from './buckets/bucket_agg_types'; export { METRIC_TYPES } from './metrics/metric_agg_types'; export { ISchemas, Schema, Schemas } from './schemas'; - -export { setBounds, termsAggFilter }; +export { AggType } from './agg_type'; +export { setBounds } from './buckets/date_histogram'; +export { termsAggFilter } from './buckets/terms'; diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts index 4ce5bb29f8ff6..d01e059c6c616 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { AggConfig } from '../agg_config'; import { SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; import { BaseParamType } from './base'; -import { toastNotifications } from '../../notify'; +import { npStart } from '../../new_platform'; import { propFilter } from '../filter'; import { Field, IFieldList } from '../../../../../plugins/data/public'; import { isNestedField } from '../../../../../plugins/data/public'; @@ -89,7 +89,7 @@ export class FieldParamType extends BaseParamType { (f: any) => f.name === fieldName ); if (!validField) { - toastNotifications.addDanger( + npStart.core.notifications.toasts.addDanger( i18n.translate( 'common.ui.aggTypes.paramTypes.field.invalidSavedFieldParameterErrorMessage', { diff --git a/src/legacy/ui/public/agg_types/utils.ts b/src/legacy/ui/public/agg_types/utils.ts index fd405d49625ed..e382f821b31a9 100644 --- a/src/legacy/ui/public/agg_types/utils.ts +++ b/src/legacy/ui/public/agg_types/utils.ts @@ -17,7 +17,7 @@ * under the License. */ -import { isValidEsInterval } from '../../../core_plugins/data/public'; +import { isValidEsInterval } from '../../../core_plugins/data/common/parse_es_interval/is_valid_es_interval'; import { leastCommonInterval } from '../vis/lib/least_common_interval'; /** diff --git a/src/legacy/ui/public/vis/lib/least_common_interval.ts b/src/legacy/ui/public/vis/lib/least_common_interval.ts index 244bc1d0111e3..72426855f70af 100644 --- a/src/legacy/ui/public/vis/lib/least_common_interval.ts +++ b/src/legacy/ui/public/vis/lib/least_common_interval.ts @@ -19,7 +19,7 @@ import dateMath from '@elastic/datemath'; import { leastCommonMultiple } from './least_common_multiple'; -import { parseEsInterval } from '../../../../core_plugins/data/public'; +import { parseEsInterval } from '../../../../core_plugins/data/common/parse_es_interval/parse_es_interval'; /** * Finds the lowest common interval between two given ES date histogram intervals diff --git a/src/plugins/embeddable/public/bootstrap.ts b/src/plugins/embeddable/public/bootstrap.ts index 801329a4a79af..1e0e7dfdb0933 100644 --- a/src/plugins/embeddable/public/bootstrap.ts +++ b/src/plugins/embeddable/public/bootstrap.ts @@ -23,6 +23,8 @@ import { APPLY_FILTER_TRIGGER, createFilterAction, PANEL_BADGE_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, } from './lib'; /** @@ -50,11 +52,25 @@ export const bootstrap = (uiActions: IUiActionsSetup) => { description: 'Actions appear in title bar when an embeddable loads in a panel', actionIds: [], }; + const selectRangeTrigger = { + id: SELECT_RANGE_TRIGGER, + title: 'Select range', + description: 'Applies a range filter', + actionIds: [], + }; + const valueClickTrigger = { + id: VALUE_CLICK_TRIGGER, + title: 'Value clicked', + description: 'Value was clicked', + actionIds: [], + }; const actionApplyFilter = createFilterAction(); uiActions.registerTrigger(triggerContext); uiActions.registerTrigger(triggerFilter); uiActions.registerAction(actionApplyFilter); uiActions.registerTrigger(triggerBadge); + uiActions.registerTrigger(selectRangeTrigger); + uiActions.registerTrigger(valueClickTrigger); // uiActions.attachAction(triggerFilter.id, actionApplyFilter.id); }; diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 583b21ddfa433..ec71a1e724c7d 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -25,6 +25,8 @@ export { APPLY_FILTER_ACTION, APPLY_FILTER_TRIGGER, PANEL_BADGE_TRIGGER, + SELECT_RANGE_TRIGGER, + VALUE_CLICK_TRIGGER, Adapters, AddPanelAction, CONTEXT_MENU_TRIGGER, diff --git a/src/plugins/embeddable/public/lib/triggers/index.ts b/src/plugins/embeddable/public/lib/triggers/index.ts index ffa7f6d0c0f44..72565b3f527ad 100644 --- a/src/plugins/embeddable/public/lib/triggers/index.ts +++ b/src/plugins/embeddable/public/lib/triggers/index.ts @@ -19,4 +19,6 @@ export const CONTEXT_MENU_TRIGGER = 'CONTEXT_MENU_TRIGGER'; export const APPLY_FILTER_TRIGGER = 'FILTER_TRIGGER'; +export const SELECT_RANGE_TRIGGER = 'SELECT_RANGE_TRIGGER'; +export const VALUE_CLICK_TRIGGER = 'VALUE_CLICK_TRIGGER'; export const PANEL_BADGE_TRIGGER = 'PANEL_BADGE_TRIGGER'; diff --git a/src/plugins/expressions/common/expression_types/kibana_datatable.ts b/src/plugins/expressions/common/expression_types/kibana_datatable.ts index c360a2be8c7f7..38227d2ed6207 100644 --- a/src/plugins/expressions/common/expression_types/kibana_datatable.ts +++ b/src/plugins/expressions/common/expression_types/kibana_datatable.ts @@ -23,9 +23,16 @@ import { Datatable, PointSeries } from '.'; const name = 'kibana_datatable'; +export interface KibanaDatatableColumnMeta { + type: string; + indexPatternId?: string; + aggConfigParams?: Record; +} + export interface KibanaDatatableColumn { id: string; name: string; + meta?: KibanaDatatableColumnMeta; formatHint?: SerializedFieldFormat; } diff --git a/test/interpreter_functional/screenshots/baseline/combined_test.png b/test/interpreter_functional/screenshots/baseline/combined_test.png index 87f3173d5602472d092da015c8e58f354f0f9bb3..56c055b8b1cff85d7dd891750efdcf6aa198e937 100644 GIT binary patch literal 16994 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 literal 22801 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 W-FKg^bGCg43Sv)JKbLh*2~7aN3?W$n diff --git a/test/interpreter_functional/screenshots/baseline/final_screenshot_test.png b/test/interpreter_functional/screenshots/baseline/final_screenshot_test.png index d860bb73521ceaf40d4b03e271b068d5fb18fbb7..753ab2c2c6e9492950600b52b61a8241c79ddc53 100644 GIT binary patch literal 17033 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 literal 22852 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 Om~LLP0HLmWB>fNO{2NjL literal 32900 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 B)?oku diff --git a/test/interpreter_functional/screenshots/baseline/metric_invalid_data.png b/test/interpreter_functional/screenshots/baseline/metric_invalid_data.png index 795f2f7c832f335a1a1d78bfd9c820e1dca976a5..e0cffd065fc4ad31a2a7f09d7f58de66722a83c2 100644 GIT binary patch literal 1806 zcmeAS@N?(olHy`uVBq!ia0y~yVCiCDVASDY1B$H4iCDcuu~YQf??lv5P84^L>lmdNFQbpc^O2E3Xg`#Xo48c4WmT?s5Bfc9tTm~ aaE4jCpDjfCnrb^JYCK*2T-G@yGywpYYsB;b literal 1920 zcmeAS@N?(olHy`uVBq!ia0y~yV41|gz^KE)1{9egI&&`r1G~GYi(^OyVv%M>FVdQ&MBb@0DHC3!vFvP diff --git a/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png b/test/interpreter_functional/screenshots/baseline/metric_percentage_mode.png index 580889bb7deaf3f06eaa5dcf06737370555241f9..14457f0a4d0ab750bd49fb09b1c1e946b3611365 100644 GIT binary patch literal 23705 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�s>~J=!KGVnmdPDTOlUHK&yu5!SW~9cP_oueCw9egC!j5aat=qDJ_Qh z)K@qVY1wruR1i|1uHNgVzL})#9WNC`7i6y&Og{Qf_5el|O30ufZvgsVS=}QBADC_l zK7*XsOTSz0UTWbB?y}DkmD!9rhP8flU6diFuZ|bGb349`g_@3rk#clx8qx7%31Shf zKU1s)8wo+x>|dU~$}wa;vCQ~xYm)nU0q#p==S*i-fxDN_lG#gC(#6p=MVufLhj5IT(&wC>CnZlzb0@e@;+pT2;n%u1QWS*1Iw*)h{tq~HeUCluyp?`0B_y4BO4=S z`xQ06Tih@EtY+wC-oE1I)en<9#RfIuwH{kLDsX=H6YORA z2pPk0-30dN4k~cBK0sFB`yCVSo+ohyh>gX_UI*&V{B&!jGV6oJVyJ*GTzOH$V_BY@z`wXgwxgt1TXvo$1715X;;@y47 zNP#}C75ogT)0-yjEu%XKIN#w`*KqFJ-=r~`jQ)NCEm@rpdmq1IlF3aOma5oitx!Nw^k-Qja=k2y)J?k1JzX2NR)wy1gn5?u{fINO%b{Et$fl0@P%%=vNJV?gx1Eiruz+ZVm} z=6-}{JpP|PjRP%~j?=JUCnOA7UVbpNx!CpsQU2CA#1yrq0NC&m#o6tYxApK97}N-e zy$HBE=eIphE9uO4Xm4Xeu1r{YEvTZJLaq1CaVfm?-A$-PvaxzrbK#wVllD}+mc~dx zpO@@7wTi(xPUVh71Yt8;pKc4*IemRj#u=-5c77hOCF_D>Rr=!BVMmjd-h`01fRy&X`Rj>a?_{N9JnA9T zWG>D~>#t9|dnwN^_~ct%@#N)^L!FaoR=8E8N;k8B5%Mi{g&Y&PRPBx{Ko0lz8Vg}C zUkgl-Elhw8S9sZ<5eZBUnse~Aw6tJY6&PKc_D)i`qFJ5YTxP?JvQ12LtA<;o@>ZBkm=^WFNOZ?_gML$&}8SttOx zWl>GS4cYfAMz$$3#hCgd43W&i%rOe0a{`c@n41crAQHho5r> z%tPADn&_^a*ZFjtP+7~VuC9(MfCc#%PL|V6yrI#r0NNZXm@5zBU)rfwOc+*B%I?4e z2PAp6^4oN}4Mm={P|$1S;$!YT3o>|yFoYgocB zaik#ugLsH8q13Bnp3F&>zoEN@KB_LiEwZu9*lEIMXe>1E@Mi-pefchc<*R@4=lIOK zbMM2p-hO}ZK~fA{3t6m@AObI)IU&>482O$A#RhGLt?q)5GLVWhnOm;6;LRg6cPD^S zQTqoL2v`!8PL-nf{RSK4uEetjQGb@aG1`QUl ze16zAgb9OfQNfg}To=bQ;YmPNntuBJNZdJa&QG>w`H>)hf!YoS(HN(%vC8yQyX}|L zx~*Bh2DN^f6*eOR9|75qBTK3;d4Ob;xS-vD?hjcK_?=={zpdK7PUQBMH8a|{e7;Cv zX70Gi6P+!lqIc}=##bzK*y71$^pmz62r+y)P787fxvqG&&ih##V+l#dK7lX`Jdi_E zDA7Q7vqspv%xjyI4i057LZP4I& zws4olR+X}Xq0=O@k#O}=9WI!1`@XzjGxM%~P^9w5eo#Tq&(B|jNMki$ z?pX`vT<%GUK_K)kuxGqwBB*S&c$qkRQg|7V^NMy{(O&)s?|VHiiw0p!2P=myWCtrf zN}yP^ic7d}y?B$0uicf+%TOH4o6Rus5N7mU+V@Z71wsR07W$>4j`45VSXpB; zF-NQ;S?tnVVk4K9cA_|!yAsm3=VQtTX~mr*E55&4cnbRIQ>|6-H%SzWCk9$5uj4o?6t41ey_3Em|DQq3+OH^YhpPOMCz1q@CvaGBrFRO+4*VO9t&IZYyn z0$jN`wPxDIm@D22 z+DN^|@6x#+7KV@mT8r)symZ&3JyC_UlYoST6pCHr>}jes{xgb|l{H=qg)cxgQpx_< zDsRBV#1xQW(N?B_3QqsF;o%@akFBo8$E;_HX@DMRKB}4NRpvM2oWae{Ppqx1tjsDY z>3@SxCvm*29`4DW7 zagoqCLjzciGU%b5cAmb6W{U5oc;LkKrfiF}K>UFQM^Ny_jZlR=&6y7YK(TYCUoqR< z{0stEktNtqF`M3X?epVhYrpdqK1d<{)Dr;m#WaB!Vz40BtKb~3ndg_|R`*qYdm7a2 zXn;fQ$vCw>7Jj!&z3d|JyE!MSyC7&L4l(^T0Te!5ZyS5r=EM^90?=HQ5!Yq6-S2N&&U-ca6?D257Vk_!~}sem*=-)X}Ic9OU+J zwL9U(3G%6zZDBHbo{zDeMP?3(*{>+9CoEehtcid6JJy3qVRh~aL1HA@Cm6T%W zJBU?ORHP=pkxf+j&7phCaYuG-qxU|Gy;B$`MiQ*u?!T!@$ikjiZ*FFYSq`2QAD{}r*Xm^5^`YH7XDu%wSaIt}X(uPHX$5L;y zGB!uFLrmH$x5myTQ6bz8^lqnM0LJ-S%t~WZfy@b1lvuWGKY6gweRHKn7${DvX;K*f zg2uI1SBFd)I~~~srpdrc(47F9>e2|Yh$JW#7=b@687h&J)O`Rd+;kr;omh{+b!tTtMs32^cKu3QZO9m)9mOIIi5FA|1 z&#xYS+!oIJH%uh1>8byQAvOiREwhr&X0=8V$~F^|M+Ad6E>ffDiq_G1&PbOXU>eac zQc7aADs4tsjb&t~GeKL9%u$M2vb%pj41PSG6SL=DY*gDm>x->i0E`6BT(7swC2xHZ z0=k3dF4Oz0X|U2J)ov;@Bo30Z@BPL6kIWoUffnt~&@^n1$l1sZsM$z-qt}fG;(n z7snhNh5|ZoDTUuOQ)aKc4uqoj_V*#R_R@=bQPjc~KAU;uUOb#`r>}SMS$*%>D|G4V<#H<-`YN1<8HsCR%@Pg2aYz zFy>4F1Y6k6&CO*jhWc+@^4abWk% zKy#{@nENdm!M>)b?eN?7j5<0cch+6<7g)vcSg^U$2rV5Xaf*F@lTT7;Tulo#M$xuZ zhuL3YRD_9~L5S#-6bi`8G^xy96c326JDK zGk9Eld|-Ko#4!!zcyl5u!@>AZH9Stzo##evO>KvdlZlDg3dq9kg(9E&x}t%t7YGwe zL2-9a@3n=|B9Ea>fZ;OOfM5#o@bZc$`?uhTbI$|Tmffo$F#9_NYtX-usQ1k2qyvw7 zJmzR~%%2Y;jBEW7F)DcNB>=n(I7+E|sxGDGRVN933?bvzyW3^(%$QpK_RcfDB_KTu zzB7>>Dloonf{$?J5+hB9$MmU=+wTs22kbJ^Uf><)LiI|6w zSR-CSnlRlUW6CFbC`mO(t0+zNcHS^!Vev~397s__#A^b8TkfKY-Dm`FouXG1FdfPj ze_fCud1Pb)(kcU?Zz^rx>Rfdhu)6&5$fbsGUsycaa0N5E znHTuwfV+fS)B`27rBET8i>=OKuD%Zx(XQ|GT(@YD!pq0BR%aj+5ptmU@oj+|Xr}@u zZwr^Ktnm;EiU^);PlT5DX5T)7Bt6u?BZl$-W$i>y$T3hC=&mU875)6>OE#ZjtzI1a z5j=OdjRVU~VY`qV8!Vajs~8AlYQeifX1K8R0p3uKLV+6mc=|&$kO|q;S{k3;YaIz>IpmDimG+6`M*Abw7(iM*PdfqrC(WWT;d8cq9Q3 zOZjT=+OfuxE)Ydf&bTh%0pYQ$bWU%sy(-V0+Aoz?(?;+)dtuD>k)yw=P_E`&nQnv<$J#a(cjm*%1jYdIbi*S=t}=|mz(bJJxyvQRu{ox z19~~t8gJ{~#WyG%rSA^qK$Z22jq2KGfIi=!JxeK^0WQT?pd8l>;23L>T83B`NRkbp zQ9v?pcHDur2Gh0dnN`=*&ITnj|@9zfzn z^!Y|)cO!S>d9MR`+!UB_xTqK5eL zor8k~JPw>y43%-woP21sH2+pUfjyt?$bmXk9=L3v-#)O0BBwoV#%#k`l6mz%>SE$7 zcN7BR2(D*`H>auxyy?N;lkvwi_3gw=FN27Q*UEDN4HB2z?n{@Vn#NJ86I{Tj#uQ!l zpCF}bn`bInd^okr2RsJ;t=|hdQZu$_)?oUfC2~V>?O z^d{8p(=Q31d$&DbdQz;1izR_+FSa;wQ@1gehmEwkX&!(Jx-(f<&Q#GeFgTV4p}ZiV zLw6qK@9sB%?f-p~PYj0K&cq^U490V)rWM1|LNWs3FGS2oDsK9l!uWZQ*>G;-z0 zv0)fEj({rxTTq!M`W@|1GVlZzU}cuOYlm%z`$tFf4PSC|f7i|(ULEw9(#Fa9%T(Fv z1?g%R8+=hIcgt0(`&qd@RD%%^GdAx4rc2C5Hu)g^7jP6}X93Ry6Uzp@%lvG$@5RXX zw`cnu+0a&COjGhZ)A}Fq>JUYkE)0F?$sNO3>7EC}0W2ufP4J)0X64o@)a$W|Xn0aq z61Am0RH%SWi<*xCkL=%_z(E*{dr3;?n}xcS?Pzb8nAvKfCw%{;R`VTr(gTKJ8IKfD z8t|1sdbW3um|i_{oc*;tu+~v?NCB8KQ{Bb!MgSu3|Mit2K4Ru1K}M7zEI_>vkwo#X z=HROuKbB+wXA_Kg>&|GSFT9p)Y({a~iNUOrV$sj2^8^4#0KH-YZ5!a-TiM zWiUbjD5i1=U;vEB0w*Sal)-rv9uu?Hn_3P)UxE?CKxTA~%=wW^@5Rb*;@)pmXAjR0 z*H}>PHtHI?erbJhDOWp7%5#143q~|Bi|~A^2se@v6I)UD2b{SMOL~kDh-|c#)=Q74 zCqC}y0MZNuWm+rWaSB8P%2?CTxNJ~ht%Inh!%Qg=L64I-QFzoMw?x$O*-c55aZ#h; z`;F-=;I}a$?b|EDC_@4yzrC(u9x0GjRlM*z6zQETv&KhStclVnmw-W!CF38ANJhXa zyrgH6jxOF0(oSmt?-Gbxl)(ShtawKZoX^1ClTkz*a4cz+z_bnHA|7z5Vkdw!V?G$D zYf-T^Rs!al=%z*!CLX^nZ0vc4#iZLZ3*)w^@;SfKsW2g@+POI15;Ft$gkn!s6}RY$ z*c&K7%5}KxuD!VXU*(%&8u?n@LoOHm;N3eoko%X>myhX zd@4|c_WIH(tir+APwL6eM7W^qJBIFY{*ncP`dLI8Fzy+divfHV*DA>}Oy%=?KxGeD zy61lIom_2j1JrQUlV$(?g99_iR&oJ7rrSUiJs12N{!Ugo1Y9|`g_Ac`iAP3i0ne(C z@Djbt3$V~}n$qA%tDTsi637u9$3rMr(m9Vs)T764WWBo3!j~UQm1wF3w_!QD=ZF zlNDxR7-9+*k#pqxrSsm-R3)=d%i|2o!7E+329PInQ`*c-PorPH_xX-b_37E=s628u zvS4SnG)%A3CZ5>l&o{+oWFO*n>1PG-1R6Pt!U99m|2CE_a6#1;eP>gZkXEe&kXxTCp`H5jY6 z)=0S8DZT3JxTjxDtj&iP1j^9w+&D6;gQFwXb2kC(hRcA8aoQU;*ad_2qGEgUM4jCHX}>pefXp%gzHFw!aVbOVHh( zHU(mi)0L)p{}aU1BNtJ|E?_3ldIW5+fZUT{>q{mW!UA z-r_B4i$OtSDe^kq4guwP!m!j#0riO+Ox!X_W)iC(I$XV(*LNHvsiRR6D$$t-J`Ooo zBNA3O9Rc2ta=CHr^{W2@kICA~v3E@<8)sxcRk2*I;Icmc2_LLDauo;wH`iVVPAlOb z9Vf%ffAM>X0Yfwo=-w7WEqv=|Z}h>|hnbc-%}?z*g)l4N2)bOzE7r+GrUk?>GpDx1 z+l0b8Z-4M^>a+in4l5sA?%+QxE%)~+`?H1U?OMn_coz^JQBZCZNr~((3 zNCl-!M`rWIaqCx`Ypq4^P#!EkK?$zPEg>-wpm9y3hDg~X8595|h&uw}NGH%yiNNR& zic}5(9)McbG8Y&TqLejd)~|BtDD0=3;$LnHKuv_ih2S&vfe@@=QO}Y>xsfQ5Bo6q* zCdVmTs55EMT#B=)upbTt^$)59ybmB%1tk@<@D+HLxBt56CIX^7R4J5z!SZ;MugYPn zDT=1_DibtbxFgClXbL=kw7ffj(#QaFSMGOO?&K$+ zlj&}YNP$JwsoY$hKYY`fU1s@QvuLsf2D4!O|L{L9P@~a7!42fi>y9MnvQj=9f<9ar z`eTf>KTF*nj=Dz^lTo#2hv*mCMFbhfi8!;w`(k40o;wD2kC{w{CftAcq>7Bvf^FHC zW@+n=Xcfu02YOAAQkktBA9Fl;*j=pPy}52_^4`KnERQnM?Kt^5yTA2`=w03+PPpge zi4jmzXrblpFVE_IATQ6FclfaBO1I=N?_+w(grj~-pnFJ}$!FPGw-Q#-iMe(0mdIO1C1sNZ@;D^Ybuq_o+cPfyr@H!y~2Wr zF|Ir>y3}NqHcfcEJAs8)L}%;H(RBj2?bq8lfVDaCN) z0t^4(XUe}B=PQ$J!0DrUdnb2|u6G(3}YMD+B2ONGOcp;5+4&eevP z8>jm(1YaaF$){$i{j{f4Ml#f&^G)!#V?l)eSt|u)-GvqM-nKxzE-#jgx+DkDS+~(> zq-UUrdG$fw{(^`cPGg&Igtdsro!ixgo`XKq#KQ%KO)TB~lY{3F*Q2#)fUY0hV}Ii%n_xm#~HTE2)D?S9_IO)o3NUjjq67nhJm)nxsB^fbNV2aa2jU(fDM^|Ly6M7Xr9 z8_lHnzYu!~s}xO%KTk2>stP8ZZ)q@;`T>Uokvl0NNrcCbS1gcj7Uy?=s!w#&v2D3o z?c~Z#mm;c@tdADzLfsEG!_BApZR?^M<*t;PtqVtIryJl z>^88XKFY}HI!nl5`uyS4XVP=*Y=P}EZdOtEuvwLvDs$!e?B^m>?Pqc?)@y%>Y1DEg z*zaUKndkJ@6i zDAaJ+#wJPZhPqh1xyM0@*5D;?Pv4UbY4{U`>HWDT=hd;u#{s(DYzH4<^euhwu<#0K zN81YTZ@caR)ftBzHHVvxlc(rg1eW_eGL&iR-4D^bX=vO_~ z=s{lPkg?}pe|8%2Z}MODAwR0`w#HA`F^Qc|b9_|LS&&~U4dudD3dZ<5#o|vpj0h*b)Kab#&-S#pSw~h@{qE!N zr)1}0!dx2HGMM;M@t#WcsXUS+4Av=q2=QZ@&E9Fd^q?LrXtn2g4`s|yJtPi#FZogC zQ1g{W`>l#t=W5#{xWVCz1r5Tc86#C}Es8~G&3!ja1d@>3!-pV}2Sd_CsMu`gKyDiw zFPxsS*wvN3lJ41+k3ZVj8_vPK`(@e1te4p#8{<0*KW(M2JdD|!crY~W!_qH?qz-EF zwp$H$n%-97y018htXi2IAPmHCY3|V%Ha__FIQOe>tUWe*uXp3~qtlKqKR0QlvOvJ5 z>J5SkXZ8wP4=?DEN=s|zY59-l4%GoHAKo>S@7z$$?Z>R+u0+G(;y=zay&ld!nNC`2 zHFUuKPB|ihrhspx+ElFtJ(*_Q(!-ue&2FYke}g8pSIpnL%25QDIyB6vgqXbFZqK@x zH(O$^qD#Lmx~Iz4FWm5e{+MWbr|rFoD-(T6x`{TL=22u!aL={W{(+W=H>73c>46I_ z^rsQiJG+htHb%i~MG1xDP@;j12e6}gy;mXS7H|_y@v&p!a`8&I$(Vx)YF++6EE7G?Mr2-A@TaQJT zCf>uyY6%xTg)k|blafNcpRorVi%05#Kp`#JwX}4gHn_iJ^eWJ$>2aV^m4F#hUP0s^ z@wU6}iRK3-Jf>k&3NE2fy_m;FC~9;sB7z=Br^zk*NEXLS7?dz{c(7n=nukTGHxon_ zvu`(kS;FTs(1dNlo49D8#f4HKd8@yn78oLoKWZYAJb7lNA$&dU496<2^y-O@pU-Pl zKWi*jNpF4LIQ_BHf_6x&1-8!Mm7n{wGzGOgx%gNKk%hB`2*jd9+M z+JQ5Na_XKMiB8Gj@eP;;D--r(S{7zpQrPvVH!Y{`E^-m*MGNz{VdbTbl`oITZC~O` zX@t{?Ttc1t2A?ECae+v1+2cEwaFNH`Tb7q(bkK zbbAIY_}UM-TUP+^Us4zHZ@$kHX$lo&I8}>um`ZYemepf1Y<3@v>k+e|9Tp zYN|RZiL>8sk|ZMxh1@A~Ihnh2s<@0h4l(ob%6_i*GU=RFCWFSGoHd z|6CSeF$QmhwT}2Xk%)D*Q{dHyiFRB4*efFci&pcSCs|gtTA5kLQz@6%j6*@TPpSIj zO#@%9z$HY<7BAn!AT5m2_dhqt%Ab@M>{-%yFfjKgB`H}9*-1p(wPD>ANqT))nBLWa z2bq9TwTd?+c1;)s>Px|?Q`R*2=7I+;P1ZuT^G1j5z?M99ntPlqT->2;GVcpRw6Dzw zWzsAXp3Kk+>MpxJv(w65>>D)hO#9rauyld3XNNshKCdrHSg^v-Xxu%h-d3zI zHWi~S$zyHYvjfxLV?#wAc}=SnkE!^T?K_BGq~jTA7QyYJRRitGdvu1T9k#v{LeEoq%g}pCXc)Fg>k2}CJSe;T4quDq2NCfU}KeS4;dO_OH`tam}%C^jkS0U!B zNttY7PgQW2{G@NBjWnTPPqm3vYikCMgaj(OaL!I|3h%`2)_wy(c^wiyjJ*ui=ml{ieAng4aw zd-S+JxMhP8!?j{Gm`ggOns%*yk7pahr>Q6}2~2VRrv>UDv4TJa`1&X&Mf1S$-i1%3 z-pNxq$Lu?n+qeg+Rez}7`{?wK?JNEjb;P=u!E0v;K|hHBbJoecZIblvR@}0ReI+ykKf?MH)?f97iJ9~PR*c^XQ3!Qx~2eFkmW6Eb0qVch2 z80aZat|ez8apX;`T-oa`-T62Be0|5W4Vu3F1trR9nNLqUGlpQaL_YS|VpWht_gKv( zvKnXZwi%2NIZiQQCq6fv^9Zdq0W;}%;Q zuBUx_kA3;~&$gCtUnD2t8E7x4y(zb%p3rQlw2B*#clX}is~hfz$@r{==w5ybaET{N zxL#18+_E}!rIjh8kz@(qO#5-iTOT`&MogX$y|04+WHRDt+S%=={lBY?EIhqg=M$k% zY%JEbsocL`g)uAWLGt`x_^5PJciM0FRc^lJ(%MBm$&5vE@Z>Tb@np@fQYLFES!@bJzSN{LJerH>m;TWw{ZBKXKdMks* z&!6ImFS%4n@MHnE{Qhz1f4rdVPv%8A_`b+41=yMz43wfd^ns&eG5NZExwM|;I_aE;UVjFShE}n|FQD>$4aFb_B%oFgI}0V=R|D< zH0yOzsu_LV-$vc_8?z!Z_oZB?_KS=^TJP37*u_u~|Hs~K5+1htU}wnm>L95i1#N$l zgN4xZjXWN>|D@jbBdF)(<4zlV({~r+yewZHq>vYf5!D@6` zwZ6Aie$i{db@(DHrYfTGmd@;_x+Aav5N0{Es zbJLZk#kF4OnuED_{`0CS46R4iYhn5aa#(}W63O-lE{~pP^tHRzo*bKa7Y3%k-JR`U zT@&E@zg-wYwP&mKHT+OuT-fd6hsVEk(M^`^10!u3`w_Y9P1H{(HZ`tm$^G&U7k z-EVB2bAkLP52et=&b(;vM%8&_bDLXQ--q6RBt(O)QvLKuj^S(IXOeo=kCMXRKd_~E zCE>1sAz}eF#xfAGkSt_kXAO^=f>u(yel~Fw>MYzkz zMqA_XYq!8hrS+RrYJ~XEa!t}Tn-*pWU;io$FmuOcpZcRJapKtgeqoVMYzUF*{|kLE z2mb->%8T5+O8gN1iJGLST2eYnw8H7fm^IkNA^b(&RbUgq0V_9&X6nX;UkXJTRtS+G4ax)7EKou#uw|#d}wnocC)~ICJPI_9!pg)JHNSF zgkrc+81Y+2@hMAn8NO4V+D?Im%F=zsmsuh~461=@4)FiOvsv}i%idun{V&G?9j>e0 zSD$Q)*JHrd_l2_$aDT1*M60=u!|FfQw1a05gd1h~K**VM3e8yi$c*xbbHM}T=okNR zH0x>R1<8MhsEYqXd`i~wkydTp%ZR!{OL-GbD&Zbn&(A%Knf}9bEq$Mj-wSq&2xEvf zrBI=jF*rLZ#SiUOVp8(=c1wx29Uk6ut29kdyOY5?^ES>VztvJa#(I=^!v?~5FSH(mj!A&PB*6#dH=Bw)(X*Nnim*a2>7!-Win&S z9p00&Wa5CAmVdZ-m&161j#`CO^LUVqS)7U?Ao<&zLHiO>_DbzGRa;pBy z-Z>@A$PwzctZ=@L3BE4#3 zM$Yo#jtMOp{LIYc?KOGhq)A_tSJRXnM)&RtPhu)`4+*MoTSC{b8#i3ioIbw3p1L=h zt^f-VrNBcr);jPEL>z1vb1*40F%9Hkjmi~2&v z=^}dg75x1(wynkz8iW%@-+&x=-FUprL3vou*F`kUL4HiO%*xOJ$1?nF|tLtyG zhySWf*Z-)_$b8Cy%Y48gxS#6GsA}~Q`Z+IM+^HfRq0BHPQfS5HzT&2uY3hJ*i=Qos zEH5smV<#gL?F-6ZT_-YEkev(8_u&C`YIvux$bJEfbE?YY1JmMHPd?!+Do94>eV-x@ zgl0p-y?h5=a|IduRY=vea3iCze!7b9*9>B<43TfX=D72Wncl3EN+$>8b}om!Yw0VV z`jRv7fuO$Fusf!ass&HfcaT11sY{owdc@(-x{+)1{C*Em*vddB$dFudr9WE`*Vkja z%GgfyUpWeJ_6X-M789_-El-5Nv2aUa>;i(>iqwP6&qmAp>%BhzEB(!Qc2|~r^P3}- z?x4CCRJI}0OtL>dRVs17P0$Y`hO*PX>D10c(9O^qA~_bI*>CrxuMdXcY{;870G!~7 z98HyGPC@$CVUklyoydv1D_71sW02GygM(k@9YDdRwhx;z5z5AvpF=bJJSm`DWldKF zr&ov3n!aG#qRulqAA3{;%(47ios=S?=0%<6!1J%|FiW+}2)4qEW2G9H+jHoHKW3EZ z4z!75YTs{+4~IKNeP{5+$%yMt5hd{ct9M=oZq$ zm?%ehHm8LS`6VEHG>Ma2^10(I2&LO*j4+bWLP?4E+Ejyp^SjpO)5R4}VGzGJybyT$ zSi!G6>yxno>(-ujdg-cfMGaI8!M!1Kgr`-GOyn&MRqhUY?!}UExkLTra1eyZ&Bi(S z2+QU`%aU%nuRpiZEA9^xjvBnBF>A4>%ws%8nGiIpQObM30=vDWiteyQ0h08ydIdZHJS?T>^6H!kg{d> zd0j2LU|PtcrM;fBQ5hW3E7HQ;8{&OPp%wa}P~NMs3xRchWaYNGjz3(laS!QH zoAeHgyW{^#Rui|_r#^aZIrb0rTMmUPn8%=KAyvp5HnNB+z9lO-A->%(cj>u8!Gkbd z7Ubx0$%1u^k(;uuu$4e|b8O2Io`Z}V%Y&QvfZ*7PUKFiM;q}+N1C%)VwlPzlYPy)n zC5gG)I1UiVT~GUYoyL=|j>YGZili$m3GbKnonrmNCii2(E$gUx9 z>J(J`qsI;br4)yoNoZ%kUJ=7S2@vTYDeO5`KajLjg&=NnhsVDUyZiA{Ye}F1xn60w zIM{bIHpgb?k(c1~-J8UcT|-b*sO?&M!o(xrAQF&i%xEw=)~h^qC+JXt(Z;*$UqA5< zC_iv{g$ok>dbmZ4J`3P1rit6p2yZFI_F{mL92l4gCPn%0D!`SK?hshr3L$*aagIV( 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 b&~h=8fy36? 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 diff --git a/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png b/test/interpreter_functional/screenshots/baseline/metric_single_metric_data.png index 916a284433874aa33d618c30dd8751b32819e457..c4fc4d3979152affbe4386deba159c20c9e5c5a6 100644 GIT binary patch literal 22004 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 F@i*ZEpSJ)2 literal 29643 zcmeFZbyQY+yDq#;P!yyarMnf7ln|svN~8n?RJt3a8$_g}RHPfEOS%N4q`Nz%A-#?qN#$fq8bIxDgaoyK-Py8RtN!+|nbp4M%{t&?-tubK1nkp$wIqMf{~{EnxUgcQx6XEmh8 zlRd{3Hm0+!Q#|$3A9-IVeRv;dNyfYF{^z*R?z=G^gHykTqYXz^^=rD0LpSDnlG~57 z2WE-}jGP?K*9VLqH=YZN{q-};`qc05ANy}2e}CL?36J9MAF{k#n1B6jz48A0Vm}n7 zzb_W!e@_1Q#nTti{=OI!ILpbk^t; zxgN(7CxdQxcK8#IVPLs zh9|UKKGD%Hb#*^qLq``Eqk8LghvG>v0|Nt({U*Lj?$Ea=?d)$$3=Lu{Dz7vKaxuno zmPM?saoj0bYu?Dl7UvnPUF9+x3h+0ORa_b^yX4|%muewJc~Mr&^+GI{iB8AxP2=$b zT9EB zSYb-K_4=s0U{ysgPckO{hwW`k8h(KoIH7UJ$uqa^41Kf)H!>&Pi>Y$)6t}#*y|?u{li~S)-FlEmS$&emhL%U28Wgl{hYt$AnhKR>fm; zmCtT1nPYU$G^6e4X6)00Gc4PUA>R_@2DooLcK<@U>=Od#=Iq_2u}kd*rMH;QHWgK} zwdD2Vcx{Nb60H_G^Ihv{F1Ak9OT3)Oxp_9pz%)B%H(@xW%9w9LY&KZjF>FJuR$)qc zzPEp2V5_U*OxXD}Fb#cfZqB*rgjgjVl&cYgh{hFF1bB&#++xdw}Ov*%w9m2 zRq~tdR7X8_@e5PZ|EBEiy zZbra`mYb8T&U=Td@87@wk)cm??=|8^{T{Sux5dOVzUAww@$WTU7CfJ3JfUZ0O$-%0 z^oU-hJNubMFDxv4Ano9A9}Um7C9SzRJSN7wW)`34aCyZn4VOr2<>xCUZ!mp4rjxe{ zp`o;#oM>*XtmfPO0~3!K@4o+;-TM|zRH2(*oPU$Ruqz?CV4Z}+@5hf12crM>nU_BY z1YB>DjTW=rvtY93V#)KV+c;i6SWo4k67qRKPz z<2{pW*RR)Ux3#y&Z0^2@bl$zMJeQh1LeI#Y*{b-RK+@( zu&F7T@~5;#oC)W`fE-OeY*JEhArk)gouqc?hUe~y=?sRQgG}06q$-bZnT!;Ri+#xe zKzlHR5$mQ4X84%Y_2dz?S}r~rf>Rd$Om?3R+oj&ol&*hTMmM!_5}h=MwEO@CSsat z6PZQ-nq_w`p#+$Cw^HhDx8bH;Rjp>#zqSnt9X4v^#`pD_g(TF}2>-*>+k5oe3Hi?M zx3#ss{igWjN!u)aFX{`36f*XX9GNP)spUwF$e&3}%E}6Qz@|4JJ6Ig7K@gWCa)!SK z+p1H4JEa^V&Re(f=Hj)A%?LfUp;5c@N5&&5yj2H1nOSNT7cj#zpkA1!1*`HUBb2>! zFx#7q8010SZj`oO|MD-Tl6ywno8PTir=)weU4xY~2NcJCW{rL0?5gs*oN$PS7Zx8c zdMEa5+bHW%%BM>OTj)O7%8q?_xF8A#8VfZ|BL8_^`|SKYCY{y?uJDNvgC~T1cI#8&U6Gh{ z)u*O6)l1eI@NL&e>tTy2A3Gn95fz(`_+Jj$ttb5MU`Ot5ywEA&ygkHmu@Q+{E5-3j zQ4?;gQ)R7+VQA9sy^ZJZz!g&}H#yv2EbqoBDJeZB+;GeD8Xe`o*s5MQAu84|0p--m zCU2Lqc#?v@C+H>j1+=CFXJFG{x>%;9}aGNTSz|khOJy>~2 z(6O+IC((qA&-_JezEDz3%ag;_)WUM>)vsOK8g-j^{6vvB+d*rL@9B& zy2D!LF!{$9N0+_BsMRsMHxIbnYIQj{3)6z!x%X1_d~N$eYy3Xm;jy5ITH~`VJgAr7 zRA~xJf^Z!*%5VOM^3o61rSICM1ZA7E3w-QhZ*PUxctbED24i5Ajg{2@a#w14KK5MB z@(Beyy8;ewJOw9bpuCWJQ?2Ndz0vTlnswP{&LX3My1u&osK^L5rkf(;dL0wf?V7MyLvui2SKiAlT6?{Nyb2_r z19sdt%xCXzXcFrMoP%uFQf_7Ey1+485p1l_jq01&%^~{iPMyw`HlpW74$m9dJ|`s| zB&9@svFwjfW!p`t{qAZ=CM|eGVph4=MB=!&DlTJvHzkC$U3V|;(0;||Y+(DFpN^P> z#FhkP^0^Gg+~jV9KHDRiz5W>cIjRNtd{?q+cP}C1bb#* zwRkVRFeOWP9rH8JZlO~AqvL6?T^@iCurDq9GkRVdWBJFAZ@Kjq%NkuB{SgW$#cn%V z$fM~UZCQR@?VuLx?B1@N%V{RpmAZob3gEp}F$T&MrCS+t&p( zwh0Th5`)trIb$o@UwW3yK4D zK9_wVb~!ZI`K^VX_|(mjVh5)^{){$>+itaBPc7c5pFewm^YhzuKeV`H2 zPSroOS{hBi1c>2;JF2MaJqBL2=+1n{W)q*)e0Y4kr~m1(YZLJ~ks~egbVr+&O;xC2 zTHn@|Y&Ga%Oa)K4wu^~1XhSq{HtrffjV7|!a~m!(3-q6yd`=_4N9J4ael0cgtzxSD zOnRYV`{6-vvc2Bom}i;Gj|cqsCVgD_PbRDEXL`FL!xFXM`2U|TQL2JyorfUMp`pS# z=aZ!;mF&CgwWESB?<5eJiwcpsZfaF-Eg81Abg_P$2)W%gHK(dA!6MVxXvl_%uNw8> z*H}i8ef^X<+e|;cf36#50#b}|YCE_OM!&4wntCVCzBSS7whoHeT2&ot6wr>mBr(|rC+;jq+bxYhK*WVNvpm)BKW^BRz$Wr9dK12T$N+i~snzAuJdMnTbQEASxLnSE5bRX?A& znQB&}UJu}=u3=NhE-Wt2@AB2C%v-ROpVYTV`QOVOP2G_Bwbtt?M>;YU{4=|U04z&p zZhqckwj{}(LEuzX=Btd6+|%ewX4JIQy33G#DJaf#A=8W2oNIZQj6^?r^KeuM=KY#qz3N;U#jZF#5T`9Rb^*eZ@+$Ul8L1EF8M~U`l-p<q3x9S1Y8!;?aP!?#m{YzB62)&zJ)C?B3Vcq$FN{_1f-gHv*@0bZ1g- zm#zJb-E0_E*4CxCd-r`C0~C|kt&;xWP12|o6oN{?98@{kq?VQv$Wv3_`%++hY$wB0 zM`AzygVmQE7kn)8RY!W(`}Z%N5K)I&%+k>SLm-tI$$Y@*>v}&Zy|mO3F$ewRFI3?@ zbpDXT+4CLRRgf7U40w3oeg`#>e}0k#7A~$o;MZi)8j>a@4Gn>3Dfdzfo;-Oc3C05X zJ9@BEpAmNLTU4>hL?fV6_AMx4yCIN2dQNX%&IT-q=tp{bdg_@L1;jcp{RaMkRO=f; zg-q&;O-RsD+^G5agED5VaS zIZ-o=niU#y$ON9&xKh^pjx}lz)tnp3Xb%0dbWS#kYH#JS#KB$}Fna;n{MPw>%UL?J z8cq!Cc)p@k`HgiY-_v8aroISG)@Idm+p@Ud%Xd8$_ra(@(}wBl_v|RLPnq-BPoC*K znzZ26jP`@nlKVa~^2yVuUbrMO@q+|R?RAOh=O5g;hl`8?S--yOy>7NTm>&!E6!y!# zHNH3Lv$Mdgo=^jcfi{}nn`%*hogEV!JDApXZQM6Kgp{RTmDl=ZI}{`AVXi!vAm~Ft z?KB)%cZpseoZ@g!7WFMg5E1s0FDDQs{=xp@&Ksj;*Mam1NvFN-=Cxd4v@a-QvuaLQ z-SbIQeV$b#$R7YNWNUydGiW>bOcZ>4DA?Fd0&JH2cXgLxMqqF{OWwOm;bWUc;fE@x7m{^3Y~S7BD4 zWPJ6iV@q7v^#Y)(6Yl%8a=Ig2DZhQ>OY*&D;wy(&VB(b3C9CaotK)?j*cd|gtS~Dj z-#Bbqph^@GJ5%lH`7jggadHvNAwBCad{0xZ(e0lgkRx(YQA_JWLOD;h;CWZNVyeS_ z)&8OL(g0LEhrk=XjgPOdae*=*Mb0fa*0eIA2BS2@=O$KKRwBlkM>_f%Ykkyku}KF4 z@xsEbJRUr;-D8puiSg@I_VbT5U6mITa~ZokUT~o={PqfA1Dn={DN+&Ax{g7AGV4(lzrDgl4dn<3MyGoEETO zrND+Y1(8Vt+yoMA6JLg!hU;4t7BS89wk0O2$E%}dzG}H$1{>9v5YjEEbe{|?l^(Iw z!8-5T@GP2gyA`QNk5Jtj=NKB4fCfBxmL%TFqglbG0P%UpqU<_*f7UC8>Yr%}9C8CJ zG62X$oHyoFwZNri0JQNyFjm5c{GBn&HsT{SO|kqHi4P8*sB zBrd7~JV?k;=Z;i0tZYRI6lzlanKROIcF8wd&;|>+KlpbXr_u;wrPgp}Ptox9R>ChC z=>S0gf~?)3dpdLoqR=>`N=o2V@BCRDX?%1YJi*?9gVX94g)DQb1iFxXq+&7+3^75r=kW zX3xI>mCv6`nE`H0D~|UP!mL`hcU*e=O&_*yp808z5EQHI2c1*=JG%rqRVnx>NP^dN zws3K#7pxXc0do7Gw>itUyuO=tDImBW>Da1ya_q6ea}9V z(1?WIa_gla+RnK9p6N%wSnCFs2SV$WbQCZbtmeNH%Jc&=t5&ITB}us+7;tNXo<*>J zb|F=Vf{W(ERQ>wr^7#8!R~3J)XnW=M=ic^Bulr2FptroM7EQ#x#}2${*f-^;PsQ#y z9L3P{XtJ!Ap5pNDN?M zWYpiJ1Xj^tH~!(21#t|TASV*vdSM&B|Mjk^?7x7l%#G?9l@Ir1A6|^j%=3G|sW-3w z)igr9{p-B_^!FG}`y`d@X9rKr&DqB3cx^sFP-Ve^fFl#j>klar=r9J*PVo_hy)#)a z4>7?ry^n?-QtwUqctPc$^x=@j1L; z#qP!YF7qR6_5>cQrY7P@{V5}K-%E05RT{wCfDlh4(@ql(!WKqRjP;^cqP%IJ=-6{gwG1{^$;B6b9 zQ*oSRc&ct4AouNV6jYo>Ls$vpT^wz+o7w*KQJJV_m45?IV$SOlYr~Hwt;()n6Duoo zK-dAjZ=GIJ_DP&Ur-B_(gdcCsLb`UlixVPSea z=972EOE<2SADKN_J8qwLn-Xb^zx!_You{(~d?zQwO)tQS|d z+F5;Jo8sG^6*-b}qnC#4t&La;BG@*4bCd1llS=ku8C+0k-Xh#T&@4UadVIXLr9th( zT2fD*hyq3Y1T6SMPU*o7%G<>4kOVfx7v$E;j9S*BGn6y&QbyOU4nB;mHNleT78Em8 zm}Sbw7B=qz&Q1UE#Rjon$aIMx81*hAx2wPbWIrn)u;%vQ8XPRqu$@p zV)oPVTQuMHxjJ>1YEG&CJ-2!>E5oI*`!dRX7%>!XTm_m-cN*K zWFis;0)>EPlIcgiM*d@{xZ^_+F{0O;M*`n#m`Dhs6FpeZ6*SDSKjy7*EhkPXhsumt z$Nhr+4W@hbK-N%%Bo)m-Pv1BId=wilCT8bhLsQf9j4xksv>6#0>y;qQO;=ehFhp-p zGeUqcz=^~ayW<}mT&pB0Nnfp^qT-Y8=Vva%^z?u{CPpoq;Md}5UpjW61`mtL*t>OA zZAlRk{sbwLcXgw723w?5`(gtWlE%|L>$_ z;JBzroveRQ56W2dbu_dYk#JWYPvLWd-&J{;74Bal<^sb)gXDUJ8x;cQp)B1T{+^ED zo#4eZI+=*b$Y~>L>g4DEKfg)m&q<%zl%G8VZu|XvT?Pt@i(BK{xD$CU%`7q}MvL)x zU%wao=pPbLr}XL-EbGaW09Y0(zs+om!Z`3YG{YLh?^cd}J!D~0wOeWHA1mKB1IC%; zaE*?Tu*TwGfRcpY-cPjV1o+((p=|YJcB`g9p4S6Sifbb!`mLYs9CUXTp-{zEoXCFG z{w>yY68MRrhW92T2cnX%2vh3nNHo$?y;r*)sh$uO;sFle_w=5g zC(hYlzTzliVoupl|BWH@^~815gj>8BnZ64@3#DRDo+%ERbr zw?UzTE2XZ!J{qQYghAky4hj_KW(x6v87#nlX0*%-LK5o`K)gIQlrt<+?1wP)8I#Zi zlnn=U^UUgmTqopsoldb}G5|8j0c8APHTjKv-RU__vB^v}VQ%FEa2O?ol|gdXX3^(5 z3Ymvqz7~Q&{6sS_G1LtNTn_|0CG)Xg=#N2=4h_&i4?zXGCicHwLJLey9D+}$$K6iD zXTONex*?R-)-P6y>a&MRDj==dJNJpo#0z3$qhKV@*j9k#Ixhq=2c$|O*l+BIsdAXu zq`|py_w{Ci^YYkE<~nOVORYBVoP|$MbH;t|n44|o-&;G6!N79~G8wU_j zA(4iT4iytqyYLp~wL3sb&vsU903NEmw=}e+F|xP^Z)!cwL>sUX zIS^ooZrc*WtTU9$jeY)c;Riiiao9*ge4K$q@L)wE3!p?0<99CF6%`+K!hi&12CTn( zhk7wI!(c#ayr4UF6dYv#94CVPuxtxdO;T+?gg5T~u6Hopoz(ni#1MPi7BPjvcue6N1YZUbbG~b7LA|KoiPA)CKM?J zx{|ZiDRe2QsR!?>Mfgfu#o?Nn7TO}6b1M?dbnca4_~*A z2VxBaKue?>Y_J~r5pqs^<}m26_eb~|X$6Had&-@scnFK)Jp)@1+$2k6A^4wuOrF5! z%I~|6@8XqSw88}h3`TSTR7YL1uKu8TZX2RYn217Yk6|ns4!eU=OfPAt!KeDksUWt(wG-OsS70Z>qSu{9E zFg?8-hLregBX-?%_pfOW=38;6U|~%ow5_wQo*wi{NWgeB#by%^lV+)$b(%ZJqM>4? z<$%yVx-_j@ng9A1Z$=yF<%edD`^$32wk8Mi|Ncggoz{+y`aV$5(-fyIbGqv%zh(K6)RT0}24C43=7OrhL%q@p@qMn?r2w6hLGO3X19% zG~@TvLqduA@HbfH$`qAaOi@-5xr! z8Nn=MTCaqFyOZw!>1eX8m_RWiYWl#&r2hNBb*Yokwv%;HN&pRLW^EJnR`H= zQMf^hlmY|*`4trQC;$8pC>*NI!SPd;wXFf00#k(qrsd|u;wdyClM#1rEMl%nXV_&r zdivxqU*5la_e4rc3TT&q`Uf*}qbd=%sTaMf4s{=6D}+h@Niu=g_j#;#j0(#jI@1 z6wmG8S_*<5fYa|>fNYU-+nhMX_25kXbsy@d0^rzdmA002f#K>95&IGVeIg%F$eiOK z1W#u}xiTKRLj+4nyAGBG0-erla7_SwmC6bVzby^|<42p!yZpdUEe?JnO_}e|2F|ld z9nwT4_r@g|Xta4&A|*N-3fvkg-`D!S%ELeihxVBuYV6OuyrGBk3No(Wi5fVW3@|OB zz0g3A1zxF>p{1dDCe@4{r2}c?MgMYe$C}=uqPiR=Q~_#$jK1N*=9-<+=Fjq9 z#0aye$UR|w2`C0w19UP)4;MMHM&w(}7;u>md-%i9&>>c<{i?f?5UH=M#x);-kUoxR z{p(I>TjJ1ybifD6VEx^{DKcjCylkTtB(k2*^URmi3y;T(B ziz6i{6z7u7AyHGosWfurjK@4F+uPeQ2djbhNoEsdekZi8tvA{49mhMIoH<<^hthB! zbZPef98Gn8Of0Or-@6B5XH0HYFgF5B5tGY++7l$NI&9Dl8~ZA+1P+zkpp3V5u}-#j z%g+QiG+c!g`2m#k+sU^(pD*U2AW=}=!s1u-&fX0ou16nvK~SXuE@Pa4TL)`zqh=-@ zys$~u9vv!ogxQfLN_|d|tiSH`1G{5`e?)l9=} z?4ErFDuN&VCYjInh^@@5dxo58TOlTM55p{t<&DR;bIM09dyYdf_NqO!oBI4 z6-7-C4-W#tRhCMN$#23w=uU63X)O;K4-c>0xV=ROEkupPZnw=zQ~pa=QJl*Hk_4{V zn;V^kr{#wZh9$`0Zq!0GZ>A{8^cQkKm)p3MBRX62YP1v+u{_|p7m{Cb^0<@}$U_Wa zn|5zLI0?*UivY%5T0M0t-Bi_v{P!V1PX|N|`h)}Br2&54*Fd}k`={vnwr}<&M-?}y zBbEpqB)-seqHR$zmIXox_c*^7;M}QfI0;atihwORne^=t+S*J?O3ymN*KANXtML{3 z`f`igw0z5vzdPwzxpM2LMnRz!TLe^A@NFzGL(}CeN4E$uYi~m(InZ9C58w(2WP3_- z@&u#YrC@GH>zV-W@ek5K(WUfY7@DuF*>O4#YHHYN-Uc%GfD*El<6+77n2JA;S_S$d ztk&*TVQw@Gs)1o?VwfpYCeVzUlvB(wlVEB&=s zK?vuRQ;~xKQtwHa$%u2+iTeC^%{M^4asx;x+xswXzkKBCg487$k5yEf#$8a@Y@ku_ zP=0x!29wMtAAzZjwPT(TN%`fYpb#Hv#)xA1AUN zc>ssLsRT4p{W@WtS88H2F>B2cFK~OhNS|Q2GLf4tlqq&sVd22>~WV0!*3@w_|?m`I$(W6*tbxi1u*!s1im}$!_m%t;rw}nl@7uiUg$bzUJ z3^S3?z5tN~!aj4_^f^#`qJMP_U9%q8U281FZk|j>G(PaQw5+|g;U6D`FGlKzMcBka zM14NO6|r@cm2Umgjd1*Eui{=%ESrDZYmn4#5>Y)@!&6gh9s-nEY@$e*+x0=pcQ1+@ z-v?L@j%jy==%+hl9269F=~@4!$GfM2!o`@g8tAW@A|xNh$V*0!rdg2(n0;$i(yJP+*cBZ)brid=xQXcmp`n_eE$aus&#wKQ8Z}fuqQ@HUkjoap9ccM>ufVq**R_M zP-^;V!Z%cILR)PqJE4W_K}2_jsvUHbec0eOP2qrUpH{eHE5hh#@3ZJ}`>idp4XrV? z!;aV06Qwb2gK#I(BBYwLG!h6jzvyf(G|pKJycI?~f??*HyC(^c`7Lcn$2x4Jf=8^P zOOB*n(=PNQ2NV-6-@n&IHHDD*>tE5y(0^uCZoBk-pO_mvZJmVQ@6UN7mD6-Sdy>So zIS(ohWNrzva(rz>3WooGfJjNRpPVq+*$s9dL0WbPR&KLJbf04Sw_FZZaj_L?rU1I3 zQJwh%_e>6-8SUE}RZeWs#B6WW;Ie-!4bjhMZkNCwvHl(l#${U?vbY>zm_Q^8r&0Yl z*OU~&NymV6Q2Uo5VhRiAOV(*S{7W~D{R%ZoJ19Hb(UCb>cX-)f8ZL}Az8?o}#EZ4#<28rZE%L~MaRJTWCh;NICag!89>mdHtA z;8E`?ucdFvn?qo{m7WCyAD;B?LlcRB@)E*P*o`L$17TLJlB=bOSGkXeNR$lsj6Srj z6sBMjV!G$rlzqky98atV;GfJp>exPUxLEIS*Ld&`=+uMOc|ZGX00WPtKGZMfV*_tk zmk8)jZ#O*j2b`hur+@yAqPnlJS(%EuLuYr;A9}o|o^+veP-o3rmWz`jw1TJtw&%xmV@u3U)UN}K!+bZ!TBk24%VoCw1%}o->zt~)P z!~SI(nOH$INDXdA5UMC|S0%y;KnW}bZvH440GJLCWrw;$mIhT^#3RL9)V}q#Nui?> z=PNml1s-WqkTaeZENWIiEsj&m?Pw}Cac%|AS)|Zf z8XV<2F+JK_YYmDq%ZHlBIlCP<9;hNu4QC5}WpZF5v;=bJ0~mSpUv@V;SMM4@*xM|E zFl`QPavEdVj1(08KyG2)4)ph*KQ{xa6KTM3+%NwS!Kk{^*pvH6C{9@auGx5ymOzDt z$%Gd01CKTTOPAd_=Yv-R*dU0YX5$9bKvyr@X4Bd3y3IXC)tvdjWNpxoJ=koF>Xq*cXrxY*O>*wp|!q)yycYtb}1_Gb>Z1{SPBco+QS z_IqxsQ@wwpKp~n#-Y{zNsL`elY=xAxv}u58LzW4sqJ?6`3P&P}|%g{)Q& zBtdJFmNO-5Y^3H7PEAG8N=W$nid1stC#HRA#iz$7W}LCiI0-^sxixr-&z_0DdPPXh zEmdZbgrlxbOa|0&Y%F6=PG@O3&&=wqS79J_wV+^2L4$Vi)|pE|L5RhCkUOV?VQc68 z`rburUjCI*Y@9(ae65$V=7o1&_dVWG1R*!FhC`-!S{PZSJ=%>I|gCg$em-#T+>u6s`l zb-m!QNUe;ku-mXGIbk6R_Q^m|=Hca?{#YUbgTT-M=S=smdy9NJ2>r2S4J#ZS)i^Xu z>Rp0=m6nx#ete?2WX)^MOKNJG0~gm^jga#8)Cz_FM35B_y}&_khUfhCE5Ps$^3;F* zkpB|lf4BsuMcDEmFQLQfdLaHUPeE?Czsm8iPk|d6Io~S$FZU(52mR~laKqM(Kc{p+ zTyC?%g%@}QJ{quG`Dwz0hXy0(&W#IDDr1m+gh{%+CqgE6VuFLKX}H~Im-kMY;Upnf z-n{UEh{vU^P4k7C{=orSOtPPGwVp>7ynIn!4Hh?uIo`16Qh$7Wf?LUJz&|%PRfyi;+Y3Ps%okwz$$s`3de)l2h$vN3 zkJu}iIDOGRH%|`C?(O}O^zyxDxcSkQAXvQIcEdNcgqFU&8rhXI)IU#XBrPL@qUPh< zNdwuf9jAnh3AGctl6kU(s zczOzCQ>g25#g^Hw@dj4QG`+mL-BhYd$a5#*T>Q!vZ+j;v@|SmshQEe^3{mY&xu+f7`l0SIJSU8e8p+PtQ z&ZBn?HFd^Zm#_+$6ykq>d=~C#y&+;;()slC$cVjnBB2qEBmAV}BAilJ(qB(CD7>N; zX-`x*IEq|NgY5+!y2aXO+zK&Oe6^ zkM6*Gp*H;r%;X)_meZdP|LYgkzm9Rj2)lTPydA4Q=Jy>!^gMKLOPXkPfcR;xyrRuv zcc#1R=u~d&Kc(=m_x;bg{WXjHr*EnJQ`uuXS_-}=jgD!ml|Dz8*X2@wtN-_>+GF*; zh)4g!KK_3Afxw?*VEya*&u|S23E#qlmj7HvA`S^oi~3A>9?(HA#ywbL_}thXs{}MF z8aC{p93>P@az2#dvO;YrWWb&o~ga^eEsh4qxySqe>*n#`&k&CON@+Zj(cbP z@XnfDJ3R{vx&F|=0r&1qTR+7Ul`QDNyTQPaiHdfUW~oTw;K)(W2HdGpo5pI{nnh4h zORVJ8!#Y2mmqkQU((MF1_EgwSv`|t7oxbwTE6kJ}S){`fzwt1)qWn|YbBr}Z#=D6Hd2B11(u7dnuEvALGEv%C(&CZ zeM2Z`LZsuvGhVypZ#8veV-e6tAUQBteNyVm8BSXr(x0Cww>fjfyejlIB*`-&gnjMN zBLeZYXGM9#c@~WDn%0-ilPewDZjRUXhN>xg#JDj)u>QWG01c^{5y| zA<7RQQct)fia0vb;InAa-1(N+?O*?Dhyv%+*RMhDo)R27&RB#o5|8+z{eL7|p)}D@ zf9`#qKU{KthnUNBW-))p-qDe5s)H(nk(pUf zvZpu05@9bmjTawWMfYoNGv+F^K4jQimreR}nw!gum@+X(ESwVwGB3n<&PUMoNeq6X zB(mAG*}tn%mva>+$EFQRHghI{T?#AEy@`Th@K{0Z;nSCFlDWZk0|UgF5I~vEIARo2 zt+uwdD9U9S!45<-`yW4h*7U8GGKNjiKPyXbRi4i)UQ>2hTq2B3mkumjWq(68Z}u3c z2SeXtYFhvE$XALSL+;PdiW*+S%V=KSSCdpShYCz+A3hxLe@WP+j@>l9Hs{~(6ReKB z;pe@;dJDD^c7Jhz8$B~1K&tv!(0g}RhDD=N=D{1GBy8W_f`@eS9F~1gJv=Y56KxdQ zjIelBRdDHY#5~Qnyh+Sul2lrHSpr2sz|ndxLV-2uNjh|3q6Ff(NpF{EPr_JvRs&k| zo$4|@(FzT=D?#RSuQ4gH-w|-z-bK8!SDyBMxLqWNIi$5?bSG)sk9L z11#)MNspHB~_@%e+ZkQULD&UhmSrLzj1rRByOD*Nmk9 zwA2;NX^(8Op|!U!cR@P0^r_bB0I}q|TR`t0uER$4a+aBIaa_1?79>X^*N*oIH91-T z1_}SWYct_3E&L+GwK85T@qBDt^qBD~f&JB|C_{M`#1f&T&Aq*z*UV@XH2?Czo`!)} z`y#}D9rR|mFDJYth3gx7;sy4sBZ|Ud{(xV_pfD!6T{Yj%jOeeeR+R{S-qvRqbGY|{ zg0k*#!j^7^tx)j5D_yZE{AA1$+UR02b`mO$_<+oz|8<0?8?tJjv~3w=`P;U`~Reci*w8{ma0 z+P$)tU+z}pK|PWB)|D<0aiFmjTnm8;%>E2&6(j^O-6be`))8sKN6%0$C)Un3{(%k8 zj8#61%Xkz~x9IuZ+&_M!u?~7AQB!U4R9Q)Bdu*TnORAwI*xbd7Cxr1lagod-4)4N} zny^Sn{JQNEf$AY&9V-eWy4SZlSmp~$q2qVE@JaNzSjO3i3Efd3wq*5r_q3Vh*&bSD z6+b5OiqWV;pd=Azz@TY7W{w84?h6qtDA`ktq6l1%mZ%K3J2_r#2B!7DPKXfKYj3zV z)6&k@&fO}#G)#RplTkJBky^fwCkkg;S{4P<-RLg^;!`Ut!Tn3C8hCCo-_v``4i4-w zVcFLxeLnTS&O5R=#oJpI_~sv;ny%dyrDc&{Ihf1+j*1-A!vAz}nEw*5!yLT6dd8q? z^4dK&enP>UjhE?ij8UL7!xcit7Xru}dt7E(0roXnaz?yT(Dd%VH8G$KK?(bROe{!e zXR?8eiTmIR8d2Lxas+CyHs%71KO?n;UbYf_ALj zw&8h;VQWu>_0s5t^lo*(yvro5COcMMefjQdzs<3I#um<5%62SvQmqGrh1|ha z$2|l)uP=&BFAm&)YwI_~dQ&7)pUei}^3StU|KjfKmp>*QGBVS9w^n{lv~b2H%WsgO zmKoll3UT#>hHCbYZnptqj1+IAe4ONmgMaXj%64_M-l{tR&=;xW6t)DjYq!*L+5f&u?fyqQ9*h}w>wlke!MoL0ET2@w*-l~O_HRe2p|&N zw_W7{0hFBS?q#U?)(L~DSS58P&a7V#4*sZ6WrEV@6a^i?=1WYnfVVaW1lTm4-@1jn zK3;IKu(HzcOhqfzRuF~*dWk#a;UrGkD*faoE}@*~)h@-S_U_<|`EV@DM~=Oa0wK0H ztP7x^ZkoLb4dt%;+}H|Z!6N*sw*(MtLaRRbaEyzK0F~1to5Kl}znZYBA;~b* z^s5QBcdIdl#V)Z%xwH3ypupDNp6+qTnDJP3?6^h#%e%!IWzQNWKP8!4Tfb;%7d5xB z(HYuSf;KY$*ZuGQoF0$!EDt~p2>cDT!d)0f+~!b!!rRv$pQN+J$jHQ>3C41n(em;J z0O=JTYZnQaa1;n49r=^7I>gpfv83l+Z z>8Q26UjF%u`lEuV*G9Fra02;lwMf6E&5LyjRj*dZqMD*rv45r2fw1i6ZkA^r7{;Yn zgeR8Ceq{Oa78VvXYtOF^4H^2a5C&37A_;Eaz(wSd`5n)zd-X3XyL#r3|02@ zll^gg=w{YTEx5Qtj;kh@$#`d(l@J20f!olh&rDYzhJ0HnKp7%B(byu2Mjn*4+ zy}(<8U)g&NzEd!--2zCub=nhwgCCV=(2oU%9ry!IuWH73^H+&<4Eq{$7C6Jx;uq|7 zKWb0?*M<^LQF;jZXfUUKd}0Cw|KhvRHPm?CBE!=o<}_snuZ^gj>G_V4EVZJ`c6O>> z>-q77986)*am8WT@Hm0SrXLH|hl&^IJx?xtchf1s)n(?^^zm+kdGQ-ciO`*%Ja?!@ zXO@X2V*JN?f8ut_?dsXJRv)q>j0XC%bL#FtU360V@t%wkf(o33^`;xV5aaeOhjb;~bm?7VDGwhx;(V`m zY@vAog2{YK_Y7*`cx;GN@@I*Cmb z8)VTkGzI9l>-+n?A(HXiZ@vX+{=$H)wijN4(>C+EHq&gmXMEv8FwyqL=H8wd9UUDN z6O)(PZD$c6-0)bBZp0GZBe?sKHFMyk#n1}hV)_Nwr;b<)7|Kg+Ll%-ucajtShr8Fl zjg8$zitnxk=FjjVDng}rLE@}D?UeXpNMg{iNe+dB1tjo8D?@BT%n&x~M{c^j`}6I& z?+)iP51$?$*ykI!>Uw?hJjNa=6gtNE!f7i*Q5Pq_FthLMQvK}YJ{K1kDpU~g*HJn) z{MUZ^BS&hzKA4bDg%TEH^FA#NHRt?@xc$kKrGXNBvtW4kgzb-<7>h&!pIEG6P@6{{cMJPvi zMb(h_y|#qCL^eYBNqd4vi|CAjbNuX$Cf`rRu^#F`3kzG3yrLrg-uX3XVw;|x{vMXp zjA>BCO$rb@bY1gh57i%bcHCaOyG-a`^x53jjR6p1`k*h#%PfpLA1ZT9INwW5mDDUk z>TH$u_4Ue=o;%{`w>^S`Z+bxD`h~HQo{@o&2=ek6z~p01RGPC)&^gCw65zRD^A%$UqeHiI!?|kb=cuDQemzg8^itN zWNhejL;A&9d2kR3`+LPy%Ud+E1QHJ(h$35%o|Pp=$ILvl;}EzA+TlhG-_1|JZTo;rMfKrpK^5S_nA8ghbQUMi8unJZ zpuR`EkZqy&@IXL2F&ED*1X-S^R+opj_Kwp(M9QSlh3S7WijWZW@7kNVNH~`UKKVos z7<~##^~ajForFY(+#gH7c*TwKTUzb_zbum4Ymu|bj~KTXm;%M=V?3a^5rDe4_Bow} zgMuWu2`>|<5zq(X1f0;PtZi9<7!n0coWH%d{l%%IGp@5UxqL7uKJOisKl)k{)XpKw zfp|J#X5@zza~h@Q*MhTsfKZ1&z!mZ~-Uh*xUM5d z`;MwR+3E(+T?$VuJ+W!!3n4-PZBb@fgqj%*?_0dLh;D0>x?zaMMig9mc$A6i;8O_Y z>N_Y$;p_Z>m7!pm?}1&q5`!0TzLxvz0tk^a({whwkVyIGuyOJ6m@FElUbg@dZr1uH zhK0vU^n0Q}TXlSj?8RH1v+)mK_Lk$S*xKGxE24-QXbu*KYUm-tO@j1vzszA49|sF( zFdBHO{HIJ$8*SITBB&&-U*3kdCTzU;?Aj0p0sdT7etv!}0|!T(A!hF|pq>X7lGl&{X6F@@mA&OsXEdf9ynuE< zP3%$^gRly+5f%^@YaA({$7zp51UgW8<3oTu$ScUt*O`1{-)1^K z-4y&J3%C>Tz&eHuWoa+sWPMbCov62+4+vo>B?0>fAkDuTruu`t%N(y3q@+J55T^b3 z(c|ZOa`Bywy7`+_tY3rcx`jiLg)WhZ0tNE^{V2jP84p*+18D|iMIA$mi#l*Y5Gb(f zhb{(*Q$n`_gGj;@6E1E9Vi52(G7~~bZm)|zxcN#Yi`Z{&lG|;J&(t-x--VYsUC-`= zWwx|-)DI02PStr!|HDi_O^!NvAvU^RWtWnNL`wd8wacKN!Nwt{30$MFd+>c5UVLD@ zm5m4Fo=UzG^WEzP?MI^2DmCu&=M^|@Y)G0}6qNN2c@x*%h>?8w58gYtca4)-g#e?^86}J)cdzE0N8=DvN9-Wowx}SsMn1>HV-Qwe!1QD zx$=ZXA^TZRwdURahvHsI-GjRQK;Octz+Z_XpPicnvCVJo=QTzc8d%iS)p=&qQMyJ3 zAfNCJX|n+Gh?Ocl^Z5tM+`eIm36$2mKPPZ_#ryIVuKB(t4UjS9w?#(i0Q@N7k7m*5 zR&#g+v`JZZ5x}2Z69U@p&ilwQF~3(6k)njY5VQaf{gKq&5}R8S@Yaw=8t7x7hv93y z%)B4ZA?Su8kmu8r^jl>7&VK*wwV^&B_ekjdLx)36nEe-*ikDsq;U}jL{+tYc>-^kGu16$j-@Yy*lPrsFN2ejDiN`^p&dtZfikauuXr?w= zEAqNgC?(j(uBB8RhP^*dlHyL{7 zwDb=Y@(^+(ZlV>8n;UC~sH{!Z^LWzS&mY3jS3otaPFUC=BqWRJC(~!{%L)@WN|zu<|w`ZQ_8uSaLg5PIyz`Uh_h4_7X4{7wiqJp(F?So>tYu!9(a* z0Rd|N+_=fe7#OEQc`7*O&R$Yp9zAr)=Mt3p1!pH%2)Y*T*s&5p{D-%CJVQP75Vgsa z@_m&y!e)obv_e2AzfH?TQ_ul^+DCnA&KAU5D2lJw70^7i!bAa$rZcCPg>I24x(a?5 z;Fv6>tZdRAuX4jpHwzm!4J;{xZqN4k2vyu%mG-r}8^(sZ)bs8AM#NE69zSue{hl2A zg8yad`L4sfN`E9H5MNKt^M}m4FQ$qI^ASg^_cs=rVca7Z_tiitkQJ&kk^wyu8wVX4 z^0|He)p~ouf<*n0&C_(faDI>HOp4~pZ$fp+ioERCbZqvQzO!D*`+@U%(iNZChH2~X z%eF{KMXOU5x3?$Q3aINK-kh4AcDo^2;d<_3aUIC735OPno+V!zRgSvh6Q{qwSe5i4kkO00qz2qD*>mIpf>_Y)12j3L= zI5M+6mPABE9JsIg8?d#XOKh{1jg2-XOqA$FkSM zPSx~q(Lj*ZM(2|6c65*Q{XW)uR?PT9Z{?ABNg~Aq>LirGN~?GMSq70}2preaRQL~Jfg`S%Qtg+gL}bi=-ayhQA-aQ0LPCNd($z+5 zD$*gBV7r5eQZmky>c}T>LsT~}I7C=sSQ51)_)U!BnX>zKjL3LMfo>iiT4UqmrJ7m& zjVhxI$!Euck3cXa++gQnmzsXJEpn@6&AT!;=eE2UQ10A7D-xxf<7jYL?fUfGiyaGv z;9k+A?Lm*u=3-h~%^K7;={+8)jG!`&)Y~mcAov^v)?96>TPO1X0!$0xw|Ni)N)V^m zb-Jxq){DKkOb*jNy1bGs@L=!kNpptGt{G{EWW#ED|MfwEb;$2oA^(`B2vnEZ%3D*u zrSia$!sPq8iA9;M$I4qA9<3b0z_Xm=ArH8tx*-EE6^)FxeISxyr2@A#-kGt1WIr*% z9kX=1m|Fiz@o?6{c0pX``T`K`9}Ilno-xv1NE_W;;LUEmYrzxR(PK0R;1p8t=^1oR zKb&R^cwX}{Q*GP&35dY{T=nuRuW!2V(&|y4}99P8yl(L#h4!?l=qMr8fUr2~^ceqH>PI}U*7uB$}cBlUr1RpG2;Lt&OV&P!q= zBEgCN7IqbI7KBLGp_!Zb$hJSO#8y73xiuLVH4SaF%vYPg@^_suR@yA}5zr@?$tTB5 zDS_~n0w*2x(2NF-QxK}NRevu?=*&-utk=2TzY{94k_4SfOg2c>gB8{QmxRNl2do)czaB^3lAko5 zwQIGoVR=rEZIUpwKO7G(M|;-c`I)xBlG}!mRQ+y7TCZO@u*M^| zd;9iny!|sDnjk8jLJg>5U;)MTF9{U)55p(Jp|wlIm*slWqQ8V)0-!s6r_G!62M@xQ za|%un1}j7`#&Hm({7`^klIJe1dyoNvlFopL zH#M?h#sqglIAUM`SvepSdgNbR_%6 zp{Wz3CtP&F@RCmV?3!w`x+NCpDM`I8@6o;oNnJaJ~?>`Toza_TW~wrnrbH|ZZ@L@ z^l~7h+HmsJiJg9s>k=ECjHpML`POVOpCAQDD)?x`jlSPYpfz$4KS@XcVN$Y!olH0N zi4UB6_H&3(Team4$P8y`W5Tq4fN))%7(;a@k1BkOn0utC~iSIXvcJiNeLV6;dh04CAS zfq^%XnQAS*8{{DOaeTZ%_2grdEEx%dgh#L_!2`pVoY{ zd341-1qlU{zYlf*z`2IB(?b0G-^_}Y;577#>rbEtpv5__`?0M~&wdwDc1HJORb(Q@ z&8l0j0k>@QV_7at{M>DOy=V9_DLq!sO%?Wp+{@-GU=SnIZ|skPdqxUmB-JF#t*gfo z{ub7O-vMLx-(Z3$`+EUcTab>4jjdvW7BsrcQB$Ycx zeM)Pd-Zcgte*5v!tzJj7L^=B3w%ms(CtX1KzE79VPAnyjN?>xhU9(;5Uod|h-^R{2 zP1s$!?dSWP;=ecMeBKE#3p^Wo-Eu8vd* z_%bv8?g;q7tT1+VfL1+q zs!{3{UuS2HikEyW)Q#7Q&Km)Wv1hs0bTu*vf1;Z*M#Ug8S_(bD>88yBSEi#V}>p(X$zo1 zNDS0}HOe8vjFiL(DOp**Qqt7apB|jvjHal2ke6ax`fna_BEv$LzcwIO!~S!SnfvhP zJ7Hmb=>D?Z*Z)PTp(uO_grV!`ovkmC_!`7j6I5_6;n!V&^VX&*%Q8x~&$qgCPlw#X ztt>9WV*7!9b`_+E_c%P3_Ah(t#qc;}lBG>L^YJdKVGd9bGZNs3b-Trd6*(O%^3uA~ zj#P0ZK}%g*9b2=Gt++FO>XZlz%XIGaEknmoZ#Xq0MK2o$^EwepSY4mYN!%t0JeYDt zVI{U9kq>|W77~wAxt(2HJb3}5tDdJ_posQN*7V%*ktCh_m|h#Sl1N^f*}hHU1;mNkYpay_^Z5dHfd90-js z)#uf7<7W7)Mohj)Y^Mk)bj*>Gy!I)G8+!8~tN+_Kn;j#k&Khy79Etw8{)D_A+-yPn z^>#yFsGentsL$IH29~Gtl&yq+(DT#pWTU&lP= zgoy+rgh7>MnNkuYy_G^L{>3ZcMUVSAIXqF$3^mIi;Op}p zUvmv9K(y+NKA~T!=7m1``em2W*>(d&0I8%VW}>z=JZ&VRtP@XAkf6dfY@SK`cA>?gBv&LiI5o4udGW;+vm<*P9gS{}iAon!zG$l1T zK0Ag0aEkan6-g5=?xALen4+RzlJ?KWBJ<G8aDSNe!V;pmWM%iM75<AL7#VrOy7Z`W|lO83p9Wwq`frrD#HzwdidO?X|MZAdvI( z96?i~huw-7BQH*Ph@2h&7395xbIuk#6|Cjeaog@d{sGU5P3sUfQo!)gnJk6m<*a5W4LHv@3%zW1e)Yyfm0E zWyg*I{DQdw@%l7FD>;N`EsH1Zj}ChO>%0ySjp)eYnBBiH<7yuU<{;#X}Mb zO^GHUiqu&t9nU}C&q27(o3j@5LM@hT*nVz3t4IJ*x#RVLs9WN}I>uNTc6WU%8wKX? zoFZcCJ|+Z@;mHd}wE?x)ji@Heur(vVt427+exVCd)d(Pe!ra`G7d8V|Jd308mNi6h z7=?-A&Z_VC&d;q_#-aNL^wZ|>jM@z35d!RBt8b# ziiN!88e0#obLGFH!I;Z@*m`SW8PWM^gr!g)0c|*@TkZa`|>Bkc6lw$kLEL2=KHV%r`onG%>eX8*{{WR$05og2@UWjr)gFSl=r?2BNT~y`A3Irz0Nz>`j$zj%m+G+r^ph?S=em z-il`nXLOX5J}&uYnN0I##s5WIMAlwPyzy=!Uf&=54X;=HiT|OvNYR*hO-x!$ys@t# sUL772ue<*JCm4l)j~E8we;qJP)}>zT#t9Pse+ow3JqEjP>% zyBsiGYk&FX;giwShkwBMm3}_C)4YodYz$E|Pt_D;NDw1e;U9xxqv9ak0kaq;J!pMPB3 zeE#E|C;ae_i(BV^`f+jY`j7XAb$@&K^@$xn?i~ByFe3Ehna>I?n;BeIfnVG~<>UV) zhW~Bk|I&{AJFx#!U{yi-h-;3QEAi7^shh=~v4$ff;)Eti^LFdSQ{&O^TI>)NVr~N& zg-Jo}QT0)9!Vdc69*+|LErJj&0?3>FsHZAhVWl23l=_l zV;Ywb9V7e!T=~^3f3I5w$}*VT4BftJ>t~gvO_Rf4}1{Ali5^*52Tcg%2!lCyzGmSt}q|UyQrH{9hqlk{XFHYpposL?Yw7`)j zI!aT^l6Si7s+mI$$rQDm3-aJgGh@`F|)VpTsM2)XzrPOHLq*Y#bQmudvecI zt<3L2CdnCy+D{dVguRlt*t~pM^h-;sLT=A1M-XU56ol5gqc4Va<8#k3G0Z2zk;3e( zd53ZTb&tV#n#DzwsFak7UR!a_nE%HAa0k%ZXDc5lvif4TGg`A>oyRjc@)e{{S$QW9 zOIW64zCSe8EqN-aGdO?@Z|;g6$9wgo)q3+o!Xy=bF)rp6&XYatT599;$@J~1i>77n zP0@I`nN0qc&94ksg!tJ94?6F(Sy88O;jLP(zop&Xr;fONy3e(`vnuPU!oAG+4SF+iC|L$#rW-CwL#g6m`L2wuzlXW};FqC3;k2Rw7l zuQCpqdgl)0*~9EXfETgF$KbR-{?dWfIrk}dEqiH|^}|MD8pNvwyU}j-^sWW#-ivlE zINtgGLJ{R@9Zo zv7oqCHXolP#oa@ccmDe_X5W9<{0wG*6I|4GkNHd88_-Ixs&GsW%jgZOgJl~ce6=e2 zm4#1LEF#4%<=T-)`_0dp%X((8B-^LDRz7~97F^*ZMoWYEX2?Q|4<79f$}zqplHpPF z{L0$bKW+FzG=4FbZA|tX_b)#aQrQp72HOvZ1^t$GD@J9xeFdij^RDF6XCj**L?4Cn zLhD6JEa_I_HB`Hd%h0nSm&5SAX8u!r&CB(XV^}elfehokQ0?3^`m=Pf z`2@5)8nU2?maWz(8#qRZG?!bsi!kc!H0lvjOgqy{?Z8cHTG8A(Ou-ex_B+USJjexl7&&|G z)FqRdkIHZfC0}2dSo>5PN8r5@=~hbFx)bp!>JrWDcI4xEXc#7JUoALAl*W6f6IT-^!BAx z)%5muNk(Ka=K>n$`K7vrxdY7BN0he+|%9y%SRZB2gqO=Juu#G?XiCL6t zC}8H5f_anK*J^R_^;DB&iJlcN$3N5c(N|T$pr0lo5)a-@ zKE}gy`8EjjpEFkgeFfwgjbja0HhNM$(M(2t%@*3sdp-Y_lh(Cy`Z20U&FOw2Vzvl} z1CP8>s+~1{!;1lVH3USZ_=6>fWb>Bt4nEymAMPs!ssfPisPb&N21{~i&KJBtzYC9o$^f=@gR157} zY0a@kyt@u`bFAq1BNLH%8N!uI(JlkyW_gQk`#miRZ-n=FM^AMGkf90q+s0rKE?;7& zE_XSy<5jG=N6wBnKdeNed(I{2upM`A`$s%l0A}$JlQF9M;#rAIY#rtT#HGoCd`fHP zBqXF~l&w*SMH?UKQ3CQJrx!ol8_<2Wgp}|!tJA$7kEts`sT4G|%4%#fb9|N{hop2I zgcJS*+^Ay_$ka7cvPB5|?E=cv6H#C>3B__lnPGS4mZV^E*LkZu*OeoBi)X4AO3J-c9zy3q-b$ZL{w)JYptZY-6I(jaPht}d&@EzWDDHfTy}?!+eVcp`>n`= z-s;HM?K-K=8|l|7)L37Gasu~`D=EPZ&VRbkr;Rs^)HQpqUz{%Q_s9$TE>TZJPd-)oiT!A9(#933RZ5igexxfXP;Nk|ynF|IW=i+Z+$k0rP$&GxJD zg71~q*|AXoGz&73pAcmZD#U;H@$Y!QoG>3JhH}F0ONI4toL^vL8E(--05Tz_AZ1}_ zHaXO5kv|vgsFNkNg8zLgHRf1~qJ^bVwj^O?%C#MuN5Vi7#C0-K80i=`i|5nt2qW?} zR}5(*6Yo^n{G9>U@A^bUPZ@VPDwzxQ6a*WtZ4`M@eV@VRee0)-3_jIpiT!p&k^SgmD{Is_Q%wo& zLrq;Ib3u-4fj1d|nVc5dE!O0P%z_t{9}$D&myYrgqLU;oxj38Tf?t)CVA}|*w1VyD zr=2K3iBjLz$s^G(1^aRaC%`TcU)t|phA7zT_brVwdCd1zae|?#4a=DW%t_6=bT40pnAlr-yobQ88lt zu>>s32EofcD3Fa|Nfmk`(Ic7D2>1RYmZ&Uy!wJLfh5bmCLQ4#C{Z?3_zp^E7@XnZBRgYiwpo z@0NjNi8#>dVM|ZQnddahjNU9nQc1?3EQQ3RB$4hJ#p|->9ARMdt}TA z!fv2Tf}(KqbDf2FDNxqoxl>wfz7rR%FoHTAn;kiw)#fg~R?>VWl-A{>hf znv*;`@_GA8!``0}i*Ly=F8i3eTHhFKBhhA{%Em~(8qF-x=2z$__X(KCtFafHHrj<8 z`}9G!IY8XbIv1BolfT0znY%zU>Nn*bCwpTBp=q+O()lO+bkw%Z!xiSOLyBGp9EWTZ zUmCKVCE|~F1?9vP%`8ZS;NEJnk@5fJCNB@q{Y#K$82vLzQ78S}_y?r5(_eWPt9y&6 z!HsikNYiNpi>zB#%9Y-h45Tc?V618h3Xosm87;^`V|~zAbS6}(^2#kgJ>_Y;h=#k+ zMMy_=YiTaoW@p^PP@e{uyR_hv3rk<7U$UAdnG;|H4-G0liU4!eaj_IhS>k1m{-4 zvg=2~J!!5tl`-l0GhIgiVd3W;IuDk=B4qWHJ&+dBJ!_Xj20?o=i2v3rLx)d23@$G+ zL5yuhJA4`6zJ>k15h%m`5dhIR+1_Bg&dM9~3q<>D*M*bi-N{7zd8^ zY)6xUUo!mBF@Y0tr%W~Mi0B=l@vMG?2iK7)ti#dufKA3b-U!Q$+%>^ zL{b`ua1x+{63(3dfT>wSgSG|{f1rPL!m7o!!z3kPh9$g5JiaS6;B@cjiUXcoSq7a& zPVxYjS@sU&k2?ido4hfTKx@N_(=9R3g5n@om{-ephBg96LlSBpP_k$G@&*&V0(Q$k zqh-!VtK5&b+WRXqeq5#0X|h9n*sV<|E=#c;5YLCbPl%ODmDEm#t_wgFvor05?>}~1-n|)5e&JV zp%Ts97#H_Y>4~B{(dzpB>Bcn6^+KT5+_pPMtzQ5gG3|xo^eiiuNzlMHB`pOp_F(5ljL438O^UeMD`ge(q>lhi5+v2i)Ui6~m0c9IqOjVFI@ zCe(>1Hz<*+G~e1E5z9+cBCBw{%8!oVB$y#nl?`X~J0+949PNhgvSvV|K*U`%2wmN8 zLRad5xJw<{HECHC5NH-umAw3lJX5|Fr+$+xQKR;q1Nm)#y;Nw(`tY=?62V)(fB~|- zv*BF}0W%-+;9{ohRqNpcK=OTef%>99d8M4X?2v%wU#~_W;zx)SI02M+B4)_Qq3IGG z=?ALP0Mt10SiMeJ8&|Pk*-*w$8iySD5Ck4Vn)nBfJ-n2YYK&*90LKC?A;!3`sSK9u z&=o)_SLn;FEnC2xELVbuEj5ieUn5B(-ux;C5YY=Am_FQRI-K8_cO0o%Z9ADO&PdTm ziJJ&TVGD&tb6=h3@2x!xjf6xmkMs?YOh96@0LXa_K314Ot8$oCpmfy9+ppbwZ@5wE ztH&$%+HfalDW#Rr0u6I9)c75+!3K4ljo)NB15RcML6N(!sXHl$cw=?ZA#Xa@n{4Aa zZB~cl+Et#ppxY~Fn3v{m+0}GuI&soo@9)F-&Hw0%Q&=+1tcTRVC5TBr2Ok|;guk0a zKJTW0!hYVS0B8|TZNE^RPc`g8x!oGs62n1DuiT6jaVT2+P>O*opVJ3@Irhg$Fba|~ z+a`!DK&b;unY~hwbzcw}o>y9+mx1c)2N_fYSj@qwrmSBBW_|%17XbB=JPl_-h6bNB zElq#g>5|J1J>;}CZdOr#ce|X00XYSB*FF^@hs)DoZDaS7$Dj4kC6SD#q>Jr@#v%J8 zc|V%X=CUI1Pdg3>;iIyRHUMj1<_v>6z?1{lIqC^XX*L0_X21PnyyoSyFvm=Z9Z1db zn(ZefCrj1;v3sWD#hVSTqUS`3a(}0#l9CdxvjBy9wl~(Usi6T8YN%$X2WI!AD#L%- z({L#*NI1fOcn4P@u=b2WqN>5csCp7#(@5jwB5P?PUV-@Hdpg!^z|ZpPOqEe&>8J0d zgESZEddY&C^uH_~5C)#i+ID8FAaUO}e4UOD7;rco+;g9A>IY8tVbZIVM+`*I1d8{H z9NN+;i1amM5QfiiHWxnFuC7kHasT*ZPq#z3dQ7*a;qF61rpq=v?SW%4Qj(H-3MEVn z#@?a&T&;PEUHE)m)6#^b%-GXQ)XwqjPs;A6ttA?D;CY6Ey%e+hr7-aQC8q`ELUTt@ zv~7dnveW!XD)$?`%6!PiPiN)836E07%twjP`k}Tk`}yHTJon}t2e&ab-i9l)l5Sb^ z$(E*hMGFK5rS#N!n|XOL1Vn_YjIQoljuUA;E(4*Y$E(aD$H<|vD|6N!zg1nz{kFE@ z)XxZC4`gD!Ke;EtSpON=7%A{jFN?dc)9rJzvd^~{SR@6}mBA|vXMQR@*!HP<&5nXB zetKPP9(91Z{5!`qCf1|IMp!|Up-Y52v4&@H3p3u~Uei5P0OEWp4L7sEp|kq!nT;L8 z-+WZjh1irTn*wZVffY46N<#|rP5=T0dS!57^<$9Zw-0_L+))>&_U{)Kft~9Sn{fj3 z>bk~@6UUv|6LxCrW3jluRtBL+gSOzi45;XVIYMhq9k8E&D%u!Y$+4fOPiKDpish&R zDN+#OXK8D5N=Cc#yDO`)F`DYwAy(uvP-r$aR7pv@G*uxEAQY|W9GPusL>T&_o!SZf zBw$=!X+0&AaOOZk@Y7GesdH;XJEK^$gL<|^Fce!@D33g^jAr=F1l_EUN`3LRyCSxz zxY&N)N42Q>(zHv2uS-?fAp5)<>zJUmCPNyu2a^R%qLf&69v18Z*`QLo$lBjsUg?rc z&*@&mvIFbl1Lz=^Orz~33`vY8VOz))k7q_B0)(#NL^+lUDu z9ZbeIv}Ic#ht>jRUQ83$^lx5kUl|Nt?d>2DF(AQ~Q+5(k0!X3LKs z4ZwMnub5BuR~kiDuPr$*?30;{`}S^9hLQ%j#u)4*z{0T4_qMb0j&m2eaY;!O5MkEL z@}$)(#b}xdX)9|TzaVr5sHBUa0r;ju_iZxII&-$Mt#nJU2$_F^niZ}QGiBU!+jFuF zBX{KPb=z#@V3U=XyPhq0RI9rNKRqb)TW=n_ zu}E#B({_SWO~FE-Qudmr0xpnXmJ92fVX+2+N;rgd#UgQv6s3WjvFTs<^2*3AypmD= z2|qMYq_+Cq47QA4^kkJz_}f;kE-|k%!uUvZk6G;^L0y%6SG85=>$O-l)xp5zmLU=C zT)?@wlIkz`sgH$vzrKr#K7XB|h^fN%GNz0l?^+z5^&x-|Ea$XzYNTvcLI@M#Ce7IC zy^=@qS_cK_xqN)BvvYOX#C-*ALviW#YAEz+ys)wn2&L5UAMk%A~Li%3Wg zxX#$p_c1_W*!m2=t?#d$x^ij1j*sV^5Tv5&t0XK(PpOQBv>7#Z6+V#3%1Q0Ce!H1@ z$bnfiowo`uLWbxu?=yW%D z=iV&&d=*JGwRm*-ayNY0P>o&vnGo9rwvHO$O$?#PzH2`J(98I{u~@-;qb=rbnK zo(LT!+;FZygtT*R#!b4i1tTXFudi0=O@z!_*0)yE?{HN4x@$IeFVuzc!VVqkMqz?T zyM^#pjLyz#r;eT+K(Cr#5lp(RD`+{Mtn30*LL+fy^6Oje+ z0Gpc~Inht4v=LsD;hpcHY!QZ96l~gF^ltrD9~`1LJO-d3aA(y#4!&dS z%rd7t@hL3{D~el^n)sdOUs82fMpPEZ)NVZ9wa_+CN-@25txTZOD~C1Wd@@HTHiw)h z=YT`dtm0uE1#U9j<>6nW8W<{`Q8g<|ek}2->vN`0dR4wNiqCxilJ)`Ga6k#j z51=>})p_5OtamQhG#iC0bQi3@W2)&e@{n-n_F+fdlaLUJvuCyU%4u{8&j^dvt1U+w zhaW?YcUj`^)mqvPD*^%*ka-@6jnUXL*7U}1#N)SOo zOTzFmV%&9#b#E8zJr_q-Ja*3NwD$XHYp&1dsO7PU(oIrxvwkw=+qe51w2e1aVC|4V zr)XR!U}EuCK&q@8A}LJq&fbxkGrW-oAh7}Yjg zf#bd*?XNY}xTOv=fxI1Qnrg}6jx$VBlHURf-E&(|@xC$O?Sk_K5pgap$HV!K9TTe` z6m2@Hk47gtvGd4-C%oQ2UUl!s^Nr9{b~~>>(J2CYgTpUhfrzv2xja82)BZFf$#b~D zgmYchBUQIHj1MEu1gzEMH9U4cB2`w0S5VdC2IGCKIug`|BC7S;<1X5UW{}^iua=gU zV_lJNPq8;M%yu@$9wsIwWk7dg6G;9mbGRpd)vTG1Z9_#TZ3&o-RZmaxsn=l{+uwwS zN;1{_mL%dUJe?`Bw{Hs(WrVYWK^=nSnPwFU3;_G!08TfA11twSen?9Jx@gvOrd?C? z4Jc#MvhUT_GAq2sGmU#nmL!0-Vz@CkY>d?AgAWR;XAmX3L9r5Ne*G#phi__R;1^M> zf;RgDeEPa9bTWl^{%+$-fbVg+yOSC-H8Pf?w15)-aaE-hza<^dsdk-FXYUE2jWXA2 ztek_s>El&Td!S1sHR75?>p{)4TbsYSJl_8Fz(u#s(+2*_0ZI_T%o72DJq~TRt91A{ zRV07U*a+e_K%^}jw-T>Bd;T0EeQc#ymQ!ES%-B4ULj2(Y1@%6OyXWVhaOGA-Ezz%GT3wuU>Nh$?h>Ocvui5%?miPqRfyZZZCBcd84_ySO?bcSM z$z#r$2S%xH63l5bP}CN;Su?Ji>ZTcA2{a zTHzZF?@k&z3x~sXgUUt?kD?DmbzyBq0fiHKoIY35`qTdTG@vo^ zij{mXT-Bfs@Njxb-h7D)xR~qj%f)7CWT2zG{pHN)B3v`lj8Zxw*Nc zqv0qZ=7IPyPF9YCQno2NI+{GkB@K`HHo4&TSI?`fZAGEB^X;GbOi`R>y2T6ZJ0>0N z(z}$*VzEqh%4*o2IFIQbZHAzVx8v$_4qQj3T2Ytr>XiUJYKE3t>e?|obar8E$jFh) zk}q1UrpIBYdKfQ<+>D}L+P-T(WJj?qWncmOi`QeemR@0pKKW~l{x0uX0M4nlxIq0M z{{tvuaj-8;4I@yUxhSTMHOEz0*%%rLRnEEaKlY$*-i$9Qe&1jM*cJqsHB+tQ);Rll zXV8Z-g->lU?_WAMIy4anz9!6&6?ZMnkbq8F_(V8~cVq46D!8zI=7>EPCIMUPo2sCc z5HTBgPj0+IP&C#^btT_sv-<%!J~k9wSdf!xI0mA4`?{)6s!+hX1mDrahiM%-#deiz zt-rHo>OCjzlM)oQckW&=5qD;;X?h=6i-n=*>+UGg%14}#WK#?6C?T^+h5|XwM$PRd z)qh3Y-p+2~opfn#n%~y_9PCV$nt4#!%zNegpVPJ%LRGMdcB%b-9>UDbdo!?qD{Ro$ z)E)MAc~EX84d_cvZbA$C<0M+Umv?QHXo`N>^qcAWzBkt6>qmE}GWubMui{sT-AGgA z5~ikL&p;Yivc`CXY*yjYpvEe2V#8OPY(<8BzCF^o*LP~>9*7lvxR}dwTWVgmtNcFb zgga-=xS|(FeerjP{TId+5Fn8-LFM}3kckPp#j=M$Z;=h0Q|F-RboJxUXoIw)g*&IO zPuDznZ7z*T3x;~QcM&qNEnM7qwf6v`qv=$W``U{~i5GmUYHMqU%a#&HK#yqZ4)tc$ zOA{JguTx4+?EG{f$H=$eu0oRTAEu~BqHKQe&2`w!t1aulZYCBLCvy6<6-6DBdkZQU zktzL^tl9dtr%mU-PWKWq^zb`&rxeqo!fJIH1hiw{NrDW`hrTZdH3r zGNlzf5x{kUrnuIQsjIu-O!FGWZNa%dAxXdW=^QfG*d{VNPQe@u0_9QwbY+kXSqGK! z#gSjd$YnK~xCAJFBA3OgvtWEIN@ia`_b=3&myFc>JlF=wDo$W7_Sdi`WV;RCouHnq zpeOH)h$C5*%WkZ3N9ki7hX?~;W~eWM?b{bS)Z?5e3EW%(UXZUxgxgaP56&0O^ z`a^vxs?&*7RHuZ_p9Oc`Gk&P0qLNwAc=*6D;M8Knxo5=9tmEVDZSVHx=3CT%vAnL` zW_WnokO698Buh5Kx)Hv4VvG7**a_uvmk0zSoNon}fI#$fG20auKc(l~mw#$&dnKlM z9~~ZX9B%}yP9nT_Ze2gOw6I&Ibj$@d1V6t|$NcMp>fHa$i;=%hcKuRMkoIPtH(_pn zpC5PoU-zi4|L210KTe?FodKC;t%CgoQ|@5M(z>V?HH0`ykgAm6C#G84MJI=ZQifw+qiiLdt6M&4yiE z40U07C|?5pjM9T?UT$=-K0b&i&(S9+zocaa%Uy9|4nqYn>PcZ8K_zUcQcIL+N#EK6 zn|A*6Uovw={o}O$q?Pb$TiI$@Njj@9GNQnaug@7~M*dk&7xb*jn4h6{jn#}CmMpbb zRzC5@RmXk2m|G_;wAB^4048q8G70F zx8`C)f+YwparQdtC5G{8;M}DXd&K(lAYR<3m2QlFa*D01pZAtL^H+4_>T1C@p^ZJ1@)mSW6#&n-` zp8V{Oh{d}e3)8g=;9$ptek+3oHk&KM5O!;Tgr|umR)mM~9^=KLnrPgEkl7k+_MVLI zLlC=9iu>xr7(qq|B>uYo3jMggUI^IuEbL<(MQE6-3-bv_t&#*k}`GFmSnTJr?Gf(xxQF;%>`ilnb~_8|q_iZQ+U zNP%4#%!ey@-kChU043?z+-gC?`f#0UdFQ6SlNaUa)JeHDuOAvo>=P>Lv@=o=n4uM@ z1bEzMyHPU8tJldHH+b%?@9fri237?`Mzykn5>>r$VA`6T z+k4LsF?JWIs4{aXC1oyiMaYUT{AKhl(L1JL*X18D&(5^k#Bc?Mu3Q?eaS#9N#q;dI zp#Hm~-C=L`@^j}OZtc%+E|&a-`Y9S2(~YH$DEuU4*+dJ_=n|F{TIWg7455pjLbJoj zu_Mp4np@xoH#8K@s~_=t*@b$@-*YWHo0pFkZjI;Y8)fTvBqbNI@>$ zrmV@xe0A-W5u=O@?}X4^|99De0SjkSB<=zLWadzcyUg!Od0B?Gyg>QQEfxpCu4%Y| zMa^2!@h=jxQyS9sOGY=h8!RaO+oiKerJAvAW<#WhgsN_dvaL`PY(I<$D##T`)VF#y z!!D3;^@fJOyE)b|om0_NaY@lcTVBdNv1Ey;(^ z!Jt>~IL!xc%c8O|)({dsgGlWcK&nN=6Mt(vImbUlz(iwGiyb+}svc+`SL2}${D8^m;R5bYQlk&{|n?_r%q8(`Mmy{it4_su#9z-fmQL zkaKT<%7$fgsESwbcrG;BPcX6OyX~lWz-yT&X8o?HvAiNK)1aw$(QNa+T-)xJMUG?l zLXa|C^6f>D5wZH*FYe#>U|3-U%d1@jJC*fhuj{LLt8Aysm`ZJNWIDpHOBbs%T^BHt z@TX@LLD->l+?Qq5yK1cTiyd21S&huC3e-h@wL!PPbNlwj)}G@-nx2cMAmMKcD3%O+ zb#dzXeryl@ykYOmzimbId!i&``&TkHIMzo2LWi83b?2$`?nICW$IxiTo+FNHDqXX1Js`_F_1-$1y8JM zxkxE#*(RifrxV*dlM zrF6AR+><|x?v>H@-dZV?1GZ8Q$c}vV;LKmAR1y*Ia!t-_44E5X-w54|zaukNqs$Zi zQ;fBn-=g%Xc7?ghr%&Z(Zl*q;A9X!yp3gU#W6H=Ze*I7d|Ec`M@6${azRM2+`2WyO0Wx5 zsmx|chi93vAi7MwHMfd{tLI%PVYPHWK?$|88tJWH4-&*HvE~_J`c1t?0$E#APspzE z2fB=);x=ds!L^jh5sgq0`7x@Kl2Hl%%qM6 zqlFpULRGrO`AticIcANxd9-4kTlK<{xJU`_b4^;vje^LWPzu)%eFH1}cCqyc!G>13 z=Vdp?=yXnrjP}Cs|C6b{$6de&7n`h!&aERDxk=A&3nq>=^GD~kds?dWbPjP*f`1}Q zT%Nd1>!H?2*ZCO?B|2i#`4D!b06Hu-`WEjEjndvhHk|swi|0dLWtAEUQ{3DFaIxBx z6$Ji!h2OCgwR_9!;W7yiP~DqYj#?rdfHGUdtD2GGZvY)4yv4qrOT()Y-0)nB&Ii(h z0-8S{KwqG9h4mVWCbfnu>>~wNJuA(TQoa#fXvZx<^?laCqOj&Ww~GEB4}FOXB8VsaqgM+Y z0jz#ho72!nDCzSRswbMe|Ijx}gLX-rp{67OJy057`jxsGRI!4EMzF?9T1Fjdr^AW8 z1n0IsGdIyM4Giu_`x$z`$^(GGl1otxCyEhs5^lskdx|LQ-g)F}|ai zA7(JmhjAvVv>h^5S7jaekwb7Ml}X=91_<1aA9>HlN60GEs4E~=PslV{Wb88qQyc(O z&vD_?Y()fn7wk}C;*>Gie=z#e-T8Rj5_DjUI@YWEK*rtP4g-N(W=+it>=6bom^ zWcY^d92}h12YiUV<#TI@Xo9sGDyRhAo$N^x5OC_vXZO`yWgYd>s>4?Y`2N;SN)McT zJ|A*Q*hCLaTj0}&;JHRa8XP@S6*B1za$MS1tD`O`^uQJb#*1rkNl${<))t-D*k`O_ z%fjAa6O)Zz-DiZ<-7f-YqA2G7a#J*ti63qk}I#2YUn zU;TFMy|;P>4%Y|l8mg+cEO!DjwfZfze0g+2($~C7!PGble={`1R6*7pvtTUJ1^6gU zb0cVTEBBMHU;lN28sKV23=B!x{C+}!g$SWp48W$`utGu*&Pol^qxpnhf45v|EO9V^b-DXupv6U=DrH6S|t<^{iu@wWRK-pEj@wXYUd(|$dvq*Pho zxA;}$A)anIHCJ>fD__;3-Z^Rp&Y>GTa>wyA5EN!nA{9e|KY&w=+CqOMigseu6Gz<% zKNh^2$5{Aw^0z&}AbyhjF<~8@dHV@q%baN@=dL+9BSz zEvBcm%D%CgiAF;hdn5Mx`|Jd21KVNCfy~V_gdu2m?oFdM0H9cwG>mBX+hG6sM#)wn zsI|2^+Iz+uwe&l38GH>Bhp~<)n*;T5LQ3V_)uZbb<%Pl&JM~SIbh>^FAz$fHX+k_uTN}NrG zPZ$s6qkPRi5sa-zWt#XIx|y@9DovS1KNRGtU1~;;@uP!04{WvCTKbw#C=oMb|3lw8 z1jR*4O~aJ?yw%aAh~sb zA93M_zh>DJgV+)|T#^x!4@)G_5tPMs}UL;(@EZf_6v zUbp@kLAW20I7dFiiOELmX>$iokZ12)^>xFA;u+XH8~NH6i~!}AUw0<+!)ZY6);Lbq zc5eOME8$jDVhC$F)QZmgx@V@+EhTM4Aw>$Tc3OP3w;dZw&!91$ zY)G(EFOX<692P*b8VxQ&RnwrQ9}DWV6UZ9c6r~i*LgA~C?oDEl|AR`9;_O8wSw;C?*DSvmpCHvfXPY7=>AYC8&3GQrVIr^gMga;ZZP0F@ zt(0A-t}F}WmYo{CAd`Z;Ip&Q}4p8ULt(mbC)EY4!2|mOWvpRz;Gj{?d-mzYw)J=2LxT4h}`)vS+^KD z91UOg&wUk_ZS`yTfZFV?&O=s{3tQrLQc_q7u+v{EIO7>XSBRz;wC1O%ArW@>1eZ}J{$oDB}5 zNBTgifucZ-tfsJy@M<7XNjx?}Zle~sLE3mnU@eOcX`W?N;@SP^ugg1@^mh4yhAXs= zYe)fLYcg;g+-03{MG_x-?Opc9ExVR-CkNDwC{B0q?|LB!Qbxq0hu41IR`{M_Hoayv zW7}r!zDu(-Gk?zl5n*Q=ln*8yT)T@;(NC9dkm6lm4%k(*Ie}8Z_oFkQ1B$brmA3jC~ib6r_n*0$WYS?&a)z%wsV~rX_(b zw1fLgKzpQsS)Ju#^rV=fd0*lDkTEz!FJR+|TGi60!x2W&_CbE>me&+b;dRdtQ&S5l zY8B_!q3D|e7@Praiov(H$?hDG7Yxmyqhh4ARb)NVDV25SE)I+F>UtRrn9Hcy@kCFp z*Kh#a=FU@`N%-RloA1A=XA_w(fODydDP_h9yzqipkjjWmgsQ@Mm4jhq@x{0elR{Q3Fg2OfIXS7C!K<&zzCKi{S_4Vl>%cSRw=FPL7H$qZrsvv4qi1w z=1D`=6}OTSLvBTqlsa!>(!0V7s80+UfjK%JE28>p*Egj|QS30r<`1TWEzu{8{_p;X zHrQVvvHI*Kqo6dXRUQ}{RnT9{cC1_5eMX3Q=^Rg8cec$$Os8w4v${Sl;OW3M74Ibs zkARb_Zg5$|OJv=u4G^2$6(&e5FyD|%|{#kXX}jDL9Bm*7R9$y1pCld{)=oRn#mcG>AyRY8MFU@D=@ zt=ui<6S$pDi3A^Ih%{u~_kv5sd$y6dCq~xNCWa`h3M7I~sjALDtk&=|B%y{-uS@sB zSYFC6as5gXLo;YLsvbAXopd5oQ9U=mH5JOIUz4;PH{1F^VWs{zl`Hdv3kH&<-f*r# zm{k#F&H@Hy*@4~@hM+%4yZEndVOoUj-QYhWAuON40tR8D93c|92324(gRVhqz+412`{iZHgMaikU5a`pu(G$sCz1-7?De6g<6VgcXFQytOQE2s7(`Oc zyaZ-?Ens?O(8f+3*}SzuSHu8uu(w-CLW?u3c#&*lIu^4#+N*SXm^+OAxvX298(3== zwwyN)+SoWVAII7;5lg<|?LG6t(+H@wU96h#c9p|4eL3Ia4|&eH#ASzh+1hp^?nqWa za^%vnq52KFEPW(kkq}xoFL>;luQyEBfm7W9#^X1}0y~&)&@t(eB;K ztG3PEi_{PaoKMQom5XOKf9EqZ>Z^mo)jJ8Q{&VHFhRiyVL<0)3q&0bn_j&e z;pEjB1jBjIdP$!{kkk!Q^yQ+0p8V>Vy5l}5HX%dW%+`_kv?Gp6^+v{RA1n`ITuVS% zzb8qV`u*-4h-s$@*;hri07Q|n8V+c`1KV1Y&jw*51g#&NzR|bZ7Kr4M%Ds%N9Emc6 zsycPc$?nOUO$$;t8|P8f`{b!OteHiiA?UKB00!QZFOEtD<}6pO>~cNPn-7oZ)y$|*n- zZZIB99JwH4ndaF#LtplD>*xi~kb%E$ZIZW-=a?-esSsd#Mzt#H4Ka39Yg3r!u_s~D z9R#mH;6kSO6e%N0&##5|jfQj|MSKg&ajcVT#0E!NfMkE_>6Q&^!Ye&XRyf zlox`XR<0k0tf*D_b{ZiE?A}uv?o>*yf2k)wTF$2li;aW+!6gX)NaBn4Y&C&{;1lZq zeB2@;oV@zf!`U~Xt^GsX5cpo6A2hsP^~0F9jHQ-$t(o$vAJr4dQ`)+TvJU`w7dk)} zOZD5TZhV_QnAI^zl(j7Hr&UbA zLN((?;1CDf*iIJHn?5y76}2S<#^KGS^d8BZ{@K7xbOUAOOjN!f8}`GG3C+ zI$Z30U{wk1Y_?+=y*uX&{2iu93D$iExDdN&djR?S`wV_!61SC05*+T)UAHF|7z6Xa zRH-&zDpGx%iJ0{)2@7PqQE`(b?H~b4Jk~o%y%` zprGy|Wm=u~*In0v;)0{q;1pGb`(Xtlx59so&xVHvBET#jjaPq*HXcQn}G&&!157G-GQ;v5-ub7~I=j z5{A{+)iTbm%`N0-H56ow2363|`0*8gEHnvM`4Qx?b^Y)lE`u~LDB1%h;lhG7L=RSq z@F8_`Hb0tzfl}cDwdwis?S{v?jn~*k8EBgELMm5j!fj3A+T7PJHeZ8uoqlzL#ilvx z@ojD!A(bl^BWz>x9A7rPiz)5>``>S!JZDu75v|m%4chAbv+gBqiGReyV~0FEQd8^L` zG|`@{FLZ`tJAdNi+y78eT}FXQdB6rM3qXf%3>e@?gSW#LejqG~aj5FePTVqaS3KWk zwqz8!yXA^CsZ`zEq+^xxOD?He*N^3tQnh}|Buwm{7TnyOZM94F**mS3de=0m<2~Q7 zc!vUxUg5x`nZLPewtblV-9V=ZxW6ElvuL5@5Z)NehW&^{n$@lkDV7Co$}A03!g5qE z^?biD>ANHeZC&zf_4^j9Xr!Tm7O6jAYf;zf8UAEXS;0m%zIW7r6k{nL{IhRlq?ms8 zS$0ZMpW~nGyq1oRo+*v1zJJclVyx;FXkcZIXa%IKPLVMV>gAQ#7|)JnbN{S5y5Ysx zOeb|@$itI|yfb;-6i?nu6;DY`#roUVGP|JdREaz(}c+&%JgUS6y5{2#SY-r>pmU=ve| zs=12z;|CU1K7FJ9V*=poZuNXERB;b)9AasPGsH@3mGS@*(tZDUw%F01jS^MPBtt3v2L4jeouvwWQXB(=(sHk61T3TbW zDXx!B$~mx$v zs*d;KVoGg`u-KlA^xdKvQ4{Xr6sS2@d=vJ>hn1XdiFT&*g{bN+$I-o5JWBRZNdDlr zsBKKb(&DG~Ug3(-o5M}svo61%hvJF7z4IykM_I(8ey67PQC3N>_qVjK;aqT)X6_ZR zayIt^w5Fz}r1jT(@;hH@@or;yE&nm!dRKZypA3pG|NVgP?)12>Y7lZPT@nV^fmJ*x zce!rCcyoSGd0TPSrR{LaARHCeWh4{UC|xyMG7L2H&hD7+Vkdem&a z1Z0KXi#Tg!Y*d8Dm?<6&b}*JZZ<8?`;!4d%oj?(WvE2Pobu_d|l-!pp$}b|K9d^|EHt4YJ1I=*#ai-+{(e~)^o3FrFg@d`P0P>omb*nM!Uf#X%7K{K`dZBiZvw3!- z`EJnE`EaENqrv0KLuG+QUnx|JunIA#-d-efh8@2@dvUH{+rC53&WI4by#mFh%a;W- zi-84N%dIDwHc6upw!7}e;8WL-eR4M_0X^Mzf_Bxv4@j)iPu{1!iQk@Yk_xc`iJF3a zysvF%=c}iuR~j17vjjMd23BMHf#S?@#ofa8_Yx26A}=lEa~@axFHUa8$CP|NcoMF3 zL;h&0Cn&hjH9RFb8J(N^$fe({Q!p${8_my;lsXn4^*_Ezv$6YZZ#74!?AZ^2+MTcB zEv;>OAYHGIl zj336&1#HsMVTv)vCf?qkz}t4!3+d+!Ekhrcw_uzPcPp8Rd60aOs;3bED4QGAp=c02 zzQt+ixS%nZ}uzi<1G1zM#T5our*p*imLlgvR2Z3sgc ztiT3X4blaS?;hHpruq{0CcqRX13BEq;-`y zCTS_EARWH+DLg}Pv1j)aVdoRQ@6b-^{Af`mlgOTV6B|gC^!*Kks^uXML2$0I(ixQZ7Q^67=sKDbKN||7_Etim^c|TZ*X(9cGG%MAC*B2|VGrk0pLcK|Cu~u!1daz#*b53+ z>s`;U_iER>t$do6Kx+u#*$T%R+Snv5S0Dd9sGV`oa;4@FbF>}qkera74^UKl)cA;f zrED!{vp`WU-@70q?_(YW+H!XRMGq{ot)60Ip7ZE9)N|01HCReWOD^OM-Tvsl*F{H@ zTK_n~wWzikrf~U3jZvd^Tqu_DkO2#uo>T~2I$^okVR){#x zI;-)+trVKz<54MctJMfUP!=C3$|7=yvSTWsc$hcj?OoRqE?E4593#`Rf%P8+-}Yb0 zlqHBn$(FWeKte7}d$E8NE-$RiQvu|dfK>}kr=i_Oe|(xD<6z%n ztgoLxKhHYt-(fJ2q$8iR;qa*6X|ieV+<3kA+M4BHxx29LK)gqt9*9HFmlub2EIIl4 zxAF=KT>6*gXK4|>{{E}D4g7f8!w!sC~zRswL*fnyD4Bb4xK7=Ju~djx410p zk<>J43<48U{xmA>B*e+$yiPz|kQ*X7i$I7XFR{bjC@WWQ!c!$&=N?l;Nk(XuI;fb@ zQ(ryIvFqzMWyaFBkI}`&A~Nvt73h0AQ<(%hHmO$l`}c(zM>~;Xjooe)BgJPjMBr|% zeSc}C%9t*tfLxL~o;zy_=gH_O*k2BNSe~OMKxrU&xW5Y)1JMF968xe2!;^}8BjO@Q z;YaFomjqnfhUh3QLmbqkOlSP4kC75kzZL#o;BdmTAI^h7N6kfzE95?Sb#~4K6dnD6 zkDxjOrvl26l#+6RHE0h;(6I~I2n|;GwdsS71W<jy zYMQ6U5u%hKYLVrb5AyR9AZNe>y0gD74%Y2232rVgN(0i}3s`;@9?L0fX5DBq|0c0p zES!!-PWRlU>xKhJ0$Z8=(ph%gN5>3`v;oX+rXcuT`(!!!7MDyv`PO{RegX=`yvg9;s4XQcKI-1)8y>4w0bi;q7;?nJ z%41@D>~aezB0`(|bEV|t!E&GgKfi&?KmWiy*G^HERkK!(ykrbE7?DGtDn9;b^Sr#gGm6!mqt*VC3kD8)`icA53#^&}0a6#geUZ>= zN6B{3cU42>koj3YD;#k@-ZI~KeOYNlR#xf!jqf3RT-mblwRi<3zpVgwW=_t?KR$S0 z$lts=@C~CMPI2tA4Qp3<(OB%{G}L&tp0+ILSVT6RDMA!4EaCO)UQ z=4DHZK#ip+GXKw7uMP5+=A%ap+fQ^GsWcVFnoDhX?}hALk5t@$iA27=uIQQQwz6V7 z_wJDpE$vJ~+Q>O8WWII9@Mq>=g`?M^M`WIsUo7!SDIXdtTLftOnf|!ws)qS3jWy*# z5d6DSR4wmry!vbgYi&@@Slen8=8LfIEP~1@_U*5PJKtb7@Q%`EUHEChzK< zoqN|Wg~3Hb5$IxfM&BJ4mB%PV;7#8`&X^0i~4TU)TI*ou} z(P~hwBQm1NAl?bd2Z(j2%4=ExPRm^P;5>-Z5}TAC1L}bqxc*GfqRo3@&697M(dsJe z#4SmlDDRY6eskD&I9B$y2U2eT;L1!}SVZDs-hpf1RacW^8OqbplDvV?4G3!BTBi+dn-+uy z1|3}=3z^jt>MsdYUmT@vC5bzb2E~!Nby3NSqSI{^ckvD#p%Pj-by+VM1!)J%cI5#G zmiPttY4Ttdx7TN9yA38LVsYxBM=?81zu#`5&Le%bbz=mTQt(H{A6N z67N9+)4vi_TnDSDH}ac-D}JWJzPJ>P{Z3F*{q&6VR!&;}97H+friMnyN7vCU*%FKD z_JT-7hpq`kz@H!EPiI_tqH2S!r9)eVPn(O|<{Y5+)P~|AdL`o_Sk{uKbzXtn>KV*?gz$sYh+MBqUzH zyDW8%K19_t@QhkKN7M4GnwuMYOKwzrwi9ceg8YGW2FKBLs~(Q9i_-)%4;R@Xq?CQbq^cWE7z{ww_Fyw>prAP zdZC=maql%Q4V|P1n#n;zFlS5V18Q|Gjcno4(mT%rr>+qQzK_Dh5DWeC0rPC1QLT&* z$LpffWPH!sJvBaiDN>R8T<)Zxp!YxU#6*3N(UD?~om3!>j*zYh|4NI-FFwX40cW&& zbeX0)cmP7t>LzDjcS7>{0^T^w% ztD#?4{FriF$MC2&Sb5I}v2%2`z)mX;X^J<`&@$&-i+JIL^f~*DWP4a;bl|%63`BA9 zAmom0m{kmpH(!fKP@MXI?^ZpIAjjKHI%yAgms*q39+qx7^@i8TjJVVUOm)bh&Qcd=nG%MeON)R(M$%1I3Ji^gBKeZEa6q&j>LYGT&GVYeF*;JW^B zq(MaJP(Nv~&W+kS>js~WeRVTjQ zxhJI4k)ix#<@7TrWP~X2J4ba}1ZCA9>xkVBX`KX+6@YQl!?sxRF8o*6Fgbt>N8 z;v&$?Zk2&OQ$T-x9&Ok$E5*OFsNT-HF1yY@9(F8G7pwL8l|@+|fxrmr5}-s0t;wdU z(9u^tq%XHG4t4uXp7pC{|3;efo2eL|7|)}v53XT zL06yrnHiXbl+>ixnJzdS&QxNxeVK_VqQs>vZ+Xb~<;%ZF6SHbCr2EJh_X?YPr~{MO zSS_C==f@SpBq1#=se8dC(X9yF?SrfvpUofe^R66WgfXeGIie!ZsPdK=VqLt~>HIXUNh z$29K#x7n>f4W6f^%L4DSSC48tbV9Le(w<;y7z743&5N{?Uu4$l$Xo^}3t*LweWW-VvNnfso5?`vQCx;}9e{z6S2ABO_x#*G{JiV8BCH*VaC zxN+m=(|cIplOp? z`gZfxnW$cg2{R|>11>Hu^m0!J?lp%(51jd{b{Q2XC#P7r<6IPMt>j_T)#^i5wXDUN z$$g_{m)nAZf?2*#h<(t@t1F+mG&;M|&AG^1Cby;gr5eb?&FUg?gpd`(*50>KV&{IAl+sb>LVPteHlNA>9^=9)t$;hejI; zIq$N!wlUUbf0o5rT3S-A$ioMtg4%Rs#a-G@HDvz0ZT8OC_qhjy@Qy2(uGe?R;)cbn0wrGzJ2d#_uybTv*!HwnqhR;WTH)le#5fX zW)fpp(4(`tY44P$&u!~{plMgPKYS0me!iK+rX`2fJVKzH6d@F>nqITN9jC#hd+bR4 zdb;kedPGi_+Rwt{>5vcduLuYTygH+3x#-E%K|j{~s%w#rLb1AMtO-_^vvZ+G=l@0y!U z=V=3zfbJg!Mnkl^7#S8HJBEejmPe9vEvQK7SHr^-U~$@lPIJqRpLX`=ejXRRXu>1z zid}SZxf;afv!{Bdg5rSx9hBtySuAhrSfO3ejTn+vw8;;;j22A8CL$-7*EDXV=y>y1 z8d;od>Jf?$QLr^Q7-H>i-kSD$V-kP`*B%<;xpN5Vc6cscKU^+LtT~*-u2(Ssm07P; zE?&E=tSky57|yfAvUg@z%4^ptqiGoV<^h#xAUt=f%#GknpDyFRmU@{@o6^k8Ox%Z! z*< zUdhPICr}lMdM<5Qv^1B{?mX~~Ongxc3*aFuNQ9cv-a3nrVErU2})*jV}h8szDijg;*^ zn_e`?(>P6YbH@cRmZ_?ZT+)7nYU{vVbIr6bb&~RdC->#kg=|DJ>knC&FZa6i!DVw3 zcbcx>?lg(MyRaHPUxIKu-($1dgRA@G_=r+D^y#vDYzVS48#f2ucA~}Q;o)J*udNjV z=NZ*|MkmI_#XUKLTkUF$!WxPf!1LnFQh!|KovpTr-}p z7gOOcN4hVWVjgK{(FTi;?V2aLKYGc-4SIDBn4dg}Sbj)L8>^1|n~T*v6)o(wEp}_? z0Yuh7AQLPB)Pd)fe0aui6z;AEwb=m4jHzylC11_ro42!-ZP0?Df=u-8GNXpa2hp>@4H z!_Ro@R4gZy6L9`u_wd&TXR^R5$^;U<5+kbv1c3`#Qx(O+>&yATSY3yDx}ejo8cBqN z1x#=IwfzR%fwnCa#%G4Rp*UQJe;UHzOt!r%dJ$n;NW5U9ixNZ~1 zlQ^#rTRqXY9mzA+(=@cVr^3u~fPn$cU>f9h&w8YA@EN?78_!=UCD%nd=?U$a2R@&tUw%&kX*IwkA(3Uq z*!lY&)cUU5)|p4fbANxaB^H$h#Y)Td{PvEHr6RRV5mn3}y}BQRML7i(VeysqVAfK~ zNVChqgo}XvL~D;$z6*=TMY?$@dd1(nKIoya+v@M^665-T%5}Gwr;`SoLr6QPR;gm4 z$WvT0i_)u0-{D-B02(Gv&KMS_%9Wb=>mNmp=MP@RGShN(nV6Vlq0*sAIP7aDnhOl39 zZLK$avESq}RmA5h?BZlZqr@b-l_u1T+iDm)ky8hLF6@8eC*rjsZEyb+R&3au`uz6! z!s=>7U5p|kA_@y@2-BC1Ac1M@;?XckhaTYlp>2P2 z^&FK(jk>t2%)PPeCI8r@=<&d-@y)x4Bu-iG*gJF>@9LtNLu37 zd+vNOz4qcM^qkGJpR2|38{4%DYs^!liBw;Qi*=((v!RZy+M9%<)^rIhF{_FiZKd=B zxyB#1XKG!Dt%=BNnVCV^;Ld4h?|4Y)!UR&8wR<<-f~U|e-}5O>)^l@@b*k<<{yy#v zus(`?V8T%lEB}U60jsC5{?z)1&}zr&aB=XlN_>h5Q<=z}YV*mi7DN^3JBu%?F6V#H z_<1_M8bB#N3T+9tM8&<;VV5wU96p_}7+y|g8h?+wFkvw}rH*Md?cV@E60I)oDC&+R z508myp^1+tNBW=Q!1x{isK5M5nmx?ygUC~*cnC&qJwS!1@|wSyMS|Vdw7S@#9JcRW zp%;YuJMjrXl^9Dp2J{{Mnm{Ow#1DCo`j(-Y?m&j{y(~d|Xo+d_Tj)g1H66@h>N}Ai zCckEoSuIf^A$L$<-zcEU9gUUi+}F7afA62BUMyfE_+5tG+j%~h7%!b2QX)vnY&%9q z655}<{CfZt;>odFdj_g`KX{^f>^;Y~sk6DlVA-2U z2<0@cQ?V8G-epJiCiAl&8655T!?lcz#9)>7Ly0P>yym6lR!Z^5lVXUyo^qWq3qHFc znhLOgCm8*ziz@BsLY6U20QFcim-{_#^P5K3Rn%Ljep`gg!5rAD;4@Q&zZnV;2je;Aa1Bd;>%l1|l1Bk0AC(lwL%$ie`U0 z^e$AusSG|fkR=)m!TOII|cvO5;6%yV-_Y{Z9eTY#fD=DTv8JP1xQw?wT zJxnqOGa^wm0pa6-x$lhMB#d8vMF(&Xm~d{$C1|p|LGPw&UMl6qpNVk+AEQbQXO}R$|h5dvVHbaT(e# z)>|^`$J5r=M=Z~FdRBD$k3vAx+N#K^^^p6t)} zq3e0qKqGgd!v-1X4A_F8%qXs+)XN<9Z8dxMh*^1ZUdOlQR)RVeKjpd>hy@iB6LZ$3 zr~iZ)%JE~R`r@=h6XhQy7G5REhbRAcN;vL^=gotSYfklu`?P9ZQ3R|Fv5Zdj>FlvYMCG(l5s%lwId~Kku~^Z!6^iVBl_tDQ;WBM0(lhaa$e-AEnBS3WlB9vc z1}`33YyW8;+~0%yT=*Xd#RfYO1bl#q`?<@>Fd+exf#ZN<;(jSLNGa8UjW@t~G|r)! zu5RN*#&w$5F5qxj9|x=5jj9%v39r)@~NMDk^x}+uLHj*cPm2a=!-N3MtJs_;?R@ zw6}kUZDA3y8;wXXpxgjL~qgBrEG7#gO4+XYb(n&pI8bW<-8ItIc!Fp5B<)BPKa>dcHYS z`$_Wl8TMYThzpt=<^Ac!^NdMQj1ckczo5B!gSw?T`XixYz13TyJd7am7lo3ZdT?8e zgz5-I5|xszwcX!IpqKWg5uB$zC4~AO{{`c5b(NNBZ%GrhQwcuv=myM6*5pZ4dOAB? z=sgZ8Q?an$k(grH^3n=o&-0p&MlZeQCELOLJ2BMmr=2)vWti*g1;HSBckBhTcMB2f z&vZTZz*7;Cv`Wi9GWPy70hx?n5lMvro-&vD9&Sq6J`(y!0D;2jHb(R@I~zkR2Mns# zqx3n_1i@+w|I_-vG?+?XmD?SgpdRa0^9}q4JZ<+1n^>_Z>-lX2uMOT2Ld=Q`Hd>++ zTuH~mf>q(Ujq^#qCPpkO+lha99+dXCkkZB@oLE=xO*^u{10FLoQ#VO)DeVHIw?C>m zeD*q|gy0~!08lZP0dCoyB~DqeQoj@QTjTTR&)iE@zf2Z10O#nKIi)Rg9isq!eK9c0 zhlSON&*9m#$UL#0&X{Z|b7URGoyylrw!8bqoe2y8mu3e_ghB$&4*;}9f{8Hnz zAXDO!(mWefcq)2cGy{i$2NS@I@`?%yKk6(5$EoX)TkpxQ`ab_otwkc?+CHvpi-6+A zy%hC)|1(!ZoND1#Y0-2DO3NctqNV1I#r4K#>PG>uRTL~7wqC9c-(ZkV|X#`M;KXK>nzECNug1p%Lq7mS=Ufb z7n@d-2tWn&1rcN%Q4qIP&UTH^SPEs=31UzbzUdXB8Zq4&AY$QtGPBa-(3nm@m-d!e zeTb+;4#dehLlRz&73#F~_2DX-?CtGQNxBe0&yF|3!xWUbF~ij;eU6KIJ@b>+8oq;U z0PThZIv!X?2;k`m48;u4s9&bgDK0(*_KRvMRe;5KEatN~5IUcv^idN?_B--`IX}ud4#3s=5ZKw@}Qm*vH$Mby$0~8$Xe(um(xAG1DnOrqiYImBfMpJ7bt+$@8Tf zD2ktw_pFIWD{vPU7VPKDJECN116n`FO0;Nr>i@EMgoS|?6#>R_FRWjYApccL=l*%xPJZh@h*T(c}A1N2%W{TMFi=;a1;yM*#V`DD<*+V z0KJB{3C>gHNv#-cP9ooSY2f8{3$eoWig*QF^j@lfBY<5k8&?=|_DI-@q z^_!SuDU0~E?$yO$yyUeTNKUkzASsUA`~WQk zi0u0d_EYo<^A|jC_OPFghAv=lvAJHbio%kLUl9BG?{AsSQS;&`>Nj5iS5Ru5)aivV>$0&E#p-sS zWhs%ndvGLx);2TUwDJg1Yn-FqqcTh=i0n`kitc}jLUJ42)&?x(KqD$J$f02?sJ^B| z@S9K2ZE7HGshh6O9)?%Bjv3gFIVfp%bagE$bj4;b58^K5ChqRugZ1nGSb8-R!iLzp z>h_9H9Qvxrj{0U=Du5XtJ|UrEIWcR{r+_X&p1xUe>e+fvIu4K(zsHLWbC1!Bozdx_ zKBlk|5|XnWm{PHLl~{jI0eS?6tH`!MyHlaq#}!AB(NT~1A1I2)lXIELf#k^zJ<6Q3 zyvitGN{)=goh-LvW?&$y0Q@KV!w1oQnEEH@1@hW#ar*Q15vPYS=8v#qM#1J_a{&5- zh?jjNILAf@Fr_}dB9^j-2C)K@VjTDdrV6K4tZ^?jwp@Z?+W_550Zt_u2w%NP{awDh zL^YQbn;=m3ILj$MqqVh(<>{uKBm0Fpy@Y`fF6hE=+pRU)rTLF{kATvYtJVk$+plw3 z!iqtUYNehyUp$T=4@&hjZt{OG;e@}qyTCZH1fFIZAkq2x*x^-<{rQP(rqq5i@?Af_ zIgr!Ht5W>R4{FYu8CSXBIS?zyKW!6UCH`!!`-x1l2y0jd)AlYA2S^0gn;AddGD>#8 zs+kAACLV8qv|67pWhEH?XyO0>CfEn}{D9_y(fJH2!McI@#J%J&hzh`8d*Tji=CQXFFGYM^@?Y z_!sd3*umFhgoFoG_*iYcx1tdAF#A$YXHWY!PK$v=RWadHAU`Hv=tT#}9PDuv!%r5#z# z&FKn=U(tm-A*M);lWz!^jdO1JYW6mXr@IJTo;4^|eM{nDmyfyrzPa=Yg)>R1WB^h| z7-kQM);~>vn1<#pgfSFfN`|QlBb?{qQSyWW;mabHK^6yk6ucl*KlrO?ZAo3%JoaF{ z@ez9a##5aquBAZDN|kV<>60*iFRqkQECL%!- zXI~4G-5vZfDJv_x2eKKl^4Ipe^%@qY-xO%ZpYI(Ud;|*yvdO1#CmqDRs*dS>eZ45q z(r0Ez7G48H1-^6f^6H|Yq1g`e85B#n?YA5tK%mcS%~Ak3%BxNwa(@5Sayi`t5LS;_ ziibEjhS<8oY}OTSi~d0SYJ8PLcQ|6)q5f58MruM_q6^Tz7`RN@qmJd9BEuAHc|kvy zvAK|=Y&ttL+}xXS8!QJMYWUqZ{=XKwpROg;|I9gf`})FPs;2Y38XUk!kdk^tZ;cx> zje#KBjMNFoXwEm2VIMoKWrJ42lM|)R*50s&yWB;iOg`8`2)dYx(p0VE-L?-+!8r{_ zQ|w?uirI6xepbDS@yov#7Ty*Xvd6?>SZbNY8qM%K8u9toC}agIBU@MVb2rpCP^7?~ z!Bl*jf;y+$YS~x#eqz`Dfw~;mVU@oD9-x9v@qj&QqQQ8#v2KA;nubWqXTAu?OVY$mdwVTCf-5#?X+c4 z>_@JIDD*m(9>?K{0~$ zUZ2h1my4l7@eR3iJd6K|MSS>xJv20gSkS8wiO~d<7X=Dx`=n0k_>+5fGqpr#PY23s{>U|Cdy5#wv+>Al*l_w!I z>a-PUzDW7dRy)kUhd4TxbYE^wAh9d{5zCd5r^FOe2DV<$mypI*u(?q9~zM zgoM_B@H1Q!0XZ7fDX^tChstHOIs>^gDlMmvG7*P!zpsRpN{obg7ykid}}gF%E%lYumCO@pv1NNoc?l((NAJel9y?R01dm_*m15w5lE=t_10n>?*E;9 zUkPjLEJhrHKIfch&@|?gafj>WJ4h3MA^lNbG6y>>L)h(syolTCQ<&JHHcQ6XC+T?$ zxfo2*2Biz04Aeme^H}8+PK;uULU20*>M_s?()@~5Oza&_lAT$p#PiudKb!b%h6d$h z6VlLP>68^$LT+T%{BEAyu!D_}+hwNd@^9;V zXU!2%RJb=Tkdi$KqT~b^FDMG^97T`Kll!RNj8nGu!2bEy?d=5l`$Cvv3aV~WZbVGX z0zfqg4e;|s?0!e~z^aRTsg*YX!>Aywn?8};IRGt(AxH@!J<*hcJQnEoA%J>`Zxa%l z&0huc5&U`6C>Q-m<8Cu!v?eLfkU8klryx@Ch-I6m~vn&qP`jzVllwR?0yne^d@se zhlN3T9uoSr78_M1n>5aMnhpo#Ec`Xp0lBo|q06ti+l0*R>S_5wPI9l>KU z<&Ap0v=pt6p+p4|1`T)>Xz>(sAln(;1LTJSiqT@j!YVMMd8eAemkGQ*vr@ zT!2dlj5V{)gMuYOLKitK1_t$yx(*^0euEO5Rm(ZTBwV_QUEu)a9^Dh)Wwz*4Yc=`O ztKC84mUh!2NqjL(|fRDbvaxL7}maBJ}}bSnfT zOg}TZou(abuQ`T)q+?j3|D_JPbF(sukC;+cVO4yIS>gPN;a`y6Kbz9Bn zE(E*^m=`hab*?K<014b&STn>Ygi`Q1zSqa-E})(O=>i>6pj8T48@yK3yi>U?LfqdTn`AvCL%_ zr&srDxc(tUlJ*JbTSZI4^r+H@)gcz))F5nzu&uvfmm%-J#FUJau6EIPT1xy5t$#O6&`WWjt zKPp!6I3hfp9;jfeTrZ_G?^NUehysK-0c!nVAp4rw?-I;1@cI%jW^v7s8fXOVdq5(* z14M}K-6=O{(erqL4VA2NimrI$ODGsI4D75V-DJlixY&?MZ5nVr}GS1+3XeplXS z8)?n%D&=9EaYjJ?1B%eXoeTwGk_wiqZ<1fzk1BCEL1z`#XB=o25-9SKZD zCSXAl#*>GKH7J120lsowu_XzQ7dO-efF!H}XSN>SuNWZu0Xp{`0VW^)Z`!%-g&i(r z)73>RYye?}K_`#_)G?L7Y8v>gTDS^W{%t~NntKI=fi9<8*PHej+srSgY^Rv9-1epErz$JduT;IFsl$Z&^SPaM5? z%<}4LCkB{TB|X;2pqUkAFz@WqYQ|X3ko`3^S()}pzyKA}JXd;kh;kGb$hj>*0Nr2D zQ&^V)@Q=}%HSbpA{_fw-RKPbB3{V3OOnhPmFk2$X*i>vMt&C#<5si7#d(ID2%YJ72 zvho!R7s(R^SMSx-4Exyw;es#j1;0|EZbS^Qe^jLia`p&NwK0?9+d3$1j(^0gMAB6> zKs{X!q&rQmZKFY8Lv_r~Qp?-SeuIqUMKLpP5lkv2KTVBKtl{>?a+t29V@4v8gutu< zH=$4!EN?KsY_z@4X4BU>_!vDL$&?S08d!Mjp=NRLvpXIZQ;9BN2#}@m+b{lMpPA@Fr((9Vc_h;)#!5u{K1g{<< z43}_olhsr|E}@>NJ9X2PO6WYCq%D8H3};n$1X~sK&(!5aR&`GBUhLAHO!FOJzSu_& zZ2x$;^E;y?^;PApE|! zVvdaFto4!pgn$6$h7_kutSuLqdgu7i#Q+yk3Ik+E0RVadl*NLrYpjI@LZK-jmV-Nu z@Cp4cflZ7F)Kbp-iid=X>@)wY)N+rhf+93ueYqOkGsSCraOZQ5g>wkJw7K`s@rO8O zH<+dYxSt3_uw0TaO5zfW5R}(OoaI6J-G%~76oSx2?Qml(FR*b6<3hjlC164?%ECW8 zQkw#_kv+N#TqO`(Vb6;@#&w4ay-9@=>>DHM#9~1dKr?2j0+h7VgS~d^l7Iz$`WI}0 zP&^0%IHg?V0r%r`ZD*@-VU5}+cV%RTaZ>J30)xK)iQy-f8pqj1GXUq!LD}qC0Y6$~ zR28}<>gfjjIhv0C{%D+)fUiJ}>rLuiih4kMm8A}7utygS2#Ze~@c$MKD=4;Z-|hzH zAY8z>!8;s$-vP)uA*TNUV=EEs&{nG^hHKyti0;d?JsbgtIXQ426HR+tTc)iJ`$&nZ zGJ2&?+2A{9vB@0QQC!Uzp#WB6EO5M0bfMb5AVs{O6AchPnD&f_>@k7v1443+``@3T zG1OB4jDrW78>A|F!frb6?mz!$h&2aa#5TIG4+El)Ip8rSmtg^Ze?)1R)w7HM{{RoB zP{5>f^)WY_1|GKYO@hPSVCI~oA_2wk98&dr9X?#0||MJ<)U37@4fV%XrLwVXT0$ze{ z@yWe^eIO5x`uC&E1#r5I-umLpNq4@8=2|GM!{DjUEYgtV=Gf)w>64%y?Ry*Jla`An z8h5mh*VYOzn6>DO46PIHch#RQVO>l`v!*rk``A6Xq$HJHR^01KYZ|4!#J_vXx}rgg zew!$O!c4zR?dlmcy^0sxf74ya3tP&UQz;-1)bwfUc8)^8B`g_Zhh7A{rqzBsjI3 zfqiYUx3>}{PcLAgM?~^6Wmegm$EEWLa2f|B_5J;Ymo&nn$CK{d9k)6j)>p|Wi%_FZ zTT(Df5FK$}7AbEqN&>puP}LV&nqM&$?h6bOQ!c4L`EEBNRVTpN2e%nXJ=&8HsVe9TerbD`Fj#ea*!`RF zBYB*8i3p{weB$il$6EQSpPlmMmQ}poT1BRCA1`sOkLHmTSnVhTQQ%*|)wb*y*z7K& zkp2l@$o9Jqa!qCpMhguieLd_J*0jPlQ;m>qi%WfwSSkC;5~j!BIqOpf%on3{Kx$`T z##4PWr6S34mOS$Q-{H`MW=HE~?2$d2vCs@hA(fLO4Q*S=%oBUrR1pWF08xGh>pI-0 zh7BP|+91Vzfhq-#Axdn`yfJSPk9)w#;sBNk;hM9@ZAtmIPGjYsgz)QMmcQ-)S_OZc z>q9P$!Uv4*wpI_Y)KmUWPGUAJen#?ESM8hxgI&4W;O*mEDH)w+ zoLir!S-W;GUooez;(zwODzY3*iY*DwC#tsL8)DUk9u+>4q4BbyJeR&YBXM3a9Y1iy zTV}AF$9#BGFeBi&$|Kf$ykrvGR}Xbv^}dIQU_@>5-Wg7LC9_g=p`1L-*K?D7E?w&S z@UoWiYZ;ecQ0ziXa!xXlHb1%7ourCgt1+_>ywV@nMze3c4);KfM{eydCs&$Y;q_r# ze_8*&H8W(_VsOi(x7z_g{VSrV8g*`Dy1JEW>E|#ZgM-+Xq7&+(PvSPFj{L_IHIkv` z;+<|aMx%YVl;yFBEBWH;jl_S{Pv;<-?F8&L^qngk8AWeH^oI-YTqh^-@bmJ%x_WWX z=m)i(oEL04&0*67M$Lt&NoZY~U8}zuh z9?v_Hd7V5k`4J{^THF7tOX^NdKyXd$T@xH8`{y^{yfK~6C-oxH-2a=P>C!phG<$oY z>?wG%;)@qk?qz9B$EfU^^n1RXH&0Fu3q_0b7Pr=-|Ia{_kz14~&gh?3^nxmK>7Te{ z#eZ+CIAsL@)i_1}K8j!y^8VNm`=0*8XQ!SOwQXLzx8z29x*RUq^(P4XulJA2-q_u5 zJyLrn)1Hd)a|h_PBpQ@gmP`_wZ|&6Fs%3ooz<)z~$)w3c3Ce3p;1$yG)+y6=rmsfL zMlN)-MI-s@)?5JA9^K_HgZ07rG~IIN2!DG#{byk8K1(FGu&S7P12}@&E!z`oEh-JP z(}0Xo6K%iVx9oW}3JBMIpri9vq`rT3{H#?1=NS`H+o2}piL@>}{5+hV;;SAj`7ydQ zD~UgQ>*lS~{Jf^yJ;v629pbMzVg!RMNN9Ah_vn%r?Cw(zTon0ntN-kj3fanh zlUwxq0Fnq$?v=-Mhavzg-KK5`Gb`cj6hQE=_$nt#Yf?V zf8Nk!PrlfmS&c8`Gqdv}3V4jrLe#iy*z@kCu;e5!-hBF!I6Sv}!eU0i?YGKbkIkOn zHMe?B)(HRVl|S@Mswe5*7-$qjuP!|Y6cW(h$Md|17TV%3CJ(4qUsHiXbtjy-2dr2f zXO(%+&VU0AQi-^_nsw;IJK^^}lr;k^#6dQ^wrn&%-tor+Afkh*I9lZ~jy=OUJWmPq zdm29@V}-7m)6T<|K8+t#a--?I*v2i`T5ck}KME09^|#{3HQh?BP4_Ad{Qj9M|IIDL zZ-cOUq?=(ovy5iv7xAbW+75VJ!31k)edd>Sl*NImSx2I@*6wygTD|2rrHS$9GqIx1 zP|h@&(?H>xyJX9b!hdgT-J657h4pSD;xp@4yx#AI-J@fNN6g>=F0qpRmmi1jYu$Ut z!!sgS<`3$j8F?xKzSUr}b|Z;CcxuI?$*JtkjpD?<&P(dFcV25_Gt62#tZO(8svxmsb+|UPrC=x(v!u<5H@{YX0$~=rze@9}i8QQS5zGZM$(+Z+0|x&z((i@3a!G zl_iaV3&`z3vPIJww99nr%g4V%71A_Hj~==Trv8X@6qDg}JZC0qaC1)4l2;G5li2=a zTF*?wb$6JFYgNoN7NYgUnLM^gOu>cV#P#~-Ym;pGa0xdG=e5A{n?V`TH(cpsOBI(k zmS%nUuigL3;E}kmWq%G5?hJnUMDMr2ysxVM&`8S1sp$K-#ytVz|2MzKfM&v|_!&wk zS$lU>u0pSr1$)e|$m!zkE+*xpqAje4Fl5-z|cV z%}uhp$a|!-#?ClHWa}B9iVPKPio!BbLhCpY%Z^TQlOySU8GGLg+c@(@?vA|cm~xo3 z5@2%=C%u=Tap#u#l!0S#xLTLvV~X^4XZNQf-I?3XLZg}j{P=omjmLpO`=;W&%96)J zxjt4V-Th9PS*jLm%jw5P>EUZJDxMFRQd5YjCR6Ciqq?1rD{|hFC+!sX5@073A$e5o zTtAltDQW4I?{64trpH4vE&Hdsc+cxP@oH8-WiyrfD#!RZnpV>&xqf9=E#2w8@5!Y+ zvXQbfB3ffP6?Ty~L_F-i7jn^5;EtZJDX^HoYE6J;y$@G7aT$Lt7Wao~9nlRFzL*Pj z^^%t-Nk)zK(y(kvUM|a_)`FgzY4j4_v3BydVjhPPVu_3+Bh9~_Lvp}b-D*9{a;o*c zu>R`oSQ=iS9(6yl=mn(2fz7h(26O4Ago zNhD`m@u|1r)hqc}EVVipCv|H-JCSL_k;%EVfunKv>Kd3od~fIE7kHN^FnB&eY7GlI zK6iU(ja`1|jDCBKNU)IO%d2SQ&)h0rw|7$g*Xd9PcerB3F!&KRMpsQEXJbEjPHJF) zdSKhgZGCifj?eyQLswL;55#r9I@_|A(Qk@(c`DKkb+X5p5S$L3$uZ7G(SiUoidJ%} zmS$(kXZPKgxQy3P+Oe!CjwBy`S0-l$px?!l1pG!=tx1%)g$;+5!y{yYl*4w)&A6IjdZtUiJ zedS7Nuye6xZDb~o7K^)@y}dp!{;!utF4fX>IDHOFfA?zAto~e?h_#A6YT@$IXYkw+ z^{PMQfHNz7NlrB_zMs9mqQSyr05O4RJ3rsgI-W(eq}^XP787Jug`_w8tt9CCb{Vgq zECxgNhDXrt7U!;OpDEFM_2vXo=+Zhno2LLlI(-0J9gKY@H47Qp1m|6D>y(~2YJw(4C^5_yEhd|g?xAVR`&8Odj%b)$l#Uz$E~9^!ix zIDPF4V6TUo8@OGq^!5mElRBj(4VL~Hu->_HHF0TqFT86?3C&+gP@FX^znJPnH>CZy zla&LcjnA4VGUX{b2mANAlpWNP(<)IyqJ1fx%3Zxx!bW}uS%DYv{*HT}{Z9rC3&ewT zb%SoW7y(XDyZ#nl)cGh z<_dM8TW5PG)!cmcal=k0-nfdw&LUFSaY{5V*J9M_{ zV!7H%*!!;!^7Z1xfs=h-20?Lu`=nRy>97k#Q`AREME4-iZe=P;#7;$^_+-$3cP?!? zh*o{1KHY6-UGc%DLYMUUNcl(MwnI-3Yv*1R%8??So^e1TEk;KaeMpPXB1xUv6YX)+ zo2T&fHS>*&9_q=-_>y95UJx=YtRvW~S5E)y<%?BI1C`);+A7uD@w2JAUn^G}0R_#L zy443WX{P@#pC>qsB2)5KcDM$;s*6;2IvpnKD!3#sWR1y!u9Q!AMJb^(2gd^WSNZ4Y zz3J)U#&4{b>QPE-b@-`sai$bJfwptL1B0%EDYOGenE^Kb$MVC-S1Kagb4kf}`ypr< zQzwB<%G6{#7_2sibT-vr){Rq?wJXD8uby}DM~VCF`7?o_AQgj>462js%Dh5RpMDf7 zaCYKCNp+PAk-p<-l3C(6ZoNOyXjAw1tms-rIWT3bux|fPs@90>%5sKwzF?kJ%M8`b z{UUN(N@R z`sn>sQ3sXnbsz3&uWh9*yEWKSre3Z^$JzfeGcDWuxcb?Ht`tCotC2&SdU@gk3S}NlRmiZChnJ?U2&~cS-8HN{KdDn+k`zl-pG0Ew61)~Tv7gIZ9L6SN)pwj# z(4$&YilP=obvs)(u5KY+l~f(;SG3ma{5%ABVC_B*EvpswVv&-Nq#957*f<}Q7@w9)^WvEW-@2Nkk{!33lGdGZBTi`T>w>P39Z zFAD>}tzX@L6DBZ{WPg@D|{8953<|T;;^ZYdt^Ze|~9n8y< nf6jTq_McP!6S4o#66P8k>w*DYuM!2rq@1FxnoNn*t9SncAim`P literal 22801 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 W-FKg^bGCg43Sv)JKbLh*2~7aN3?W$n diff --git a/test/interpreter_functional/screenshots/baseline/partial_test_3.png b/test/interpreter_functional/screenshots/baseline/partial_test_3.png index ee9182a654d1e8590a02aa23d87abb57144a51a4..7b96f3ec43c7ed6cde24842fb49ffd41f02133be 100644 GIT binary patch literal 7054 zcmeHMYgCh0)`oB~;UX9)0%8FPNR)6_%!rb{c*zBX>L|+96hTr6P_QaRE-7zHon{b2 z3~~`jM?lApV2t6Sk`k>r1OcHA5F0MHU=X8;vJ+4jkwPw2mF zi@R7i`^m`b>9M)M!(HQv4{87W@?rExp@*EqqrTiL?~T0rY2^0kvJ{)2p(8(?*h_oc z@$IJ<@LUfm|J!QpPhzqlnQ1N*9=iN5NZnrhX}LN3Zqi1YP(8P$YHN3CMd`r(@y=Jr z6ICI}BlQt25En-Qe~Tll$l6brSxW<4k^TScKxx(wgyS@<*4EL~&Y|th=Cfi47fiqr z*?q2)#wYHD8}Ta7B-#4w%CfZJBw6XiuN?$lD0U-r0QZU3S+I@eB4r|z)xcmitkaiI z`YOe8E}R~WCR`R$ps0Mb45_)!6*v0ey^SwbkBe6_^xN?j65LQeB|x)h_rw4 zx?BOr{g5$5$_ro$4AuV zi_hnWc15*#@E`$i6=G_+U(;X3(JQ+|u7)TPFMac?oJoAFY*p*8>rZS5dhZg{@$x7> z!K&-JrPi~foVGUUmn^X;e4kSMSp95I^qplvZ&sw#@Wi4T0&_s~c18=Fm*$0*)yfue za>p%oS5@Z9#y=;S5^CTcz8?~%Wo1NwGvF1IeZp&yP9oK)>>9=O&Wc;6*8hZmvV8#i0hbhef^>8o%< ztTYB}3Xe)bk3}ZQZcWyL+%;lTl4Q;N5*_?v2S-sT5eeGVdD}u#_}z@a@x6Wu*akyZ zOVFN`KRq5LFMX{MDUapMN93M~zqRl-uInW`;`0v}{__c@Lfra{_OXh2I_U1@Oik84 z*UpAZOp%p-9>s2XJrjTP&ff(6W}#-H+5lO>$z)=XRYt)OZwXOqr_+nq6!C6dXv zLl%qkw;i`5RFR@RKe&GwYX^Ei|L+G9$BTDLl&>b9he>{1uiH98Ey}XyLmIqkaCDP@ zJZRQh^Q|&l#X!K8XN3M~tPLc|OqOU9d1U^6s~jFUwnz48|0;#e^@P1_wa>u0NO9I9VL@I{v*ncgn?K z9R}HrpWk#_{o=1)!r4nzIV-!jOy4;9%kA8I<&WPyJlQ{zWjZqPHm-fUE0WEXei@GQ zJr)ak$Jb%PV?%W;7;LtD?-iQV4dV||lpUi1xpM|5$FEhzO9sFAa-ffodWkY;vLF;8 zV11Ecq!Z3rALZN`pIEkRtr{HeEq6+C25whWbaKql;Z8=t!EgjRS$>UYU@?hU0`J}- z<+h#lRgW4(JbL-@_w1E*Z4ZYF`{pa>celj3H;8(;B-s&aHH?GzN^R+7#_Ds#Tj|o^ z`mDTVVPVA5!>Y{BIG=Gr2KbINaLs zV&J%d;7=M5-Ta1Yh&II9nvre$4py$c5=~UF`ql@|{D_a(Rj=pF!iC?<({V*X88I9i z>Ez7wEP{QcLb$JQ&t2OPyNb&3M=`_K;%wyXnw}a_ct&T9h3{|afug9G9vgJIIGLLkOo2=(WG4&-JZ^}s zWmuwEmRKieLw8G^EE+5aaRV|Zlog)j#P&IOAjt`4kXT21_}=?l8W=M3{39La0Frq{ zBux^bJWDjk3`>SgFX(nuP~KdG1=~5QABi?IuJN!oamn9p!U+)>nJ7MzmZrM_JfjGf z8!^BGG+dNcOTvB#N5RrQxUe|fy{N&&+P>>TzF86t#ruuhfQh@PXfebdG&G^u+VQwH zXpj|ev<^dgr7o@lLO;*ahKiN@VpYYZxFJ*K0lqM93J%}L#*tVJI_;gktskYSgzFs&kQz}xo|hKN>f!q!EDlaL8R$WQQf-6ADSYp%?+8bN_p`+~fv)Uz zjiQ;veiVK0vAHb~s!|yrzImGUyTyj~nc{1j*o4kAwp@k?0=& z2RnnS9I6Q*%P3L9QTl<7lz}fKFZk^FlrVg`*m57jcm=B*TzS1(;g#JtFU=X~tKs!R zO}`iN&QY>{q&r*MEA#*YF`LG+0j|;TV>%5PwaVe5w(=HFw5dBUJs7EC^u(0E5&DhX5e0QFY*Hq9Q#JdGW9uDNz~kv7@3+>!!Af3B zB;2aSovF_(W2ggg27!Y>+ojXNgG99YWp||e<@9sIxldQWdVip6Ub;D#AEACc9dus+ z6l;_ma>g);C5DtGP8pVWu0VM#Nh_90(xE9Q*IvymZ(Yo;dUoqoZdpKQ^tG%?$?Jec z{l&39uP=B|R1!YHJBfxxYdPspJs`6ht)?&U7h*x!|I!Fw^WUeNnZUmdfOgqW@%b6vIUTui-Z8MCy^4G>Q?`2`sCMDi>(3a zpDMlqsz0+-jI4J3el#hsh6FV=<<30netmjzeATQn?wxJ<=>-KXIp=A%j>a!r^BTsb;%P)-x*sNOMP8 z-0M9ZlxG?jO}bmpdriF9+?xL?cVunmtGOzbx!+DxuayWsFSf;{F?Gdpv!8nVGwnmG zkzg;&myGK8V{^bvE|j?esYnI(4u6j>fkUwnTJhZ{NLkx%UdV%wm=KgX>jP*I7BN!M zW@Js4-j}BbX)0dRS)Gi#ZoZrc=u{c98++pQPu=N3B4sqT%k^aJ?;?765ik(z=8UmZ z{4Mv*+Gugu!t*-Y_(aaBv|{Au<6>SIeuV;I0ng*}8I`l=E#B*wD7zQl8*QCx@6PbG zsKqA(9>g5}i`JXv{uvhxThfMY^D`?qZ6PW;&bFm4Of8!kIvmuV)A;mPVI9FpZe%Y< zu?-_r_BmlhBr=3DgwC~h`djQ$l@>UD^{v;uuz*M`wX?IVCYb;Sbvj}yF~tSLWPuV= z!+~A%XAh~`mR)HW+_KHoYgayCkGi=nb2UdL;RhK4R~EmYN5W9hmq7{hH&^*&e5Hg7 zVyE>iDl5}{yF1l)Z1ZuT&VbdmZXeC^+ldx{Yb+X<=G*-OI1)SUWT)u(=SRnGtiF*= zAGr1k06kC0$TWAwSfX*>wng5xKrBQzr%Mg$B2G-O_FSom)rU}&>LPukuaQ`6$f(8v z_3s|zgo%d}Fm-ix9G`Q(o3;#Jz0q=5;vq7n31|#p72G+Z2SD=ZXF55 zrm54-(m>-rf56(lzMJ+q=@w2&Rk?#)@;y0bwWszHiA1`+l{%+=;ZT@qjE#*6D1Jxe zLQDWag&RhMb2gA3!iJvw& z+ z_0}+C&7f1uX*<8)1l&vb-ytop=e^ext_^w(T^df=X{z}l-W*v8FHgT_!%yfwAE~)1 zkwDu|>=2I_&$>bwlcO{}-n8~(bC*Tn63J}-7HQ#of^yI6wQ)cGciJzzsm)dhKMp$* zVmi)$E)#l)YOFO1S{d$#Fl`+$1@5!_8v<*r^t>G+d3TG9P^<=wZ1?!jz3lSq$~DN{ zt0h+&^MEfFvD2K9rDMv(L!htkX}Qn2!c?=erS69ouxIr^cXr|C2mBj!#QQlnR?z&< zbP%hYf?f;jOixJ2kM|Bn+MqpRn%qbf2<>8k zvZGtxwFT{mY9K=jHz3fZ6@%Njlk<1eE`-BnX$*0tON++0rzu;fj9$S8a{u7W20j}WAJ0`2h~=J;)-JsF7{hzk$@`+@nmhr W-lz9Dga5PXQMT{eb_F{4^}hjykH1|2 literal 7049 zcmeHMdsI_rwubMF#QcPbiK#bw1GrQBmw2&>iDNYF!w>$H zXFB~|@}-BzkBl!ZO{}c;m5r7PMhN0K@$EL*-tsR`O5Uc~AA9!C<9{m`RsV3yCA8vX z+XhFJ#hF3u-gfCn$CLPkC;zp9a%sz^W_u>_Ak*5BzqyQja_BN^n_S}Pl_UR${OR_t zsG@#{^JRB#&D?xiaVMzp;CNkJ+Xn(X4SaN60ilLp<;WeCmEn3YmA}41@W;OzzGV)E z?+d-I`X>c=_6EcELI3}?h=jZz)j~S$3xlM}5?<~uhO>k;5vu4`E!5);avqOD*$^o5C8rlU(Bo8TR)1R!mei$%ci2YjEgtY&9t(#`y|~=4k^DG!Ut@ zH03U=K|{7bCg5igi_INSSm8|-KMjiHwiJed^RvjPtDxIjrlBj{Ym@Fw7vs0HX}5bh zf2!s+ImtgV=$dfs0}@CwNm4#8Rq=T|(SpJ#QT<`v^b1d|4x}mNxG-)*h|}%fIq#0o zmVA=p!ngyXnkK}UY@lEOM^|Mo%~ayIV=_j*RTg$p!il!3%%5=v2cAz2+y!K23OOlB zN-gJJLjHrl1+(Jl)e<|jVH5B5bDE;V81%qeugOA9LeSJwEh}NT6o-;YNL2Rebz=t~ zkSeOd71#C?Qf*Sga6|Ia$hD%i8&&G8Djlls+eP1(B^f$bojO^&r*m@r$my)9;nHp` z**C{Y&NDYE8dW4vh%#_TB(2c+7LF&YbGx3UI4@oSGP;&u`f{EYrG8zY~K?UV1XOVCht4^Pck>CT4q5nm%&&_R$G zVXZ@m3xZXgFPqklIT+5MM=t0en=V-FTFJ=2qMI*T_E`1RO?qvQWVA_z6tQq4f}lwM z?H@B{FmWm@%9I|KUlDa=c_QD#<;75jdV2NI`0Crv7gFl_(*xf#MR?pvV}v`C@_GwPOc ziUCX{BvbHIR>yLWvg6j3x{jN2-_FI!f)^XLZ&0gr6J7tTkrCibJC(p33HAZCIh#A@ z&TSgm)HkoUlUdifu+`=BdBRv)^CQ`j7ob9yV4y(4REK_~v~r zgrb@Du47qiGiN*h+YrI@>zr`CC)4j=EL@&Nvd_hgyi+UpSvsr%S;0pz{a5t?sVSJ3 zCr@;T%nb)FuZHN}MR4+rwB1Y(v&iSuMay@QE&LSFaeZyhX3f=L&F9O-jD`7Ah3Cp& zTx~AI{M$`^P_x!<7ClWyqILJ}kuT$t^UGq_R*KZ^FZ*U+ATrvvbhlSm%-o9G^Ds(# zJfyE?^~JcBv3k2`v8LiJBdWK8z51>x#wQ->4O-Brm%6oO-FMBp#~BYomX+hzbJxo3 zTQ3{)bSkb=ypHTMVJKm9tSgdT?Yb}b9H9kY8WhPTXBQocwb5ruWS$v<(r-&p&aBGo!!?7(cajO2mr`S@LZmb?G)HYXXW^ za$DnzM}3?KWC~V_NIjpXj)1Z?on_2~oRQX+dkdK_E_N*hb!v|#Pt=cRwvhm`Z(_QgyrYJY>#(GBa$rSaPZ^u=awSBV__xCcl+E$Y-;Tk;oRThBG+#A(S@{Yd>%+co9*Ee8O1ArK%5>wfpb>nIOsCuD~V2ce!@vL6ewp)th z0+7GN**yGB_>gt(%m_2}c1laufqv}_x4UgnTFaA4crhg^l)ATP7gk{VN66oe)Oh!s zDV&c_&bst)kwZ>9bE|NxgVZjRi$wf7BQU&G1k1h7Fq`+q`uOUFz;Bmr(AEidm65%q4_3|8$Qv6l zuOd+GTbL>C{Q9Y!S`sVHQzj9LyiD~Z#%XitQ3@7&?#}6}`_DoC+fo$~=d2(Y*;IK# z;0~;c(kg(xHl-%rXsDSC^HH?DW;Z024Txe;G7ANu0)ja*je?6BKJpd-V z!sel9Q%l%*Am(wALbwU9zC`wXhp(`z^|FTR9X*$28ZNigVeIcs4{N0hWWK={oAHESCMt9xc z67vZfIreQ_o2Zd3T0k!)nElKvulINVG^$?pzL5d?-V8w17 zLNQ|(kD1xG^A4zI3ZsTMqi+09hE}q;Vg~hJ11F?gJP>0E|4IQbIz;{q2%Qrl5wNNd zr@Y-`dHecRrMNVr1;PdT{3}SpK@sk?$)2|4{N1mu1W(e34oP?>?1O;jAo(}18v>k9 zRAXYKXWPA24r%H`qI-5R(B%j|ezHCQNo1N!Bw4nm>@ibQ@BD}!cF=9)r(d~Z>``2p zxW1`&l6yci5VOuzB|?^w1=d)5d>YQsD*}8<<6X6Ke0E}oZ|61pe2?V&HC3yqi)rD* zr7@nu1@fy{I@gevEIefdbCgV&`SHvse-2})262|) z=*W$s{Sns(Na~VYbE)FVM|;4c_m?(}_uFV|>gMi%s$rJ6f-O?_>#nbk#m#1lD2y~> z%IknN7>Uh= zqZTbH3m(=L@ypY~h#6RC?5m(0S1piRhA52mUnYTrLRM1Ve>xoU!%p5tRp!^4okpgg z+r%+59Re)t;rokbA>BpGF00#C6~0;=HMwBgx}@->z3%Oz6H8COEqdI)5NIHiSR*{@t{=sG6j)~lu@8*|YzdTdXiGFSUU z$S=2C)a3;;9-UP#tJGw5BPj421C#%akLik87;{7E3ohd=@n=_07yWQ01W{heRgZIw z$M%e)HbmTU9^ntM))(JeBB(tfH+XJOw80_SvE5|?PLx=(ouy&YLX8&qkC3_e4tqQ( z2YEtc7|&aKgelB;nFJ!#p_S{79T1a-1ssyv@q|H^b4K6?Sdh%zA=(e{_>116@7XKg z)~&wpkMZB8*nuqS1JN8g14^u@6BpaE8+)mlSsOFQE-IjPR4y3`O zWVF(IN~GSKkf+1=&QW!@S6W-*&smv!@=?G6i1DDjojMISkwMjnacB0{Cwgz!jSyDW zFtR!MC?~gTqECI5NeZsDi)W*JfJKGHwGdVX7?H%8g8 zFEx|`tmg=gu^Cf1P&`2oYmRM+$1N%qnAyXX5iK1p`@6;)L+pmkj!9*_4W3leY&G5# z#WgSH!gs8d4G$I;zL=OvTv*M@ZRyB@KsqP8nHlp=q@435UPh*PCb>cW%%tw~+j7r2 zQHbGp;-d^44Ucl!$#g(p+cP@IuuQ5gUUs!ThInfQX3c;;$4HNFhOd$(_4DOH;- z*+hvZ>}b<=Z-{Dv1!coLH3#qKkgB*@Qs%eGeFw@7A$zIoMJHN{SNFgPaScO$O6XA-kR&6Roio%Q($d1QC6+M7 zjChkTI;40fB8=N4BUnkmkb%pI0<>`kf&e0^G_);>dxKBL(p`-W?t7z(zX@&MK!aFW zTkRlPN1ub6?9f2>xVPQ{S~U-V*%k#sFrOnKW3f^^LhLWDmwo}<1P_osZ7FG%ymxHV zFkD;E5WzKby**~Mz#el;S}28RN&%JHf)a|PSZnVJ;(AS^qtJazw&yEbl$|H7MT7wQe1;kV41h`t z{4_k!a*4NsmBP(&V;=*zG0zCB&pNP!KEjT%jkArfRGHGI#BfDB6IsyzA!F<<=psm0`bG?o47o?!^!14r zNCjo=Yf{u+BsG8isZtK8-B+o_hmu-{MCETF9EGJ{1=^I#YhchRZyLFV(0j(PsAi3+ zp-nFxUjL(lWfYXA?54*Ln??PD6;!*?70jd=eQzUm?=lOBAjh{4mU=XWjDW@!{OxYs z*Wa7So;;jq)Tc?^ZK9aUSBmVbNt5)-KQV?5@kBA;flH)|bVOI|EzvQ#MHpkuTEd^Z zU=(zsiR@aiBYt?{!jLo(GaxE|9kJz#VuxZArX2JWJkFcQ%YgKq4Cf({hdsJnKce5o zh&1kX2nqLJJTknHwl$52J*5*v|p`2?g!n9T7^wT)?Xn5xlm&OH0G>@J_w( zp(JK;k}G7MGgYnWglld@?{?pTO;zmTf;LFi?yugv`zZo>kv*i2|2MSduyeDcM=0CJc#MgRZ+ diff --git a/test/interpreter_functional/screenshots/baseline/tagcloud_all_data.png b/test/interpreter_functional/screenshots/baseline/tagcloud_all_data.png index 03ffc7ac7b1a58b38d4e54eb63ca198a3101021e..a7088de3849a54e103677f10d3d4224921e8feac 100644 GIT binary patch literal 11827 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 XKy)i_PxajewpesD4K>QuZruM5$4YVU literal 15701 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 G$o~LiX#uhT diff --git a/test/interpreter_functional/screenshots/baseline/tagcloud_fontsize.png b/test/interpreter_functional/screenshots/baseline/tagcloud_fontsize.png index 3a7315df405dfe11b32534e49b44700e67e60d9d..8f93ba81ad2adc2d9bfcd4645303607fd962a233 100644 GIT binary patch literal 10314 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 bZTAH~-nel&0XzVK!A_nCJ>K-q=^y_K?U1dR literal 13808 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 aaE4jCpDjfCnrb^JYCK*2T-G@yGywpYYsB;b literal 1920 zcmeAS@N?(olHy`uVBq!ia0y~yV41|gz^KE)1{9egI&&`r1G~GYi(^OyVv%M>FVdQ&MBb@0DHC3!vFvP diff --git a/test/interpreter_functional/screenshots/baseline/tagcloud_metric_data.png b/test/interpreter_functional/screenshots/baseline/tagcloud_metric_data.png index 5cdce6929667362eae577ab636fe34f789debb6d..98890b9687ac95a52ecf4a4299df070eb5e86fa6 100644 GIT binary patch literal 6712 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 mFo1ISpN??m3@2hhkpk)MYIF} literal 9163 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 diff --git a/test/interpreter_functional/screenshots/baseline/tagcloud_options.png b/test/interpreter_functional/screenshots/baseline/tagcloud_options.png index 394b5585097a24da5e3c10ed2c1b50bf8a9cc1cb..479280f598aef7d02121f8874c21c223c9f642dc 100644 GIT binary patch literal 15100 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 aHE&iz8^0!7DlG?zEGmi`4~yiVy!s!XBR4+) literal 18998 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 V8LWggH9(gQQeyINi$(N5{y#)d+qwV% diff --git a/test/interpreter_functional/snapshots/baseline/combined_test2.json b/test/interpreter_functional/snapshots/baseline/combined_test2.json index 98c7844e41f19..84203617ff853 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test2.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/combined_test3.json b/test/interpreter_functional/snapshots/baseline/combined_test3.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/baseline/combined_test3.json +++ b/test/interpreter_functional/snapshots/baseline/combined_test3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/final_output_test.json b/test/interpreter_functional/snapshots/baseline/final_output_test.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/baseline/final_output_test.json +++ b/test/interpreter_functional/snapshots/baseline/final_output_test.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_all_data.json b/test/interpreter_functional/snapshots/baseline/metric_all_data.json index 26f111e9edcf9..9b0122c157481 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_all_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json index 7f64f97845191..2d6e756a7f0a3 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json index e171a65be8bab..37c6885d76cb0 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/baseline/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json index ed8b0b258fd90..60a0e450906a2 100644 --- a/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_1.json b/test/interpreter_functional/snapshots/baseline/partial_test_1.json index 8a349aa5df060..6b2f93b47c0b2 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_1.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_1.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_2.json b/test/interpreter_functional/snapshots/baseline/partial_test_2.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_2.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_2.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/partial_test_3.json b/test/interpreter_functional/snapshots/baseline/partial_test_3.json index c1e429508c37f..4241d6f208bfd 100644 --- a/test/interpreter_functional/snapshots/baseline/partial_test_3.json +++ b/test/interpreter_functional/snapshots/baseline/partial_test_3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"region_map"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"region_map"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test2.json b/test/interpreter_functional/snapshots/baseline/step_output_test2.json index 98c7844e41f19..84203617ff853 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test2.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/step_output_test3.json b/test/interpreter_functional/snapshots/baseline/step_output_test3.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/baseline/step_output_test3.json +++ b/test/interpreter_functional/snapshots/baseline/step_output_test3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json index 1325c7fbed03e..ae1e817424cb1 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json index 2b063b518665a..c0da479472880 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json index 6152fd406961f..c5fbcd63b0685 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json index e4c6b09a264dd..b67b074449403 100644 --- a/test/interpreter_functional/snapshots/baseline/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/baseline/tagcloud_options.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test2.json b/test/interpreter_functional/snapshots/session/combined_test2.json index 98c7844e41f19..84203617ff853 100644 --- a/test/interpreter_functional/snapshots/session/combined_test2.json +++ b/test/interpreter_functional/snapshots/session/combined_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/combined_test3.json b/test/interpreter_functional/snapshots/session/combined_test3.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/session/combined_test3.json +++ b/test/interpreter_functional/snapshots/session/combined_test3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/final_output_test.json b/test/interpreter_functional/snapshots/session/final_output_test.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/session/final_output_test.json +++ b/test/interpreter_functional/snapshots/session/final_output_test.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_all_data.json b/test/interpreter_functional/snapshots/session/metric_all_data.json index 26f111e9edcf9..9b0122c157481 100644 --- a/test/interpreter_functional/snapshots/session/metric_all_data.json +++ b/test/interpreter_functional/snapshots/session/metric_all_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":2,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json index 7f64f97845191..2d6e756a7f0a3 100644 --- a/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_multi_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json index e171a65be8bab..37c6885d76cb0 100644 --- a/test/interpreter_functional/snapshots/session/metric_percentage_mode.json +++ b/test/interpreter_functional/snapshots/session/metric_percentage_mode.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":1000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":true,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json index ed8b0b258fd90..60a0e450906a2 100644 --- a/test/interpreter_functional/snapshots/session/metric_single_metric_data.json +++ b/test/interpreter_functional/snapshots/session/metric_single_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"},{"id":"col-2-1","name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"metrics":[{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"},{"id":"col-2-1","meta":{"aggConfigParams":{"field":"bytes"},"indexPatternId":"logstash-*","type":"max"},"name":"Max bytes"}],"rows":[{"col-0-2":"200","col-1-1":12891,"col-2-1":19986},{"col-0-2":"404","col-1-1":696,"col-2-1":19881},{"col-0-2":"503","col-1-1":417,"col-2-1":0}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_1.json b/test/interpreter_functional/snapshots/session/partial_test_1.json index 8a349aa5df060..6b2f93b47c0b2 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_1.json +++ b/test/interpreter_functional/snapshots/session/partial_test_1.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_2.json b/test/interpreter_functional/snapshots/session/partial_test_2.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_2.json +++ b/test/interpreter_functional/snapshots/session/partial_test_2.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/partial_test_3.json b/test/interpreter_functional/snapshots/session/partial_test_3.json index c1e429508c37f..4241d6f208bfd 100644 --- a/test/interpreter_functional/snapshots/session/partial_test_3.json +++ b/test/interpreter_functional/snapshots/session/partial_test_3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"region_map"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":0},"metric":{"accessor":1,"format":{"id":"number"}}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"region_map"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test2.json b/test/interpreter_functional/snapshots/session/step_output_test2.json index 98c7844e41f19..84203617ff853 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test2.json +++ b/test/interpreter_functional/snapshots/session/step_output_test2.json @@ -1 +1 @@ -{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file +{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/step_output_test3.json b/test/interpreter_functional/snapshots/session/step_output_test3.json index 310377eadd165..af9fe198d88ea 100644 --- a/test/interpreter_functional/snapshots/session/step_output_test3.json +++ b/test/interpreter_functional/snapshots/session/step_output_test3.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"dimensions":{"bucket":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"metrics":[{"accessor":1,"format":{"id":"number","params":{}},"type":"vis_dimension"}]},"metric":{"colorSchema":"Green to Red","colorsRange":[{"from":0,"to":10000,"type":"range"}],"invertColors":false,"labels":{"show":true},"metricColorMode":"None","percentageMode":false,"style":{"bgColor":false,"bgFill":"#000","fontSize":60,"labelColor":false,"subText":""},"useRanges":false}},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"metric"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json index 1325c7fbed03e..ae1e817424cb1 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_all_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_all_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json index 2b063b518665a..c0da479472880 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_fontsize.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":40,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":20,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json index 6152fd406961f..c5fbcd63b0685 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_metric_data.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"single","scale":"linear","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file diff --git a/test/interpreter_functional/snapshots/session/tagcloud_options.json b/test/interpreter_functional/snapshots/session/tagcloud_options.json index e4c6b09a264dd..b67b074449403 100644 --- a/test/interpreter_functional/snapshots/session/tagcloud_options.json +++ b/test/interpreter_functional/snapshots/session/tagcloud_options.json @@ -1 +1 @@ -{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visData":{"columns":[{"id":"col-0-2","name":"response.raw: Descending"},{"id":"col-1-1","name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file +{"as":"visualization","type":"render","value":{"params":{"listenOnChange":true},"visConfig":{"bucket":{"accessor":1,"format":{"id":"string","params":{}},"type":"vis_dimension"},"maxFontSize":72,"metric":{"accessor":0,"format":{"id":"string","params":{}},"type":"vis_dimension"},"minFontSize":18,"orientation":"multiple","scale":"log","showLabel":true},"visData":{"columns":[{"id":"col-0-2","meta":{"aggConfigParams":{"field":"response.raw","missingBucket":false,"missingBucketLabel":"Missing","order":"desc","orderBy":"1","otherBucket":false,"otherBucketLabel":"Other","size":4},"indexPatternId":"logstash-*","type":"terms"},"name":"response.raw: Descending"},{"id":"col-1-1","meta":{"aggConfigParams":{},"indexPatternId":"logstash-*","type":"count"},"name":"Count"}],"rows":[{"col-0-2":"200","col-1-1":12891},{"col-0-2":"404","col-1-1":696},{"col-0-2":"503","col-1-1":417}],"type":"kibana_datatable"},"visType":"tagcloud"}} \ No newline at end of file From c76519e15c4ca10c9e2c7fc5b0dcb8216d7661b0 Mon Sep 17 00:00:00 2001 From: Victor Martinez Date: Mon, 3 Feb 2020 14:02:28 +0000 Subject: [PATCH 03/60] enable darwin for the node installation in the CI (#51705) * enable darwin for the node installation in the CI * refactor: avoid hardcode strings and customise based on the OS flavour * fix classifier Co-authored-by: Elastic Machine --- src/dev/ci_setup/setup_env.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/dev/ci_setup/setup_env.sh b/src/dev/ci_setup/setup_env.sh index 5217fdf002be9..823c70e80fe7c 100644 --- a/src/dev/ci_setup/setup_env.sh +++ b/src/dev/ci_setup/setup_env.sh @@ -56,23 +56,23 @@ export KIBANA_PKG_BRANCH="$kbnBranch" ### ### download node ### +nodeVersion="$(cat "$dir/.node-version")" +nodeDir="$cacheDir/node/$nodeVersion" +nodeBin="$nodeDir/bin" +classifier="x64.tar.gz" + UNAME=$(uname) OS="linux" if [[ "$UNAME" = *"MINGW64_NT"* ]]; then OS="win" + nodeBin="$HOME/node" + classifier="x64.zip" +elif [[ "$UNAME" == "Darwin" ]]; then + OS="darwin" fi echo " -- Running on OS: $OS" -nodeVersion="$(cat "$dir/.node-version")" -nodeDir="$cacheDir/node/$nodeVersion" - -if [[ "$OS" == "win" ]]; then - nodeBin="$HOME/node" - nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-win-x64.zip" -else - nodeBin="$nodeDir/bin" - nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-linux-x64.tar.gz" -fi +nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$nodeVersion/node-v$nodeVersion-${OS}-${classifier}" if [[ "$installNode" == "true" ]]; then echo " -- node: version=v${nodeVersion} dir=$nodeDir" From b64f0a76fd4eda536f6fc0cda891a45620249e9c Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 3 Feb 2020 15:33:57 +0100 Subject: [PATCH 04/60] [ML] Setup apiDocs to generate routes docs (#56006) * [ML] setup typedoc to generate routes docs * [ML] remove typedoc packages * [ML] apiDoc * [ML] update optional params * [ML] address pr comments * [ML] change names * [ML] change description for GetDataFrameAnalyticsMessages * [ML] add custom order --- x-pack/legacy/plugins/ml/.gitignore | 1 + .../legacy/plugins/ml/server/routes/README.md | 16 +++ .../plugins/ml/server/routes/apidoc.json | 21 ++++ .../ml/server/routes/data_frame_analytics.ts | 107 ++++++++++++++++++ 4 files changed, 145 insertions(+) create mode 100644 x-pack/legacy/plugins/ml/.gitignore create mode 100644 x-pack/legacy/plugins/ml/server/routes/README.md create mode 100644 x-pack/legacy/plugins/ml/server/routes/apidoc.json diff --git a/x-pack/legacy/plugins/ml/.gitignore b/x-pack/legacy/plugins/ml/.gitignore new file mode 100644 index 0000000000000..708c5b199467b --- /dev/null +++ b/x-pack/legacy/plugins/ml/.gitignore @@ -0,0 +1 @@ +routes_doc diff --git a/x-pack/legacy/plugins/ml/server/routes/README.md b/x-pack/legacy/plugins/ml/server/routes/README.md new file mode 100644 index 0000000000000..1d08335af3d2e --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/routes/README.md @@ -0,0 +1,16 @@ +# ML Kibana API routes + +This folder contains ML API routes in Kibana. + +Each route handler requires [apiDoc](https://github.com/apidoc/apidoc) annotations in order +to generate documentation. +The [apidoc-markdown](https://github.com/rigwild/apidoc-markdown) package is also required in order to generate the markdown. + +For now the process is pretty manual. You need to make sure the packages mentioned above are installed globally +to execute the following command from the directory in which this README file is located. +``` +apidoc -i . -o ../routes_doc && apidoc-markdown -p ../routes_doc -o ../routes_doc/ML_API.md +``` + +It will create a new directory `routes_doc` (next to the `routes` folder) which contains the documentation in HTML format +as well as `ML_API.md` file. \ No newline at end of file diff --git a/x-pack/legacy/plugins/ml/server/routes/apidoc.json b/x-pack/legacy/plugins/ml/server/routes/apidoc.json new file mode 100644 index 0000000000000..8292e946cd344 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/routes/apidoc.json @@ -0,0 +1,21 @@ +{ + "name": "ml_kibana_api", + "version": "0.1.0", + "description": "ML Kibana API", + "title": "ML Kibana API", + "url" : "/api/ml/", + "order": [ + "DataFrameAnalytics", + "GetDataFrameAnalytics", + "GetDataFrameAnalyticsById", + "GetDataFrameAnalyticsStats", + "GetDataFrameAnalyticsStatsById", + "UpdateDataFrameAnalytics", + "EvaluateDataFrameAnalytics", + "ExplainDataFrameAnalytics", + "DeleteDataFrameAnalytics", + "StartDataFrameAnalyticsJob", + "StopsDataFrameAnalyticsJob", + "GetDataFrameAnalyticsMessages" + ] +} diff --git a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts index 7b855e5f87cbf..67fa2fba46f1a 100644 --- a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts @@ -15,7 +15,20 @@ import { dataAnalyticsExplainSchema, } from '../new_platform/data_analytics_schema'; +/** + * Routes for the data frame analytics + */ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteInitialization) { + /** + * @apiGroup DataFrameAnalytics + * + * @api {get} /api/ml/data_frame/analytics Get analytics data + * @apiName GetDataFrameAnalytics + * @apiDescription Returns the list of data frame analytics jobs. + * + * @apiSuccess {Number} count + * @apiSuccess {Object[]} data_frame_analytics + */ router.get( { path: '/api/ml/data_frame/analytics', @@ -35,6 +48,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {get} /api/ml/data_frame/analytics/:analyticsId Get analytics data by id + * @apiName GetDataFrameAnalyticsById + * @apiDescription Returns the data frame analytics job. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}', @@ -57,6 +79,13 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {get} /api/ml/data_frame/analytics/_stats Get analytics stats + * @apiName GetDataFrameAnalyticsStats + * @apiDescription Returns data frame analytics jobs statistics. + */ router.get( { path: '/api/ml/data_frame/analytics/_stats', @@ -76,6 +105,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {get} /api/ml/data_frame/analytics/:analyticsId/_stats Get stats for requested analytics job + * @apiName GetDataFrameAnalyticsStatsById + * @apiDescription Returns data frame analytics job statistics. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stats', @@ -101,6 +139,16 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {put} /api/ml/data_frame/analytics/:analyticsId Instantiate a data frame analytics job + * @apiName UpdateDataFrameAnalytics + * @apiDescription This API creates a data frame analytics job that performs an analysis + * on the source index and stores the outcome in a destination index. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.put( { path: '/api/ml/data_frame/analytics/{analyticsId}', @@ -130,6 +178,13 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {post} /api/ml/data_frame/_evaluate Evaluate the data frame analytics for an annotated index + * @apiName EvaluateDataFrameAnalytics + * @apiDescription Evaluates the data frame analytics for an annotated index. + */ router.post( { path: '/api/ml/data_frame/_evaluate', @@ -154,6 +209,22 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {post} /api/ml/data_frame/_explain Explain a data frame analytics config + * @apiName ExplainDataFrameAnalytics + * @apiDescription This API provides explanations for a data frame analytics config + * that either exists already or one that has not been created yet. + * + * @apiParam {String} [description] + * @apiParam {Object} [dest] + * @apiParam {Object} source + * @apiParam {String} source.index + * @apiParam {Object} analysis + * @apiParam {Object} [analyzed_fields] + * @apiParam {String} [model_memory_limit] + */ router.post( { path: '/api/ml/data_frame/analytics/_explain', @@ -178,6 +249,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {delete} /api/ml/data_frame/analytics/:analyticsId Delete specified analytics job + * @apiName DeleteDataFrameAnalytics + * @apiDescription Deletes specified data frame analytics job. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.delete( { path: '/api/ml/data_frame/analytics/{analyticsId}', @@ -205,6 +285,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {post} /api/ml/data_frame/analytics/:analyticsId/_start Start specified analytics job + * @apiName StartDataFrameAnalyticsJob + * @apiDescription Starts a data frame analytics job. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_start', @@ -229,6 +318,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {post} /api/ml/data_frame/analytics/:analyticsId/_stop Stop specified analytics job + * @apiName StopsDataFrameAnalyticsJob + * @apiDescription Stops a data frame analytics job. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.post( { path: '/api/ml/data_frame/analytics/{analyticsId}/_stop', @@ -263,6 +361,15 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti }) ); + /** + * @apiGroup DataFrameAnalytics + * + * @api {get} /api/ml/data_frame/analytics/:analyticsId/messages Get analytics job messages + * @apiName GetDataFrameAnalyticsMessages + * @apiDescription Returns the list of audit messages for data frame analytics jobs. + * + * @apiParam {String} analyticsId Analytics ID. + */ router.get( { path: '/api/ml/data_frame/analytics/{analyticsId}/messages', From 7dc8c6f9ba15d4566147dbac144b32becdf2adee Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Mon, 3 Feb 2020 07:51:53 -0700 Subject: [PATCH 05/60] [Uptime] Fix flaky functional test for #54541 (#56449) * Add timeout block to race-prone functional test code. * Add timeout for pagination click functions. Co-authored-by: Elastic Machine --- x-pack/test/functional/apps/uptime/overview.ts | 12 ++++++++---- x-pack/test/functional/services/uptime.ts | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/x-pack/test/functional/apps/uptime/overview.ts b/x-pack/test/functional/apps/uptime/overview.ts index 73b91a61196bf..9a879032fadc1 100644 --- a/x-pack/test/functional/apps/uptime/overview.ts +++ b/x-pack/test/functional/apps/uptime/overview.ts @@ -53,8 +53,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('pagination is cleared when filter criteria changes', async () => { await pageObjects.uptime.goToUptimePageAndSetDateRange(DEFAULT_DATE_START, DEFAULT_DATE_END); await pageObjects.uptime.changePage('next'); - // there should now be pagination data in the URL - await pageObjects.uptime.pageUrlContains('pagination'); await pageObjects.uptime.pageHasExpectedIds([ '0010-down', '0011-up', @@ -67,9 +65,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '0018-up', '0019-up', ]); + await retry.tryForTime(12000, async () => { + // there should now be pagination data in the URL + await pageObjects.uptime.pageUrlContains('pagination'); + }); await pageObjects.uptime.setStatusFilter('up'); - // ensure that pagination is removed from the URL - await pageObjects.uptime.pageUrlContains('pagination', false); await pageObjects.uptime.pageHasExpectedIds([ '0000-intermittent', '0001-up', @@ -82,6 +82,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { '0008-up', '0009-up', ]); + await retry.tryForTime(12000, async () => { + // ensure that pagination is removed from the URL + await pageObjects.uptime.pageUrlContains('pagination', false); + }); }); describe('snapshot counts', () => { diff --git a/x-pack/test/functional/services/uptime.ts b/x-pack/test/functional/services/uptime.ts index ed39f28aabbfa..1d8e0c97b99c4 100644 --- a/x-pack/test/functional/services/uptime.ts +++ b/x-pack/test/functional/services/uptime.ts @@ -38,10 +38,10 @@ export function UptimeProvider({ getService }: FtrProviderContext) { await browser.pressKeys(browser.keys.ENTER); }, async goToNextPage() { - await testSubjects.click('xpack.uptime.monitorList.nextButton'); + await testSubjects.click('xpack.uptime.monitorList.nextButton', 5000); }, async goToPreviousPage() { - await testSubjects.click('xpack.uptime.monitorList.prevButton'); + await testSubjects.click('xpack.uptime.monitorList.prevButton', 5000); }, async setStatusFilterUp() { await testSubjects.click('xpack.uptime.filterBar.filterStatusUp'); From 1c849acdd57859256d94080682ea933680c9cef0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20St=C3=BCrmer?= Date: Mon, 3 Feb 2020 16:00:17 +0100 Subject: [PATCH 06/60] [Logs UI] Add smoke tests for log rate and categories tabs (#55088) This adds function smoke tests for the new log rate and categories tabs in the Logs UI. --- x-pack/test/functional/apps/infra/index.ts | 2 + .../apps/infra/log_entry_categories_tab.ts | 28 +++++++++ .../apps/infra/log_entry_rate_tab.ts | 28 +++++++++ .../apps/infra/logs_source_configuration.ts | 62 ++++++++++++------- .../page_objects/infra_logs_page.ts | 10 +-- x-pack/test/functional/services/index.ts | 4 +- .../test/functional/services/logs_ui/index.ts | 18 ++++++ .../services/logs_ui/log_entry_categories.ts | 23 +++++++ .../services/logs_ui/log_entry_rate.ts | 23 +++++++ .../log_stream.ts} | 17 +++-- 10 files changed, 182 insertions(+), 33 deletions(-) create mode 100644 x-pack/test/functional/apps/infra/log_entry_categories_tab.ts create mode 100644 x-pack/test/functional/apps/infra/log_entry_rate_tab.ts create mode 100644 x-pack/test/functional/services/logs_ui/index.ts create mode 100644 x-pack/test/functional/services/logs_ui/log_entry_categories.ts create mode 100644 x-pack/test/functional/services/logs_ui/log_entry_rate.ts rename x-pack/test/functional/services/{infra_log_stream.ts => logs_ui/log_stream.ts} (70%) diff --git a/x-pack/test/functional/apps/infra/index.ts b/x-pack/test/functional/apps/infra/index.ts index b706dc8cce546..597b522a88c51 100644 --- a/x-pack/test/functional/apps/infra/index.ts +++ b/x-pack/test/functional/apps/infra/index.ts @@ -12,6 +12,8 @@ export default ({ loadTestFile }: FtrProviderContext) => { loadTestFile(require.resolve('./home_page')); loadTestFile(require.resolve('./feature_controls')); + loadTestFile(require.resolve('./log_entry_categories_tab')); + loadTestFile(require.resolve('./log_entry_rate_tab')); loadTestFile(require.resolve('./logs_source_configuration')); loadTestFile(require.resolve('./metrics_source_configuration')); loadTestFile(require.resolve('./link_to')); diff --git a/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts b/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts new file mode 100644 index 0000000000000..c703738e37228 --- /dev/null +++ b/x-pack/test/functional/apps/infra/log_entry_categories_tab.ts @@ -0,0 +1,28 @@ +/* + * 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 '../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const logsUi = getService('logsUi'); + const retry = getService('retry'); + + describe('Log Entry Categories Tab', function() { + this.tags('smoke'); + + describe('with a trial license', () => { + it('is visible', async () => { + await logsUi.logEntryCategoriesPage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryCategoriesPage.getSetupScreen()).to.be.ok(); + }); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts b/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts new file mode 100644 index 0000000000000..95228a520aaa2 --- /dev/null +++ b/x-pack/test/functional/apps/infra/log_entry_rate_tab.ts @@ -0,0 +1,28 @@ +/* + * 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 '../../ftr_provider_context'; + +export default ({ getService }: FtrProviderContext) => { + const logsUi = getService('logsUi'); + const retry = getService('retry'); + + describe('Log Entry Rate Tab', function() { + this.tags('smoke'); + + describe('with a trial license', () => { + it('is visible', async () => { + await logsUi.logEntryRatePage.navigateTo(); + + await retry.try(async () => { + expect(await logsUi.logEntryRatePage.getSetupScreen()).to.be.ok(); + }); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/infra/logs_source_configuration.ts b/x-pack/test/functional/apps/infra/logs_source_configuration.ts index 183acf3a980ee..ecad5a40ec42e 100644 --- a/x-pack/test/functional/apps/infra/logs_source_configuration.ts +++ b/x-pack/test/functional/apps/infra/logs_source_configuration.ts @@ -10,12 +10,14 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); - const infraLogStream = getService('infraLogStream'); + const logsUi = getService('logsUi'); const infraSourceConfigurationForm = getService('infraSourceConfigurationForm'); const pageObjects = getPageObjects(['common', 'infraLogs']); + const retry = getService('retry'); describe('Logs Source Configuration', function() { this.tags('smoke'); + before(async () => { await esArchiver.load('empty_kibana'); }); @@ -32,8 +34,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('can change the log indices to a pattern that matches nothing', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings'); - await infraSourceConfigurationForm.getForm(); + await pageObjects.infraLogs.navigateToTab('settings'); + + await retry.try(async () => { + await infraSourceConfigurationForm.getForm(); + }); const nameInput = await infraSourceConfigurationForm.getNameInput(); await nameInput.clearValueWithKeyboard({ charByChar: true }); @@ -47,13 +52,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('renders the no indices screen when no indices match the pattern', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream'); - await pageObjects.infraLogs.getNoLogsIndicesPrompt(); + await logsUi.logStreamPage.navigateTo(); + + await retry.try(async () => { + await logsUi.logStreamPage.getNoLogsIndicesPrompt(); + }); }); it('can change the log indices back to a pattern that matches something', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings'); - await infraSourceConfigurationForm.getForm(); + await pageObjects.infraLogs.navigateToTab('settings'); + + await retry.try(async () => { + await infraSourceConfigurationForm.getForm(); + }); const logIndicesInput = await infraSourceConfigurationForm.getLogIndicesInput(); await logIndicesInput.clearValueWithKeyboard({ charByChar: true }); @@ -63,16 +74,19 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('renders the default log columns with their headers', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream'); - const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels(); + await logsUi.logStreamPage.navigateTo(); - expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'event.dataset', 'Message']); + await retry.try(async () => { + const columnHeaderLabels = await logsUi.logStreamPage.getColumnHeaderLabels(); - const logStreamEntries = await infraLogStream.getStreamEntries(); + expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'event.dataset', 'Message']); + }); + + const logStreamEntries = await logsUi.logStreamPage.getStreamEntries(); expect(logStreamEntries.length).to.be.greaterThan(0); const firstLogStreamEntry = logStreamEntries[0]; - const logStreamEntryColumns = await infraLogStream.getLogColumnsOfStreamEntry( + const logStreamEntryColumns = await logsUi.logStreamPage.getLogColumnsOfStreamEntry( firstLogStreamEntry ); @@ -80,32 +94,34 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { }); it('can change the log columns', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/settings'); - await infraSourceConfigurationForm.getForm(); + await pageObjects.infraLogs.navigateToTab('settings'); + + await retry.try(async () => { + await infraSourceConfigurationForm.getForm(); + }); await infraSourceConfigurationForm.removeAllLogColumns(); await infraSourceConfigurationForm.addTimestampLogColumn(); await infraSourceConfigurationForm.addFieldLogColumn('host.name'); - // await infraSourceConfigurationForm.moveLogColumn(0, 1); - await infraSourceConfigurationForm.saveConfiguration(); }); it('renders the changed log columns with their headers', async () => { - await pageObjects.common.navigateToActualUrl('infraLogs', 'logs/stream'); - const columnHeaderLabels = await infraLogStream.getColumnHeaderLabels(); + await logsUi.logStreamPage.navigateTo(); + + await retry.try(async () => { + const columnHeaderLabels = await logsUi.logStreamPage.getColumnHeaderLabels(); - // TODO: make test more robust - // expect(columnHeaderLabels).to.eql(['host.name', 'Timestamp']); - expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'host.name']); + expect(columnHeaderLabels).to.eql(['Oct 17, 2018', 'host.name']); + }); - const logStreamEntries = await infraLogStream.getStreamEntries(); + const logStreamEntries = await logsUi.logStreamPage.getStreamEntries(); expect(logStreamEntries.length).to.be.greaterThan(0); const firstLogStreamEntry = logStreamEntries[0]; - const logStreamEntryColumns = await infraLogStream.getLogColumnsOfStreamEntry( + const logStreamEntryColumns = await logsUi.logStreamPage.getLogColumnsOfStreamEntry( firstLogStreamEntry ); diff --git a/x-pack/test/functional/page_objects/infra_logs_page.ts b/x-pack/test/functional/page_objects/infra_logs_page.ts index 6eb1349210bae..1c58f8dc41eba 100644 --- a/x-pack/test/functional/page_objects/infra_logs_page.ts +++ b/x-pack/test/functional/page_objects/infra_logs_page.ts @@ -18,12 +18,14 @@ export function InfraLogsPageProvider({ getPageObjects, getService }: FtrProvide await pageObjects.common.navigateToApp('infraLogs'); }, - async getLogStream() { - return await testSubjects.find('logStream'); + async navigateToTab(logsUiTab: LogsUiTab) { + await pageObjects.common.navigateToActualUrl('infraLogs', `/logs/${logsUiTab}`); }, - async getNoLogsIndicesPrompt() { - return await testSubjects.find('noLogsIndicesPrompt'); + async getLogStream() { + return await testSubjects.find('logStream'); }, }; } + +type LogsUiTab = 'log-categories' | 'log-rate' | 'settings' | 'stream'; diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 84d5a792ae6ca..aec91ba9e9034 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -46,7 +46,7 @@ import { GrokDebuggerProvider } from './grok_debugger'; import { UserMenuProvider } from './user_menu'; import { UptimeProvider } from './uptime'; import { InfraSourceConfigurationFormProvider } from './infra_source_configuration_form'; -import { InfraLogStreamProvider } from './infra_log_stream'; +import { LogsUiProvider } from './logs_ui'; import { MachineLearningProvider } from './ml'; import { TransformProvider } from './transform'; @@ -88,7 +88,7 @@ export const services = { userMenu: UserMenuProvider, uptime: UptimeProvider, infraSourceConfigurationForm: InfraSourceConfigurationFormProvider, - infraLogStream: InfraLogStreamProvider, + logsUi: LogsUiProvider, ml: MachineLearningProvider, transform: TransformProvider, }; diff --git a/x-pack/test/functional/services/logs_ui/index.ts b/x-pack/test/functional/services/logs_ui/index.ts new file mode 100644 index 0000000000000..c70a8470aafce --- /dev/null +++ b/x-pack/test/functional/services/logs_ui/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { LogEntryCategoriesPageProvider } from './log_entry_categories'; +import { LogEntryRatePageProvider } from './log_entry_rate'; +import { LogStreamPageProvider } from './log_stream'; + +export function LogsUiProvider(context: FtrProviderContext) { + return { + logEntryCategoriesPage: LogEntryCategoriesPageProvider(context), + logEntryRatePage: LogEntryRatePageProvider(context), + logStreamPage: LogStreamPageProvider(context), + }; +} diff --git a/x-pack/test/functional/services/logs_ui/log_entry_categories.ts b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts new file mode 100644 index 0000000000000..b9a400b155679 --- /dev/null +++ b/x-pack/test/functional/services/logs_ui/log_entry_categories.ts @@ -0,0 +1,23 @@ +/* + * 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 { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function LogEntryCategoriesPageProvider({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['infraLogs']); + const testSubjects = getService('testSubjects'); + + return { + async navigateTo() { + pageObjects.infraLogs.navigateToTab('log-categories'); + }, + + async getSetupScreen(): Promise { + return await testSubjects.find('logEntryCategoriesSetupPage'); + }, + }; +} diff --git a/x-pack/test/functional/services/logs_ui/log_entry_rate.ts b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts new file mode 100644 index 0000000000000..96c69e85aa0a4 --- /dev/null +++ b/x-pack/test/functional/services/logs_ui/log_entry_rate.ts @@ -0,0 +1,23 @@ +/* + * 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 { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export function LogEntryRatePageProvider({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['infraLogs']); + const testSubjects = getService('testSubjects'); + + return { + async navigateTo() { + pageObjects.infraLogs.navigateToTab('log-rate'); + }, + + async getSetupScreen(): Promise { + return await testSubjects.find('logEntryRateSetupPage'); + }, + }; +} diff --git a/x-pack/test/functional/services/infra_log_stream.ts b/x-pack/test/functional/services/logs_ui/log_stream.ts similarity index 70% rename from x-pack/test/functional/services/infra_log_stream.ts rename to x-pack/test/functional/services/logs_ui/log_stream.ts index af113d3afffb4..ce37d2d5a60da 100644 --- a/x-pack/test/functional/services/infra_log_stream.ts +++ b/x-pack/test/functional/services/logs_ui/log_stream.ts @@ -4,14 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FtrProviderContext } from '../ftr_provider_context'; -import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { WebElementWrapper } from '../../../../../test/functional/services/lib/web_element_wrapper'; -export function InfraLogStreamProvider({ getService }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); +export function LogStreamPageProvider({ getPageObjects, getService }: FtrProviderContext) { + const pageObjects = getPageObjects(['infraLogs']); const retry = getService('retry'); + const testSubjects = getService('testSubjects'); return { + async navigateTo() { + pageObjects.infraLogs.navigateToTab('stream'); + }, + async getColumnHeaderLabels(): Promise { const columnHeaderElements: WebElementWrapper[] = await testSubjects.findAll( '~logColumnHeader' @@ -35,5 +40,9 @@ export function InfraLogStreamProvider({ getService }: FtrProviderContext) { ): Promise { return await testSubjects.findAllDescendant('~logColumn', entryElement); }, + + async getNoLogsIndicesPrompt() { + return await testSubjects.find('noLogsIndicesPrompt'); + }, }; } From 85988386b6fcc5f71c0b01fd3b6fd14767bdb93d Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 3 Feb 2020 07:04:23 -0800 Subject: [PATCH 07/60] Containers (#56571) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 move state containers to /common folder * refactor: 💡 remove RecursiveReadonly type in state containers * fix: 🐛 assume we are in production by default on server-side Co-authored-by: Elastic Machine --- src/plugins/kibana_utils/common/index.ts | 1 + .../create_state_container.test.ts | 0 .../create_state_container.ts | 25 +++++++++++-------- ...ate_state_container_react_helpers.test.tsx | 0 .../create_state_container_react_helpers.ts | 0 .../state_containers/index.ts | 0 .../state_containers/types.ts | 18 ++++++------- .../demos/state_containers/counter.ts | 2 +- .../demos/state_containers/todomvc.ts | 2 +- .../kibana_utils/demos/state_sync/url.ts | 2 +- src/plugins/kibana_utils/public/index.ts | 2 +- .../public/state_sync/state_sync.test.ts | 2 +- .../public/state_sync/state_sync.ts | 2 +- .../kibana_utils/public/state_sync/types.ts | 2 +- 14 files changed, 31 insertions(+), 27 deletions(-) rename src/plugins/kibana_utils/{public => common}/state_containers/create_state_container.test.ts (100%) rename src/plugins/kibana_utils/{public => common}/state_containers/create_state_container.ts (86%) rename src/plugins/kibana_utils/{public => common}/state_containers/create_state_container_react_helpers.test.tsx (100%) rename src/plugins/kibana_utils/{public => common}/state_containers/create_state_container_react_helpers.ts (100%) rename src/plugins/kibana_utils/{public => common}/state_containers/index.ts (100%) rename src/plugins/kibana_utils/{public => common}/state_containers/types.ts (88%) diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index bfb45b88964d8..d4aeb2c0fe4ad 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -19,4 +19,5 @@ export * from './defer'; export * from './of'; +export * from './state_containers'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container.test.ts similarity index 100% rename from src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts rename to src/plugins/kibana_utils/common/state_containers/create_state_container.test.ts diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container.ts similarity index 86% rename from src/plugins/kibana_utils/public/state_containers/create_state_container.ts rename to src/plugins/kibana_utils/common/state_containers/create_state_container.ts index d420aec30f068..78bfc0c3e9090 100644 --- a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts +++ b/src/plugins/kibana_utils/common/state_containers/create_state_container.ts @@ -19,7 +19,6 @@ import { BehaviorSubject } from 'rxjs'; import { skip } from 'rxjs/operators'; -import { RecursiveReadonly } from '@kbn/utility-types'; import deepFreeze from 'deep-freeze-strict'; import { PureTransitionsToTransitions, @@ -32,14 +31,18 @@ import { const $$observable = (typeof Symbol === 'function' && (Symbol as any).observable) || '@@observable'; const $$setActionType = '@@SET'; -const freeze: (value: T) => RecursiveReadonly = - process.env.NODE_ENV !== 'production' - ? (value: T): RecursiveReadonly => { - const isFreezable = value !== null && typeof value === 'object'; - if (isFreezable) return deepFreeze(value) as RecursiveReadonly; - return value as RecursiveReadonly; - } - : (value: T) => value as RecursiveReadonly; +const isProduction = + typeof window === 'object' + ? process.env.NODE_ENV === 'production' + : !process.env.NODE_ENV || process.env.NODE_ENV === 'production'; + +const freeze: (value: T) => T = isProduction + ? (value: T) => value as T + : (value: T): T => { + const isFreezable = value !== null && typeof value === 'object'; + if (isFreezable) return deepFreeze(value) as T; + return value as T; + }; export function createStateContainer( defaultState: State @@ -66,7 +69,7 @@ export function createStateContainer< pureTransitions: PureTransitions = {} as PureTransitions, pureSelectors: PureSelectors = {} as PureSelectors ): ReduxLikeStateContainer { - const data$ = new BehaviorSubject>(freeze(defaultState)); + const data$ = new BehaviorSubject(freeze(defaultState)); const state$ = data$.pipe(skip(1)); const get = () => data$.getValue(); const container: ReduxLikeStateContainer = { @@ -101,7 +104,7 @@ export function createStateContainer< ), addMiddleware: middleware => (container.dispatch = middleware(container as any)(container.dispatch)), - subscribe: (listener: (state: RecursiveReadonly) => void) => { + subscribe: (listener: (state: State) => void) => { const subscription = state$.subscribe(listener); return () => subscription.unsubscribe(); }, diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx similarity index 100% rename from src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx rename to src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.test.tsx diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts similarity index 100% rename from src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts rename to src/plugins/kibana_utils/common/state_containers/create_state_container_react_helpers.ts diff --git a/src/plugins/kibana_utils/public/state_containers/index.ts b/src/plugins/kibana_utils/common/state_containers/index.ts similarity index 100% rename from src/plugins/kibana_utils/public/state_containers/index.ts rename to src/plugins/kibana_utils/common/state_containers/index.ts diff --git a/src/plugins/kibana_utils/public/state_containers/types.ts b/src/plugins/kibana_utils/common/state_containers/types.ts similarity index 88% rename from src/plugins/kibana_utils/public/state_containers/types.ts rename to src/plugins/kibana_utils/common/state_containers/types.ts index 5f27a3d2c1dca..26a29bc470e8a 100644 --- a/src/plugins/kibana_utils/public/state_containers/types.ts +++ b/src/plugins/kibana_utils/common/state_containers/types.ts @@ -18,7 +18,7 @@ */ import { Observable } from 'rxjs'; -import { Ensure, RecursiveReadonly } from '@kbn/utility-types'; +import { Ensure } from '@kbn/utility-types'; export type BaseState = object; export interface TransitionDescription { @@ -27,7 +27,7 @@ export interface TransitionDescription = (...args: Args) => State; export type PureTransition = ( - state: RecursiveReadonly + state: State ) => Transition; export type EnsurePureTransition = Ensure>; export type PureTransitionToTransition> = ReturnType; @@ -36,9 +36,9 @@ export type PureTransitionsToTransitions = { }; export interface BaseStateContainer { - get: () => RecursiveReadonly; + get: () => State; set: (state: State) => void; - state$: Observable>; + state$: Observable; } export interface StateContainer< @@ -55,12 +55,12 @@ export interface ReduxLikeStateContainer< PureTransitions extends object = {}, PureSelectors extends object = {} > extends StateContainer { - getState: () => RecursiveReadonly; - reducer: Reducer>; - replaceReducer: (nextReducer: Reducer>) => void; + getState: () => State; + reducer: Reducer; + replaceReducer: (nextReducer: Reducer) => void; dispatch: (action: TransitionDescription) => void; - addMiddleware: (middleware: Middleware>) => void; - subscribe: (listener: (state: RecursiveReadonly) => void) => () => void; + addMiddleware: (middleware: Middleware) => void; + subscribe: (listener: (state: State) => void) => () => void; } export type Dispatch = (action: T) => void; diff --git a/src/plugins/kibana_utils/demos/state_containers/counter.ts b/src/plugins/kibana_utils/demos/state_containers/counter.ts index 4ddf532c1506d..0484a906a60d3 100644 --- a/src/plugins/kibana_utils/demos/state_containers/counter.ts +++ b/src/plugins/kibana_utils/demos/state_containers/counter.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createStateContainer } from '../../public/state_containers'; +import { createStateContainer } from '../../common/state_containers'; interface State { count: number; diff --git a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts index e807783a56f31..0a07d721479b3 100644 --- a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts +++ b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts @@ -17,7 +17,7 @@ * under the License. */ -import { createStateContainer, PureTransition } from '../../public/state_containers'; +import { createStateContainer, PureTransition } from '../../common/state_containers'; export interface TodoItem { text: string; diff --git a/src/plugins/kibana_utils/demos/state_sync/url.ts b/src/plugins/kibana_utils/demos/state_sync/url.ts index 2c426cae6733a..80c016950d224 100644 --- a/src/plugins/kibana_utils/demos/state_sync/url.ts +++ b/src/plugins/kibana_utils/demos/state_sync/url.ts @@ -18,7 +18,7 @@ */ import { defaultState, pureTransitions, TodoActions, TodoState } from '../state_containers/todomvc'; -import { BaseState, BaseStateContainer, createStateContainer } from '../../public/state_containers'; +import { BaseState, BaseStateContainer, createStateContainer } from '../../common/state_containers'; import { createKbnUrlStateStorage, syncState, diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 00c1c95028b4d..78828ad9c4b2d 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -25,7 +25,7 @@ export * from './field_wildcard'; export * from './parse'; export * from './render_complete'; export * from './resize_checker'; -export * from './state_containers'; +export * from '../common/state_containers'; export * from './storage'; export { hashedItemStore, HashedItemStore } from './storage/hashed_item_store'; export { diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index 17f41483a0a21..c55c60f9b0f89 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BaseState, BaseStateContainer, createStateContainer } from '../state_containers'; +import { BaseState, BaseStateContainer, createStateContainer } from '../../common/state_containers'; import { defaultState, pureTransitions, diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts index 28d133829e07c..ed57723f8f2b7 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts @@ -23,7 +23,7 @@ import defaultComparator from 'fast-deep-equal'; import { IStateSyncConfig } from './types'; import { IStateStorage } from './state_sync_state_storage'; import { distinctUntilChangedWithInitialValue } from '../../common'; -import { BaseState } from '../state_containers'; +import { BaseState } from '../../common/state_containers'; import { applyDiff } from '../state_management/utils/diff_object'; /** diff --git a/src/plugins/kibana_utils/public/state_sync/types.ts b/src/plugins/kibana_utils/public/state_sync/types.ts index 3009c1d161a53..2acb466d92e92 100644 --- a/src/plugins/kibana_utils/public/state_sync/types.ts +++ b/src/plugins/kibana_utils/public/state_sync/types.ts @@ -17,7 +17,7 @@ * under the License. */ -import { BaseState, BaseStateContainer } from '../state_containers/types'; +import { BaseState, BaseStateContainer } from '../../common/state_containers/types'; import { IStateStorage } from './state_sync_state_storage'; export interface INullableBaseStateContainer From c86ee1b6ea001863fde399721febaf1704fd2fc0 Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Mon, 3 Feb 2020 07:05:39 -0800 Subject: [PATCH 08/60] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20AppArch=20p?= =?UTF-8?q?lugins=20to=20CODEOWNERS=20(#56397)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 add AppArch plugins to CODEOWNERS And sort the list alphabetically. * chore: 🤖 add @kbn/interpreter to AppArch CODEOWNERS Co-authored-by: Elastic Machine --- .github/CODEOWNERS | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2e2b20a46baed..0b0addf117f6f 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -28,15 +28,7 @@ /src/plugins/dev_tools/ @elastic/kibana-app # App Architecture -/src/plugins/data/ @elastic/kibana-app-arch -/src/plugins/embeddable/ @elastic/kibana-app-arch -/src/plugins/expressions/ @elastic/kibana-app-arch -/src/plugins/kibana_react/ @elastic/kibana-app-arch -/src/plugins/kibana_utils/ @elastic/kibana-app-arch -/src/plugins/navigation/ @elastic/kibana-app-arch -/src/plugins/ui_actions/ @elastic/kibana-app-arch -/src/plugins/visualizations/ @elastic/kibana-app-arch -/x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch +/packages/kbn-interpreter/ @elastic/kibana-app-arch /src/legacy/core_plugins/data/ @elastic/kibana-app-arch /src/legacy/core_plugins/elasticsearch/lib/create_proxy.js @elastic/kibana-app-arch /src/legacy/core_plugins/embeddable_api/ @elastic/kibana-app-arch @@ -48,6 +40,19 @@ /src/legacy/core_plugins/kibana/server/routes/api/suggestions/ @elastic/kibana-app-arch /src/legacy/core_plugins/visualizations/ @elastic/kibana-app-arch /src/legacy/server/index_patterns/ @elastic/kibana-app-arch +/src/plugins/bfetch/ @elastic/kibana-app-arch +/src/plugins/dashboard_embeddable_container/ @elastic/kibana-app-arch +/src/plugins/data/ @elastic/kibana-app-arch +/src/plugins/embeddable/ @elastic/kibana-app-arch +/src/plugins/expressions/ @elastic/kibana-app-arch +/src/plugins/inspector/ @elastic/kibana-app-arch +/src/plugins/kibana_react/ @elastic/kibana-app-arch +/src/plugins/kibana_utils/ @elastic/kibana-app-arch +/src/plugins/management/ @elastic/kibana-app-arch +/src/plugins/navigation/ @elastic/kibana-app-arch +/src/plugins/ui_actions/ @elastic/kibana-app-arch +/src/plugins/visualizations/ @elastic/kibana-app-arch +/x-pack/plugins/advanced_ui_actions/ @elastic/kibana-app-arch # APM /x-pack/legacy/plugins/apm/ @elastic/apm-ui From 95e40e7fa3a7be1b9818c67177f3e8f4927eb9ba Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 3 Feb 2020 16:22:38 +0100 Subject: [PATCH 09/60] [Uptime] Refresh absolute date ranges for Ping Histogram (#56381) * fix abs date mismatch * fixed types * update pr * simplify params Co-authored-by: Elastic Machine --- .../connected/charts/ping_histogram.tsx | 36 +- .../connected/charts/snapshot_container.tsx | 95 +++ .../filter_group/filter_group_container.tsx | 17 +- .../public/components/connected/index.ts | 3 + .../monitor/status_bar_container.tsx | 85 +++ .../monitor/status_details_container.tsx | 64 ++ .../monitor_charts.test.tsx.snap | 2 - .../__tests__/monitor_charts.test.tsx | 2 - .../functional/__tests__/snapshot.test.tsx | 4 +- .../monitor_bar_series.test.tsx.snap | 156 +++-- .../__tests__/monitor_bar_series.test.tsx | 159 ++++- .../functional/charts/monitor_bar_series.tsx | 25 +- .../public/components/functional/index.ts | 2 - .../components/functional/monitor_charts.tsx | 26 +- .../__snapshots__/monitor_list.test.tsx.snap | 554 ++++++++++++++++++ .../monitor_list_pagination.test.tsx.snap | 4 +- .../__tests__/monitor_list.test.tsx | 25 +- .../monitor_list_pagination.test.tsx | 6 +- .../functional/monitor_list/monitor_list.tsx | 20 +- .../monitor_status.bar.test.tsx.snap | 0 .../__test__}/monitor_status.bar.test.tsx | 12 +- .../monitor_status_details/index.ts | 31 +- .../monitor_status_bar/index.ts | 44 +- .../monitor_status_bar/monitor_status_bar.tsx | 26 +- .../monitor_status_details.tsx | 27 +- .../public/components/functional/snapshot.tsx | 107 +--- .../components/functional/status_panel.tsx | 42 +- .../public/hooks/update_kuery_string.ts | 4 +- .../plugins/uptime/public/pages/monitor.tsx | 17 +- .../plugins/uptime/public/pages/overview.tsx | 11 +- 30 files changed, 1148 insertions(+), 458 deletions(-) create mode 100644 x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx create mode 100644 x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx rename x-pack/legacy/plugins/uptime/public/components/functional/{__tests__ => monitor_status_details/__test__}/__snapshots__/monitor_status.bar.test.tsx.snap (100%) rename x-pack/legacy/plugins/uptime/public/components/functional/{__tests__ => monitor_status_details/__test__}/monitor_status.bar.test.tsx (79%) diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx index a6607ca81fc18..cbdd921a36e81 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/ping_histogram.tsx @@ -15,34 +15,40 @@ import { getPingHistogram } from '../../../state/actions'; import { selectPingHistogram } from '../../../state/selectors'; import { withResponsiveWrapper, ResponsiveWrapperProps } from '../../higher_order'; import { GetPingHistogramParams, HistogramResult } from '../../../../common/types'; +import { useUrlParams } from '../../../hooks'; -type Props = GetPingHistogramParams & - ResponsiveWrapperProps & - PingHistogramComponentProps & - DispatchProps & { lastRefresh: number }; +type Props = ResponsiveWrapperProps & + Pick & + DispatchProps & { lastRefresh: number; monitorId?: string }; const PingHistogramContainer: React.FC = ({ data, loadData, - statusFilter, - filters, - dateStart, - dateEnd, - absoluteStartDate, - absoluteEndDate, monitorId, lastRefresh, - ...props + height, + loading, }) => { + const [getUrlParams] = useUrlParams(); + const { + absoluteDateRangeStart, + absoluteDateRangeEnd, + dateRangeStart: dateStart, + dateRangeEnd: dateEnd, + statusFilter, + filters, + } = getUrlParams(); + useEffect(() => { loadData({ monitorId, dateStart, dateEnd, statusFilter, filters }); }, [loadData, dateStart, dateEnd, monitorId, filters, statusFilter, lastRefresh]); return ( ); }; @@ -68,7 +74,7 @@ const mapDispatchToProps = (dispatch: any): DispatchProps => ({ export const PingHistogram = connect< StateProps, DispatchProps, - PingHistogramComponentProps, + Pick, AppState >( mapStateToProps, diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx new file mode 100644 index 0000000000000..6d01ebae1e100 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/charts/snapshot_container.tsx @@ -0,0 +1,95 @@ +/* + * 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, { useEffect } from 'react'; +import { connect } from 'react-redux'; +import { useUrlParams } from '../../../hooks'; +import { AppState } from '../../../state'; +import { fetchSnapshotCount } from '../../../state/actions'; +import { SnapshotComponent } from '../../functional/snapshot'; +import { Snapshot as SnapshotType } from '../../../../common/runtime_types'; + +/** + * Props expected from parent components. + */ +interface OwnProps { + /** + * Height is needed, since by default charts takes height of 100% + */ + height?: string; +} + +/** + * Props given by the Redux store based on action input. + */ +interface StoreProps { + count: SnapshotType; + lastRefresh: number; + loading: boolean; +} + +/** + * Contains functions that will dispatch actions used + * for this component's life cycle + */ +interface DispatchProps { + loadSnapshotCount: typeof fetchSnapshotCount; +} + +/** + * Props used to render the Snapshot component. + */ +type Props = OwnProps & StoreProps & DispatchProps; + +export const Container: React.FC = ({ + count, + height, + lastRefresh, + loading, + loadSnapshotCount, +}: Props) => { + const [getUrlParams] = useUrlParams(); + const { dateRangeStart, dateRangeEnd, statusFilter, filters } = getUrlParams(); + + useEffect(() => { + loadSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter); + }, [dateRangeStart, dateRangeEnd, filters, lastRefresh, loadSnapshotCount, statusFilter]); + return ; +}; + +/** + * Provides state to connected component. + * @param state the root app state + */ +const mapStateToProps = ({ + snapshot: { count, loading }, + ui: { lastRefresh }, +}: AppState): StoreProps => ({ + count, + lastRefresh, + loading, +}); + +/** + * Used for fetching snapshot counts. + * @param dispatch redux-provided action dispatcher + */ +const mapDispatchToProps = (dispatch: any) => ({ + loadSnapshotCount: ( + dateRangeStart: string, + dateRangeEnd: string, + filters?: string, + statusFilter?: string + ): DispatchProps => { + return dispatch(fetchSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter)); + }, +}); + +export const Snapshot = connect( + // @ts-ignore connect is expecting null | undefined for some reason + mapStateToProps, + mapDispatchToProps +)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/filter_group/filter_group_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/filter_group/filter_group_container.tsx index 2d1c21d1c997d..569c6bb883cbd 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/filter_group/filter_group_container.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/connected/filter_group/filter_group_container.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { connect } from 'react-redux'; import { useUrlParams } from '../../../hooks'; import { parseFiltersMap } from '../../functional/filter_group/parse_filter_map'; @@ -12,6 +12,7 @@ import { AppState } from '../../../state'; import { fetchOverviewFilters, GetOverviewFiltersPayload } from '../../../state/actions'; import { FilterGroupComponent } from '../../functional/filter_group'; import { OverviewFilters } from '../../../../common/runtime_types/overview_filters'; +import { UptimeRefreshContext } from '../../../contexts'; interface OwnProps { esFilters?: string; @@ -37,8 +38,9 @@ export const Container: React.FC = ({ loadFilterGroup, overviewFilters, }: Props) => { - const [getUrlParams, updateUrl] = useUrlParams(); + const { lastRefresh } = useContext(UptimeRefreshContext); + const [getUrlParams, updateUrl] = useUrlParams(); const { dateRangeStart, dateRangeEnd, statusFilter, filters: urlFilters } = getUrlParams(); useEffect(() => { @@ -53,7 +55,16 @@ export const Container: React.FC = ({ statusFilter, tags: filterSelections.tags ?? [], }); - }, [dateRangeStart, dateRangeEnd, esKuery, esFilters, statusFilter, urlFilters, loadFilterGroup]); + }, [ + lastRefresh, + dateRangeStart, + dateRangeEnd, + esKuery, + esFilters, + statusFilter, + urlFilters, + loadFilterGroup, + ]); // update filters in the URL from filter group const onFilterUpdate = (filtersKuery: string) => { diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts index 5bb0d1ae8468f..2fd4c762cf45f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/connected/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/connected/index.ts @@ -5,6 +5,9 @@ */ export { PingHistogram } from './charts/ping_histogram'; +export { Snapshot } from './charts/snapshot_container'; export { KueryBar } from './kuerybar/kuery_bar_container'; export { OverviewPage } from './pages/overview_container'; export { FilterGroup } from './filter_group/filter_group_container'; +export { MonitorStatusDetails } from './monitor/status_details_container'; +export { MonitorStatusBar } from './monitor/status_bar_container'; diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.tsx new file mode 100644 index 0000000000000..db6337732091a --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_bar_container.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. + */ + +import React, { useContext, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { AppState } from '../../../state'; +import { selectMonitorLocations, selectMonitorStatus } from '../../../state/selectors'; +import { MonitorStatusBarComponent } from '../../functional/monitor_status_details/monitor_status_bar'; +import { getMonitorStatus, getSelectedMonitor } from '../../../state/actions'; +import { useUrlParams } from '../../../hooks'; +import { Ping } from '../../../../common/graphql/types'; +import { MonitorLocations } from '../../../../common/runtime_types/monitor'; +import { UptimeRefreshContext } from '../../../contexts'; + +interface StateProps { + monitorStatus: Ping; + monitorLocations: MonitorLocations; +} + +interface DispatchProps { + loadMonitorStatus: (dateStart: string, dateEnd: string, monitorId: string) => void; +} + +interface OwnProps { + monitorId: string; +} + +type Props = OwnProps & StateProps & DispatchProps; + +export const Container: React.FC = ({ + loadMonitorStatus, + monitorId, + monitorStatus, + monitorLocations, +}: Props) => { + const { lastRefresh } = useContext(UptimeRefreshContext); + + const [getUrlParams] = useUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); + + useEffect(() => { + loadMonitorStatus(dateStart, dateEnd, monitorId); + }, [monitorId, dateStart, dateEnd, loadMonitorStatus, lastRefresh]); + + return ( + + ); +}; + +const mapStateToProps = (state: AppState, ownProps: OwnProps) => ({ + monitorStatus: selectMonitorStatus(state), + monitorLocations: selectMonitorLocations(state, ownProps.monitorId), +}); + +const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({ + loadMonitorStatus: (dateStart: string, dateEnd: string, monitorId: string) => { + dispatch( + getMonitorStatus({ + monitorId, + dateStart, + dateEnd, + }) + ); + dispatch( + getSelectedMonitor({ + monitorId, + }) + ); + }, +}); + +// @ts-ignore TODO: Investigate typescript issues here +export const MonitorStatusBar = connect( + // @ts-ignore TODO: Investigate typescript issues here + mapStateToProps, + mapDispatchToProps +)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx new file mode 100644 index 0000000000000..6929e3bd64c4d --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/connected/monitor/status_details_container.tsx @@ -0,0 +1,64 @@ +/* + * 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, { useContext, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { useUrlParams } from '../../../hooks'; +import { AppState } from '../../../state'; +import { selectMonitorLocations } from '../../../state/selectors'; +import { fetchMonitorLocations, MonitorLocationsPayload } from '../../../state/actions/monitor'; +import { MonitorStatusDetailsComponent } from '../../functional/monitor_status_details'; +import { MonitorLocations } from '../../../../common/runtime_types'; +import { UptimeRefreshContext } from '../../../contexts'; + +interface OwnProps { + monitorId: string; +} + +interface StoreProps { + monitorLocations: MonitorLocations; +} + +interface DispatchProps { + loadMonitorLocations: typeof fetchMonitorLocations; +} + +type Props = OwnProps & StoreProps & DispatchProps; + +export const Container: React.FC = ({ + loadMonitorLocations, + monitorLocations, + monitorId, +}: Props) => { + const { lastRefresh } = useContext(UptimeRefreshContext); + + const [getUrlParams] = useUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); + + useEffect(() => { + loadMonitorLocations({ dateStart, dateEnd, monitorId }); + }, [loadMonitorLocations, monitorId, dateStart, dateEnd, lastRefresh]); + + return ( + + ); +}; +const mapStateToProps = (state: AppState, { monitorId }: OwnProps) => ({ + monitorLocations: selectMonitorLocations(state, monitorId), +}); + +const mapDispatchToProps = (dispatch: Dispatch) => ({ + loadMonitorLocations: (params: MonitorLocationsPayload) => { + dispatch(fetchMonitorLocations(params)); + }, +}); + +export const MonitorStatusDetails = connect( + // @ts-ignore TODO: Investigate typescript issues here + mapStateToProps, + mapDispatchToProps +)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap index f6846dfb1164d..9853ed5cadfc9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_charts.test.tsx.snap @@ -180,8 +180,6 @@ exports[`MonitorCharts component renders the component without errors 1`] = ` }, } } - dateRangeEnd="2011-12-03T10:15:30+01:00" - dateRangeStart="2011-12-03T10:15:30+01:00" loading={false} mean="mean" monitorId="something" diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_charts.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_charts.test.tsx index 81c60c8fbeaaa..331b5c9c0b096 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_charts.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_charts.test.tsx @@ -73,8 +73,6 @@ describe('MonitorCharts component', () => { range="range" success="success" monitorId="something" - dateRangeStart="2011-12-03T10:15:30+01:00" - dateRangeEnd="2011-12-03T10:15:30+01:00" /> ) ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx index d645eb21ac776..214b0394369f7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Snapshot } from '../../../../common/runtime_types'; -import { PresentationalComponent } from '../snapshot'; +import { SnapshotComponent } from '../snapshot'; describe('Snapshot component', () => { const snapshot: Snapshot = { @@ -17,7 +17,7 @@ describe('Snapshot component', () => { }; it('renders without errors', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap index c3b99c9785cbe..8ca73879cab8c 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap @@ -1,67 +1,107 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`MonitorBarSeries component renders a series when there are down items 1`] = ` +exports[`MonitorBarSeries component renders if the data series is present 1`] = `
- - - - - +
+
+

+ No data to display +

+
+
+
`; + +exports[`MonitorBarSeries component shallow renders a series when there are down items 1`] = ` + + + +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/monitor_bar_series.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/monitor_bar_series.test.tsx index 3cede0be00ef1..c3e98134e438d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/monitor_bar_series.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/monitor_bar_series.test.tsx @@ -5,15 +5,16 @@ */ import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { renderWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { MonitorBarSeries, MonitorBarSeriesProps } from '../monitor_bar_series'; +import { renderWithRouter } from '../../../../lib'; +import { SummaryHistogramPoint } from '../../../../../common/graphql/types'; describe('MonitorBarSeries component', () => { let props: MonitorBarSeriesProps; + let histogramSeries: SummaryHistogramPoint[]; beforeEach(() => { props = { - absoluteStartDate: 1548697920000, - absoluteEndDate: 1548700920000, dangerColor: 'A danger color', histogramSeries: [ { @@ -33,20 +34,144 @@ describe('MonitorBarSeries component', () => { }, ], }; + histogramSeries = [ + { timestamp: 1580387868000, up: 0, down: 5 }, + { timestamp: 1580387904000, up: 0, down: 20 }, + { + timestamp: 1580387940000, + up: 0, + down: 19, + }, + { + timestamp: 1580387976000, + up: 0, + down: 16, + }, + { + timestamp: 1580388012000, + up: 0, + down: 20, + }, + { + timestamp: 1580388048000, + up: 0, + down: 15, + }, + { + timestamp: 1580388084000, + up: 0, + down: 20, + }, + { + timestamp: 1580388120000, + up: 0, + down: 19, + }, + { + timestamp: 1580388156000, + up: 0, + down: 16, + }, + { + timestamp: 1580388192000, + up: 0, + down: 20, + }, + { + timestamp: 1580388228000, + up: 0, + down: 15, + }, + { + timestamp: 1580388264000, + up: 0, + down: 20, + }, + { + timestamp: 1580388300000, + up: 0, + down: 19, + }, + { + timestamp: 1580388336000, + up: 0, + down: 16, + }, + { + timestamp: 1580388372000, + up: 0, + down: 20, + }, + { + timestamp: 1580388408000, + up: 0, + down: 15, + }, + { + timestamp: 1580388444000, + up: 0, + down: 20, + }, + { + timestamp: 1580388480000, + up: 0, + down: 19, + }, + { + timestamp: 1580388516000, + up: 0, + down: 16, + }, + { + timestamp: 1580388552000, + up: 0, + down: 20, + }, + { + timestamp: 1580388588000, + up: 0, + down: 15, + }, + { + timestamp: 1580388624000, + up: 0, + down: 20, + }, + { + timestamp: 1580388660000, + up: 0, + down: 19, + }, + { + timestamp: 1580388696000, + up: 0, + down: 16, + }, + { + timestamp: 1580388732000, + up: 0, + down: 20, + }, + { + timestamp: 1580388768000, + up: 0, + down: 10, + }, + ]; }); - it('renders a series when there are down items', () => { - const component = shallowWithIntl(); + it('shallow renders a series when there are down items', () => { + const component = shallowWithIntl(renderWithRouter()); expect(component).toMatchSnapshot(); }); - it('renders null when there are no down items', () => { + it('shallow renders null when there are no down items', () => { props.histogramSeries = []; - const component = shallowWithIntl(); + const component = shallowWithIntl(renderWithRouter()); expect(component).toEqual({}); }); - it('renders nothing if the down count has no counts', () => { + it(' shallow renders nothing if the down count has no counts', () => { props.histogramSeries = [ { timestamp: 123, @@ -64,19 +189,21 @@ describe('MonitorBarSeries component', () => { up: 0, }, ]; - const component = shallowWithIntl(); + const component = shallowWithIntl(renderWithRouter()); expect(component).toEqual({}); }); - it('renders nothing if the data series is null', () => { + it('shallow renders nothing if the data series is null', () => { const component = shallowWithIntl( - + renderWithRouter() ); expect(component).toEqual({}); }); + + it('renders if the data series is present', () => { + const component = renderWithIntl( + renderWithRouter() + ); + expect(component).toMatchSnapshot(); + }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx index ce91bf5b1638f..2338bf0278348 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx @@ -19,16 +19,9 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiToolTip } from '@elastic/eui'; import { SummaryHistogramPoint } from '../../../../common/graphql/types'; import { getChartDateLabel, seriesHasDownValues } from '../../../lib/helper'; +import { useUrlParams } from '../../../hooks'; export interface MonitorBarSeriesProps { - /** - * The date/time for the start of the timespan. - */ - absoluteStartDate: number; - /** - * The date/time for the end of the timespan. - */ - absoluteEndDate: number; /** * The color to use for the display of down states. */ @@ -44,23 +37,23 @@ export interface MonitorBarSeriesProps { * so we will only render the series component if there are down counts for the selected monitor. * @param props - the values for the monitor this chart visualizes */ -export const MonitorBarSeries = ({ - absoluteStartDate, - absoluteEndDate, - dangerColor, - histogramSeries, -}: MonitorBarSeriesProps) => { +export const MonitorBarSeries = ({ dangerColor, histogramSeries }: MonitorBarSeriesProps) => { + const [getUrlParams] = useUrlParams(); + const { absoluteDateRangeStart, absoluteDateRangeEnd } = getUrlParams(); + const id = 'downSeries'; return seriesHasDownValues(histogramSeries) ? (
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with height specified 1`] = `"
"`; @@ -21,7 +21,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with height specified
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with page specified 1`] = `"
"`; @@ -33,7 +33,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with page specified 2`
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width and height specified 1`] = `"
"`; @@ -45,7 +45,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with width and height
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; exports[`Canvas Shareable Workpad API Placed successfully with width specified 1`] = `"
"`; @@ -57,5 +57,5 @@ exports[`Canvas Shareable Workpad API Placed successfully with width specified 2
markdown mock
markdown mock
My Canvas Workpad
" +
markdown mock
My Canvas Workpad
" `; diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap index 8a8799207ace8..73d7599a60359 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/footer/settings/__tests__/__snapshots__/settings.test.tsx.snap @@ -65,7 +65,6 @@ exports[` can navigate Autoplay Settings 1`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <path d="M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 000-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 010 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z" /> @@ -85,7 +84,6 @@ exports[`<Settings /> can navigate Autoplay Settings 1`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -110,7 +108,6 @@ exports[`<Settings /> can navigate Autoplay Settings 1`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z" /> @@ -130,7 +127,6 @@ exports[`<Settings /> can navigate Autoplay Settings 1`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -219,7 +215,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 000-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 010 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z" /> @@ -239,7 +234,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -264,7 +258,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z" /> @@ -284,7 +277,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -317,7 +309,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M10.843 13.069L6.232 8.384a.546.546 0 010-.768l4.61-4.685a.552.552 0 000-.771.53.53 0 00-.759 0l-4.61 4.684a1.65 1.65 0 000 2.312l4.61 4.684a.53.53 0 00.76 0 .552.552 0 000-.771z" fill-rule="nonzero" @@ -366,7 +357,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M7.293 8L3.146 3.854a.5.5 0 11.708-.708L8 7.293l4.146-4.147a.5.5 0 01.708.708L8.707 8l4.147 4.146a.5.5 0 01-.708.708L8 8.707l-4.146 4.147a.5.5 0 01-.708-.708L7.293 8z" /> @@ -381,7 +371,6 @@ exports[`<Settings /> can navigate Autoplay Settings 2`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M6.5 12a.502.502 0 01-.354-.146l-4-4a.502.502 0 01.708-.708L6.5 10.793l6.646-6.647a.502.502 0 01.708.708l-7 7A.502.502 0 016.5 12" fill-rule="evenodd" @@ -561,7 +550,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 1`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 000-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 010 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z" /> @@ -581,7 +569,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 1`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -606,7 +593,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 1`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z" /> @@ -626,7 +612,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 1`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -715,7 +700,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M4.608 3.063C4.345 2.895 4 3.089 4 3.418v9.167c0 .329.345.523.608.356l7.2-4.584a.426.426 0 000-.711l-7.2-4.583zm.538-.844l7.2 4.583a1.426 1.426 0 010 2.399l-7.2 4.583C4.21 14.38 3 13.696 3 12.585V3.418C3 2.307 4.21 1.624 5.146 2.22z" /> @@ -735,7 +719,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -760,7 +743,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M0 6h4v4H0V6zm1 1v2h2V7H1zm5-1h4v4H6V6zm1 1v2h2V7H7zm5-1h4v4h-4V6zm1 3h2V7h-2v2z" /> @@ -780,7 +762,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M5.157 13.069l4.611-4.685a.546.546 0 000-.768L5.158 2.93a.552.552 0 010-.771.53.53 0 01.759 0l4.61 4.684c.631.641.63 1.672 0 2.312l-4.61 4.684a.53.53 0 01-.76 0 .552.552 0 010-.771z" fill-rule="nonzero" @@ -813,7 +794,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M10.843 13.069L6.232 8.384a.546.546 0 010-.768l4.61-4.685a.552.552 0 000-.771.53.53 0 00-.759 0l-4.61 4.684a1.65 1.65 0 000 2.312l4.61 4.684a.53.53 0 00.76 0 .552.552 0 000-.771z" fill-rule="nonzero" @@ -871,7 +851,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M7.293 8L3.146 3.854a.5.5 0 11.708-.708L8 7.293l4.146-4.147a.5.5 0 01.708.708L8.707 8l4.147 4.146a.5.5 0 01-.708.708L8 8.707l-4.146 4.147a.5.5 0 01-.708-.708L7.293 8z" /> @@ -886,7 +865,6 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M6.5 12a.502.502 0 01-.354-.146l-4-4a.502.502 0 01.708-.708L6.5 10.793l6.646-6.647a.502.502 0 01.708.708l-7 7A.502.502 0 016.5 12" fill-rule="evenodd" @@ -927,4 +905,4 @@ exports[`<Settings /> can navigate Toolbar Settings, closes when activated 2`] = </div> `; -exports[`<Settings /> can navigate Toolbar Settings, closes when activated 3`] = `"<div><div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-guard=\\"true\\" tabindex=\\"-1\\" style=\\"width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;\\"></div><div data-focus-lock-disabled=\\"disabled\\"><div class=\\"euiPanel euiPopover__panel euiPopover__panel--top euiPopover__panel-withTitle\\" aria-live=\\"assertive\\" role=\\"dialog\\" aria-modal=\\"true\\" aria-describedby=\\"generated-id\\" style=\\"top: -16px; left: -22px; z-index: 2000;\\"><div class=\\"euiPopover__panelArrow euiPopover__panelArrow--top\\" style=\\"left: 10px; top: 0px;\\"></div><div><div class=\\"euiContextMenu\\" style=\\"height: 0px;\\"><div class=\\"euiContextMenuPanel euiContextMenu__panel euiContextMenuPanel-txOutLeft\\" tabindex=\\"0\\"><div class=\\"euiPopoverTitle\\"><span class=\\"euiContextMenu__itemLayout\\">Settings</span></div><div><div><button class=\\"euiContextMenuItem\\" type=\\"button\\"><span class=\\"euiContextMenu__itemLayout\\"><svg width=\\"16\\" height=\\"16\\" viewBox=\\"0 0 16 16\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--medium euiIcon-isLoaded euiContextMenu__icon\\" focusable=\\"false\\" role=\\"img\\" aria-hidden=\\"true\\"><title>Auto Play
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; +exports[` can navigate Toolbar Settings, closes when activated 3`] = `"
Settings
Hide Toolbar
Hide the toolbar when the mouse is not within the Canvas?
"`; diff --git a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap index 9256bee4e756b..353dc58e6d401 100644 --- a/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap +++ b/x-pack/legacy/plugins/license_management/__jest__/__snapshots__/upload_license.test.tsx.snap @@ -296,7 +296,6 @@ exports[`UploadLicense should display a modal when license requires acknowledgem } > @@ -1401,7 +1399,6 @@ exports[`UploadLicense should display an error when ES says license is expired 1 width={16} xmlns="http://www.w3.org/2000/svg" > - <path d="M9 10.114l1.85-1.943a.52.52 0 01.77 0c.214.228.214.6 0 .829l-1.95 2.05a1.552 1.552 0 01-2.31 0L5.41 9a.617.617 0 010-.829.52.52 0 01.77 0L8 10.082V1.556C8 1.249 8.224 1 8.5 1s.5.249.5.556v8.558zM4.18 6a.993.993 0 00-.972.804l-1.189 6A.995.995 0 002.991 14h11.018a1 1 0 00.972-1.196l-1.19-6a.993.993 0 00-.97-.804H4.18zM6 5v1h5V5h1.825c.946 0 1.76.673 1.946 1.608l1.19 6A2 2 0 0114.016 15H2.984a1.992 1.992 0 01-1.945-2.392l1.19-6C2.414 5.673 3.229 5 4.174 5H6z" /> @@ -1871,7 +1868,6 @@ exports[`UploadLicense should display an error when ES says license is invalid 1 width={16} xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M9 10.114l1.85-1.943a.52.52 0 01.77 0c.214.228.214.6 0 .829l-1.95 2.05a1.552 1.552 0 01-2.31 0L5.41 9a.617.617 0 010-.829.52.52 0 01.77 0L8 10.082V1.556C8 1.249 8.224 1 8.5 1s.5.249.5.556v8.558zM4.18 6a.993.993 0 00-.972.804l-1.189 6A.995.995 0 002.991 14h11.018a1 1 0 00.972-1.196l-1.19-6a.993.993 0 00-.97-.804H4.18zM6 5v1h5V5h1.825c.946 0 1.76.673 1.946 1.608l1.19 6A2 2 0 0114.016 15H2.984a1.992 1.992 0 01-1.945-2.392l1.19-6C2.414 5.673 3.229 5 4.174 5H6z" /> @@ -2806,7 +2802,6 @@ exports[`UploadLicense should display error when ES returns error 1`] = ` width={16} xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M9 10.114l1.85-1.943a.52.52 0 01.77 0c.214.228.214.6 0 .829l-1.95 2.05a1.552 1.552 0 01-2.31 0L5.41 9a.617.617 0 010-.829.52.52 0 01.77 0L8 10.082V1.556C8 1.249 8.224 1 8.5 1s.5.249.5.556v8.558zM4.18 6a.993.993 0 00-.972.804l-1.189 6A.995.995 0 002.991 14h11.018a1 1 0 00.972-1.196l-1.19-6a.993.993 0 00-.97-.804H4.18zM6 5v1h5V5h1.825c.946 0 1.76.673 1.946 1.608l1.19 6A2 2 0 0114.016 15H2.984a1.992 1.992 0 01-1.945-2.392l1.19-6C2.414 5.673 3.229 5 4.174 5H6z" /> diff --git a/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap b/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap index 2dc355513ece2..c62b07a89e7a3 100644 --- a/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/components/__snapshots__/geometry_filter_form.test.js.snap @@ -33,6 +33,7 @@ exports[`should not render relation select when geo field is geo_point 1`] = ` fullWidth={true} hasDividers={true} isInvalid={false} + isLoading={false} itemClassName="mapGeometryFilter__geoFieldItem" onChange={[Function]} options={ @@ -110,6 +111,7 @@ exports[`should not show "within" relation when filter geometry is not closed 1` fullWidth={true} hasDividers={true} isInvalid={false} + isLoading={false} itemClassName="mapGeometryFilter__geoFieldItem" onChange={[Function]} options={ @@ -214,6 +216,7 @@ exports[`should render error message 1`] = ` fullWidth={true} hasDividers={true} isInvalid={false} + isLoading={false} itemClassName="mapGeometryFilter__geoFieldItem" onChange={[Function]} options={ @@ -294,6 +297,7 @@ exports[`should render relation select when geo field is geo_shape 1`] = ` fullWidth={true} hasDividers={true} isInvalid={false} + isLoading={false} itemClassName="mapGeometryFilter__geoFieldItem" onChange={[Function]} options={ diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap index 9a55e46b40aea..9d07b9c641e0f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/styles/heatmap/components/__snapshots__/heatmap_style_editor.test.js.snap @@ -15,6 +15,7 @@ exports[`HeatmapStyleEditor is rendered 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap index ee76657c8d27a..4dd77842894c6 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/__snapshots__/flow_direction_select.test.tsx.snap @@ -3,25 +3,17 @@ exports[`Select Flow Direction rendering it renders the basic group button for uni-direction and bi-direction 1`] = ` <EuiFilterGroup> <EuiFilterButton - color="text" data-test-subj="uniDirectional" - grow={true} hasActiveFilters={true} - iconSide="right" onClick={[Function]} - type="button" withNext={true} > Unidirectional </EuiFilterButton> <EuiFilterButton - color="text" data-test-subj="biDirectional" - grow={true} hasActiveFilters={false} - iconSide="right" onClick={[Function]} - type="button" > Bidirectional </EuiFilterButton> diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap index 4c9a27b76060c..8f40d0203afd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/groups_filter_popover.test.tsx.snap @@ -5,16 +5,12 @@ exports[`GroupsFilterPopover renders correctly against snapshot 1`] = ` anchorPosition="downCenter" button={ <EuiFilterButton - color="text" data-test-subj="groups-filter-popover-button" - grow={true} hasActiveFilters={false} - iconSide="right" iconType="arrowDown" isSelected={false} numActiveFilters={0} onClick={[Function]} - type="button" > Groups </EuiFilterButton> diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap index fac91f75978f0..747ac63551b55 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap @@ -110,25 +110,17 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` > <EuiFilterGroup> <EuiFilterButton - color="text" data-test-subj="show-elastic-jobs-filter-button" - grow={true} hasActiveFilters={false} - iconSide="right" onClick={[Function]} - type="button" withNext={true} > Elastic jobs </EuiFilterButton> <EuiFilterButton - color="text" data-test-subj="show-custom-jobs-filter-button" - grow={true} hasActiveFilters={false} - iconSide="right" onClick={[Function]} - type="button" > Custom jobs </EuiFilterButton> diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap index 7e3e099bf0276..28481e9970a5e 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/toasters/__snapshots__/modal_all_errors.test.tsx.snap @@ -3,7 +3,6 @@ exports[`Modal all errors rendering it renders the default all errors modal when isShowing is positive 1`] = ` <EuiOverlayMask> <EuiModal - maxWidth={true} onClose={[Function]} > <EuiModalHeader> diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap index 8930dedfa0035..6e422bc13f06b 100644 --- a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap @@ -5,7 +5,6 @@ exports[`ConfirmDeleteModal renders as expected 1`] = ` <EuiModal className="spcConfirmDeleteModal" initialFocus="input[name=\\"confirmDeleteSpaceInput\\"]" - maxWidth={true} onClose={[MockFunction]} > <EuiModalHeader> diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx index 6eed58a784212..3a4861f4fbc9e 100644 --- a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx @@ -9,8 +9,6 @@ import { EuiButton, EuiButtonEmpty, EuiCallOut, - // @ts-ignore - EuiConfirmModal, EuiFieldText, EuiFormRow, EuiModal, @@ -89,7 +87,7 @@ class ConfirmDeleteModalUI extends Component<Props, State> { // This is largely the same as the built-in EuiConfirmModal component, but we needed the ability // to disable the buttons since this could be a long-running operation - const modalProps: EuiModalProps & CommonProps = { + const modalProps: Omit<EuiModalProps, 'children'> & CommonProps = { onClose: onCancel, className: 'spcConfirmDeleteModal', initialFocus: 'input[name="confirmDeleteSpaceInput"]', diff --git a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap index d7702e2f18d44..750afcfc44e7e 100644 --- a/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap @@ -3,7 +3,6 @@ exports[`ConfirmAlterActiveSpaceModal renders as expected 1`] = ` <EuiOverlayMask> <EuiConfirmModal - buttonColor="primary" cancelButtonText="Cancel" confirmButtonText="Update space" defaultFocusedButton="confirm" diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap index 2d3351ec1c0d2..da9153f4a6c8d 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/filter_bar.test.tsx.snap @@ -6,26 +6,18 @@ exports[`FilterBar renders 1`] = ` > <EuiFilterGroup> <EuiFilterButton - color="text" - grow={true} hasActiveFilters={false} - iconSide="right" key="all" numFilters={2} onClick={[Function]} - type="button" > all </EuiFilterButton> <EuiFilterButton - color="text" - grow={true} hasActiveFilters={true} - iconSide="right" key="critical" numFilters={2} onClick={[Function]} - type="button" > critical </EuiFilterButton> diff --git a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap index b36e0c1a2bfdb..dfc69c57cfff6 100644 --- a/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap +++ b/x-pack/legacy/plugins/upgrade_assistant/public/np_ready/application/components/tabs/checkup/__snapshots__/group_by_bar.test.tsx.snap @@ -6,24 +6,16 @@ exports[`GroupByBar renders 1`] = ` > <EuiFilterGroup> <EuiFilterButton - color="text" - grow={true} hasActiveFilters={true} - iconSide="right" key="message" onClick={[Function]} - type="button" > by issue </EuiFilterButton> <EuiFilterButton - color="text" - grow={true} hasActiveFilters={false} - iconSide="right" key="index" onClick={[Function]} - type="button" > by index </EuiFilterButton> diff --git a/x-pack/package.json b/x-pack/package.json index ad0be351483f6..99e2a32bf3372 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -176,7 +176,7 @@ "@elastic/apm-rum-react": "^0.3.2", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "7.6.0", - "@elastic/eui": "18.2.1", + "@elastic/eui": "18.3.0", "@elastic/filesaver": "1.1.2", "@elastic/maki": "6.1.0", "@elastic/node-crypto": "^1.0.0", diff --git a/x-pack/plugins/apm/server/tutorial/index.ts b/x-pack/plugins/apm/server/tutorial/index.ts index bb5d553b26b36..5399d13937179 100644 --- a/x-pack/plugins/apm/server/tutorial/index.ts +++ b/x-pack/plugins/apm/server/tutorial/index.ts @@ -84,7 +84,7 @@ It allows you to monitor the performance of thousands of applications in real ti '{config.docs.base_url}guide/en/apm/get-started/{config.docs.version}/index.html', }, }), - euiIconType: 'logoAPM', + euiIconType: 'apmApp', artifacts, onPrem: onPremInstructions(indices), elasticCloud: createElasticCloudInstructions(cloud), diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap index 42fd4417e238b..f8bbfbc8bb33d 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/__snapshots__/api_keys_grid_page.test.tsx.snap @@ -49,7 +49,6 @@ exports[`APIKeysGridPage renders a callout when API keys are not enabled 1`] = ` width={16} xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M7.59 10.059L7.35 5.18h1.3L8.4 10.06h-.81zm.394 1.901a.61.61 0 01-.448-.186.606.606 0 01-.186-.444c0-.174.062-.323.186-.446a.614.614 0 01.448-.184c.169 0 .315.06.44.182.124.122.186.27.186.448a.6.6 0 01-.189.446.607.607 0 01-.437.184zM2 14a1 1 0 01-.878-1.479l6-11a1 1 0 011.756 0l6 11A1 1 0 0114 14H2zm0-1h12L8 2 2 13z" fillRule="evenodd" @@ -189,7 +188,6 @@ exports[`APIKeysGridPage renders permission denied if user does not have require width={32} xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M14 32l-.36-.14A21.07 21.07 0 010 12.07V5.44L14 .06l14 5.38v6.63a21.07 21.07 0 01-13.64 19.78L14 32zM2 6.82v5.25a19.08 19.08 0 0012 17.77 19.08 19.08 0 0012-17.77V6.82L14 2.2 2 6.82z" /> diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap index 37db2e118861e..a2741773f183b 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/simple_privilege_section/__snapshots__/simple_privilege_section.test.tsx.snap @@ -39,6 +39,7 @@ exports[`<SimplePrivilegeForm> renders without crashing 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap index e9f2f946e9885..8d10e27df9694 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/kibana/space_aware_privilege_section/__snapshots__/privilege_space_form.test.tsx.snap @@ -176,6 +176,7 @@ exports[`<PrivilegeSpaceForm> renders without crashing 1`] = ` fullWidth={true} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ diff --git a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap index 970cbfd03954a..4789314d9f780 100644 --- a/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap +++ b/x-pack/plugins/security/public/management/roles/roles_grid/__snapshots__/roles_grid_page.test.tsx.snap @@ -67,7 +67,6 @@ exports[`<RolesGridPage /> renders permission denied if required 1`] = ` width={32} xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M14 32l-.36-.14A21.07 21.07 0 010 12.07V5.44L14 .06l14 5.38v6.63a21.07 21.07 0 01-13.64 19.78L14 32zM2 6.82v5.25a19.08 19.08 0 0012 17.77 19.08 19.08 0 0012-17.77V6.82L14 2.2 2 6.82z" /> diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 47e11817ffa5d..3a6ba45413de6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1198,8 +1198,6 @@ "kbn.embeddable.inspectorRequestDataTitle": "データ", "kbn.embeddable.inspectorRequestDescription": "このリクエストは Elasticsearch にクエリをかけ、検索データを取得します。", "kbn.embeddable.search.displayName": "検索", - "kbn.home.addData.addDataToKibanaDescription": "これらのソリューションで、データを作成済みのダッシュボードと監視システムへとすぐに変えることができます。", - "kbn.home.addData.addDataToKibanaTitle": "Kibana にデータを追加", "kbn.home.addData.apm.addApmButtonLabel": "APM を追加", "kbn.home.addData.apm.nameDescription": "APM は、集約内から自動的に詳細なパフォーマンスメトリックやエラーを集めます。", "kbn.home.addData.apm.nameTitle": "APM", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 86d9a69dc0900..f7cbaa7d72158 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1198,8 +1198,6 @@ "kbn.embeddable.inspectorRequestDataTitle": "数据", "kbn.embeddable.inspectorRequestDescription": "此请求将查询 Elasticsearch 以获取搜索的数据。", "kbn.embeddable.search.displayName": "搜索", - "kbn.home.addData.addDataToKibanaDescription": "使用这些解决方案可快速将您的数据转换成预建仪表板和监测系统。", - "kbn.home.addData.addDataToKibanaTitle": "将数据添加到 Kibana", "kbn.home.addData.apm.addApmButtonLabel": "添加 APM", "kbn.home.addData.apm.nameDescription": "APM 自动从您的应用程序内收集深入全面的性能指标和错误。", "kbn.home.addData.apm.nameTitle": "APM", diff --git a/yarn.lock b/yarn.lock index a3acc2ae216c5..4b56ec6460775 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1953,10 +1953,10 @@ tabbable "^1.1.0" uuid "^3.1.0" -"@elastic/eui@18.2.1": - version "18.2.1" - resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.2.1.tgz#6ce6d0bd1d0541052d21f2918305524d71e91678" - integrity sha512-6C5tnWJTlBB++475i0vRoCsnz4JaYznb4zMNFLc+z5GY3vA3/E3AXTjmmBwybEicCCi3h1SnpJxZsgMakiZwRA== +"@elastic/eui@18.3.0": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@elastic/eui/-/eui-18.3.0.tgz#e21c6246624f694e2ae1c7c1f1a11b612faf260a" + integrity sha512-Rkj1rTtDa6iZMUF7pxYRojku1sLXzTU0FK1D9i0XE3H//exy3VyTV6qUlbdkiKXjO7emrgQqfzKDeXT+ZYztgg== dependencies: "@types/chroma-js" "^1.4.3" "@types/lodash" "^4.14.116" From 4d3803d310f991dd0ee9f1214b7c6bda50498263 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad <frank.hassanabad@elastic.co> Date: Mon, 3 Feb 2020 16:40:29 -0700 Subject: [PATCH 25/60] [SIEM][Detection Engine] Critical blocker, fixes pre-packaged rule miscounts ## Summary * Found multiple issues with how unstable finds can occur where iterating over multiple pages of find API with saved objects might return the same results per page and omit things as you try to figure out which pre-packaged rules are installed and which ones are not. * This makes a distinct trade off of doing more JSON.parse() on the event loop by querying all the pre-packaged rules at one time. This however gives a stable and accurate count * Fixed the tags aggregation to do the same thing. * Fixes https://github.com/elastic/siem-team/issues/506 ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ - [x] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ - [x] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../get_existing_prepackaged_rules.test.ts | 191 ++++-------------- .../rules/get_existing_prepackaged_rules.ts | 55 ++--- .../lib/detection_engine/tags/read_tags.ts | 46 ++--- 3 files changed, 73 insertions(+), 219 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts index dc308263baab6..8d00ddb18be6b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts @@ -35,32 +35,7 @@ describe('get_existing_prepackaged_rules', () => { expect(rules).toEqual([getResult()]); }); - test('should return 2 items over two pages, one per page', async () => { - const alertsClient = alertsClientMock.create(); - - const result1 = getResult(); - result1.params.immutable = true; - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result2 = getResult(); - result2.params.immutable = true; - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 2 }) - ); - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 2 }) - ); - - const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; - const rules = await getExistingPrepackagedRules({ - alertsClient: unsafeCast, - }); - expect(rules).toEqual([result1, result2]); - }); - - test('should return 3 items with over 3 pages one per page', async () => { + test('should return 3 items over 1 page with all on one page', async () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); @@ -75,40 +50,17 @@ describe('get_existing_prepackaged_rules', () => { result3.params.immutable = true; result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + // first result mock which is for returning the total alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result3], perPage: 1, page: 2, total: 3 }) + getFindResultWithMultiHits({ + data: [result1], + perPage: 1, + page: 1, + total: 3, + }) ); - const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; - const rules = await getExistingPrepackagedRules({ - alertsClient: unsafeCast, - }); - expect(rules).toEqual([result1, result2, result3]); - }); - - test('should return 3 items over 1 pages with all on one page', async () => { - const alertsClient = alertsClientMock.create(); - - const result1 = getResult(); - result1.params.immutable = true; - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result2 = getResult(); - result2.params.immutable = true; - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result3 = getResult(); - result3.params.immutable = true; - result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; - + // second mock which will return all the data on a single page alertsClient.find.mockResolvedValueOnce( getFindResultWithMultiHits({ data: [result1, result2, result3], @@ -137,7 +89,7 @@ describe('get_existing_prepackaged_rules', () => { expect(rules).toEqual([getResult()]); }); - test('should return 2 items over two pages, one per page', async () => { + test('should return 2 items over 1 page', async () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); @@ -146,11 +98,19 @@ describe('get_existing_prepackaged_rules', () => { const result2 = getResult(); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + // first result mock which is for returning the total alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 2 }) + getFindResultWithMultiHits({ + data: [result1], + perPage: 1, + page: 1, + total: 2, + }) ); + + // second mock which will return all the data on a single page alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 2 }) + getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 }) ); const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; @@ -160,7 +120,7 @@ describe('get_existing_prepackaged_rules', () => { expect(rules).toEqual([result1, result2]); }); - test('should return 3 items with over 3 pages one per page', async () => { + test('should return 3 items over 1 page with all on one page', async () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); @@ -172,37 +132,17 @@ describe('get_existing_prepackaged_rules', () => { const result3 = getResult(); result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + // first result mock which is for returning the total alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result3], perPage: 1, page: 2, total: 3 }) + getFindResultWithMultiHits({ + data: [result1], + perPage: 3, + page: 1, + total: 3, + }) ); - const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; - const rules = await getNonPackagedRules({ - alertsClient: unsafeCast, - }); - expect(rules).toEqual([result1, result2, result3]); - }); - - test('should return 3 items over 1 pages with all on one page', async () => { - const alertsClient = alertsClientMock.create(); - - const result1 = getResult(); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result2 = getResult(); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result3 = getResult(); - result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; - + // second mock which will return all the data on a single page alertsClient.find.mockResolvedValueOnce( getFindResultWithMultiHits({ data: [result1, result2, result3], @@ -241,80 +181,27 @@ describe('get_existing_prepackaged_rules', () => { const result2 = getResult(); result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 2 }) - ); - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 2 }) - ); - - const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; - const rules = await getRules({ - alertsClient: unsafeCast, - filter: '', - }); - expect(rules).toEqual([result1, result2]); - }); - - test('should return 3 items with over 3 pages one per page', async () => { - const alertsClient = alertsClientMock.create(); - - const result1 = getResult(); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result2 = getResult(); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result3 = getResult(); - result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 3 }) - ); - - alertsClient.find.mockResolvedValueOnce( - getFindResultWithMultiHits({ data: [result3], perPage: 1, page: 2, total: 3 }) - ); - - const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; - const rules = await getRules({ - alertsClient: unsafeCast, - filter: '', - }); - expect(rules).toEqual([result1, result2, result3]); - }); - - test('should return 3 items over 1 pages with all on one page', async () => { - const alertsClient = alertsClientMock.create(); - - const result1 = getResult(); - result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result2 = getResult(); - result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; - - const result3 = getResult(); - result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; - + // first result mock which is for returning the total alertsClient.find.mockResolvedValueOnce( getFindResultWithMultiHits({ - data: [result1, result2, result3], - perPage: 3, + data: [result1], + perPage: 1, page: 1, - total: 3, + total: 2, }) ); + // second mock which will return all the data on a single page + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result1, result2], perPage: 2, page: 1, total: 2 }) + ); + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; const rules = await getRules({ alertsClient: unsafeCast, filter: '', }); - expect(rules).toEqual([result1, result2, result3]); + expect(rules).toEqual([result1, result2]); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index b7ab6a97634a8..a48957da7aa94 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -9,7 +9,6 @@ import { AlertsClient } from '../../../../../alerting'; import { RuleAlertType, isAlertTypes } from './types'; import { findRules } from './find_rules'; -export const DEFAULT_PER_PAGE = 100; export const FILTER_NON_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:false"`; export const FILTER_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`; @@ -33,84 +32,56 @@ export const getRulesCount = async ({ filter, perPage: 1, page: 1, + sortField: 'createdAt', + sortOrder: 'desc', }); return firstRule.total; }; export const getRules = async ({ alertsClient, - perPage = DEFAULT_PER_PAGE, filter, }: { alertsClient: AlertsClient; - perPage?: number; filter: string; }): Promise<RuleAlertType[]> => { - const firstPrepackedRules = await findRules({ + const count = await getRulesCount({ alertsClient, filter }); + const rules = await findRules({ alertsClient, filter, - perPage, + perPage: count, page: 1, + sortField: 'createdAt', + sortOrder: 'desc', }); - const totalPages = Math.ceil(firstPrepackedRules.total / firstPrepackedRules.perPage); - if (totalPages <= 1) { - if (isAlertTypes(firstPrepackedRules.data)) { - return firstPrepackedRules.data; - } else { - // If this was ever true, you have a really messed up system. - // This is keep typescript happy since we have an unknown with data - return []; - } - } else { - const returnPrepackagedRules = await Array(totalPages - 1) - .fill({}) - .map((_, page) => { - // page index starts at 2 as we already got the first page and we have more pages to go - return findRules({ - alertsClient, - filter, - perPage, - page: page + 2, - }); - }) - .reduce<Promise<object[]>>(async (accum, nextPage) => { - return [...(await accum), ...(await nextPage).data]; - }, Promise.resolve(firstPrepackedRules.data)); - if (isAlertTypes(returnPrepackagedRules)) { - return returnPrepackagedRules; - } else { - // If this was ever true, you have a really messed up system. - // This is keep typescript happy since we have an unknown with data - return []; - } + if (isAlertTypes(rules.data)) { + return rules.data; + } else { + // If this was ever true, you have a really messed up system. + // This is keep typescript happy since we have an unknown with data + return []; } }; export const getNonPackagedRules = async ({ alertsClient, - perPage = DEFAULT_PER_PAGE, }: { alertsClient: AlertsClient; - perPage?: number; }): Promise<RuleAlertType[]> => { return getRules({ alertsClient, - perPage, filter: FILTER_NON_PREPACKED_RULES, }); }; export const getExistingPrepackagedRules = async ({ alertsClient, - perPage = DEFAULT_PER_PAGE, }: { alertsClient: AlertsClient; - perPage?: number; }): Promise<RuleAlertType[]> => { return getRules({ alertsClient, - perPage, filter: FILTER_PREPACKED_RULES, }); }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/tags/read_tags.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/tags/read_tags.ts index 0f973d816917f..02456732df3b4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/tags/read_tags.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/tags/read_tags.ts @@ -9,8 +9,6 @@ import { INTERNAL_IDENTIFIER } from '../../../../common/constants'; import { AlertsClient } from '../../../../../alerting'; import { findRules } from '../rules/find_rules'; -const DEFAULT_PER_PAGE: number = 1000; - export interface TagType { id: string; tags: string[]; @@ -42,39 +40,37 @@ export const convertTagsToSet = (tagObjects: object[]): Set<string> => { // Ref: https://www.elastic.co/guide/en/kibana/master/saved-objects-api.html export const readTags = async ({ alertsClient, - perPage = DEFAULT_PER_PAGE, }: { alertsClient: AlertsClient; - perPage?: number; }): Promise<string[]> => { - const tags = await readRawTags({ alertsClient, perPage }); + const tags = await readRawTags({ alertsClient }); return tags.filter(tag => !tag.startsWith(INTERNAL_IDENTIFIER)); }; export const readRawTags = async ({ alertsClient, - perPage = DEFAULT_PER_PAGE, }: { alertsClient: AlertsClient; perPage?: number; }): Promise<string[]> => { - const firstTags = await findRules({ alertsClient, fields: ['tags'], perPage, page: 1 }); - const firstSet = convertTagsToSet(firstTags.data); - const totalPages = Math.ceil(firstTags.total / firstTags.perPage); - if (totalPages <= 1) { - return Array.from(firstSet); - } else { - const returnTags = await Array(totalPages - 1) - .fill({}) - .map((_, page) => { - // page index starts at 2 as we already got the first page and we have more pages to go - return findRules({ alertsClient, fields: ['tags'], perPage, page: page + 2 }); - }) - .reduce<Promise<Set<string>>>(async (accum, nextTagPage) => { - const tagArray = convertToTags((await nextTagPage).data); - return new Set([...(await accum), ...tagArray]); - }, Promise.resolve(firstSet)); - - return Array.from(returnTags); - } + // Get just one record so we can get the total count + const firstTags = await findRules({ + alertsClient, + fields: ['tags'], + perPage: 1, + page: 1, + sortField: 'createdAt', + sortOrder: 'desc', + }); + // Get all the rules to aggregate over all the tags of the rules + const rules = await findRules({ + alertsClient, + fields: ['tags'], + perPage: firstTags.total, + sortField: 'createdAt', + sortOrder: 'desc', + page: 1, + }); + const tagSet = convertTagsToSet(rules.data); + return Array.from(tagSet); }; From 653c28a8895e08665f35e8c88c8fc1413f67bff4 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian <andrew@andrewvc.com> Date: Mon, 3 Feb 2020 20:09:56 -0600 Subject: [PATCH 26/60] [Uptime] Add unit tests for QueryContext time calculation (#56671) Add Unit tests for the QueryContext class that was missing testing. This would have caught #56612 --- .../search/__tests__/query_context.test.ts | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/query_context.test.ts diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/query_context.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/query_context.test.ts new file mode 100644 index 0000000000000..8924d07ac0c4d --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/query_context.test.ts @@ -0,0 +1,83 @@ +/* + * 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 { QueryContext } from '../query_context'; +import { CursorPagination } from '../..'; +import { CursorDirection, SortOrder } from '../../../../../../common/graphql/types'; + +describe(QueryContext, () => { + // 10 minute range + const rangeStart = '2019-02-03T19:06:54.939Z'; + const rangeEnd = '2019-02-03T19:16:54.939Z'; + + const pagination: CursorPagination = { + cursorDirection: CursorDirection.AFTER, + sortOrder: SortOrder.DESC, + }; + + let qc: QueryContext; + beforeEach(() => (qc = new QueryContext({}, rangeStart, rangeEnd, pagination, null, 10))); + + describe('dateRangeFilter()', () => { + const expectedRange = { + range: { + '@timestamp': { + gte: rangeStart, + lte: rangeEnd, + }, + }, + }; + describe('when hasTimespan() is true', () => { + it('should create a date range filter including the timespan', async () => { + const mockHasTimespan = jest.fn(); + mockHasTimespan.mockReturnValue(true); + qc.hasTimespan = mockHasTimespan; + + expect(await qc.dateRangeFilter()).toEqual({ + bool: { + filter: [ + expectedRange, + { + bool: { + should: [ + qc.timespanClause(), + { bool: { must_not: { exists: { field: 'monitor.timespan' } } } }, + ], + }, + }, + ], + }, + }); + }); + }); + + describe('when hasTimespan() is false', () => { + it('should only use the timestamp fields in the returned filter', async () => { + const mockHasTimespan = jest.fn(); + mockHasTimespan.mockReturnValue(false); + qc.hasTimespan = mockHasTimespan; + + expect(await qc.dateRangeFilter()).toEqual(expectedRange); + }); + }); + }); + + describe('timespanClause()', () => { + it('should always cover the last 5m', () => { + // 5m expected range between GTE and LTE in the response + // since timespan is hardcoded to 5m + expect(qc.timespanClause()).toEqual({ + range: { + 'monitor.timespan': { + // end date minus 5m + gte: new Date(Date.parse(rangeEnd) - 5 * 60 * 1000).toISOString(), + lte: rangeEnd, + }, + }, + }); + }); + }); +}); From 0f117c9c3276c40f40a4812c128a266db6174346 Mon Sep 17 00:00:00 2001 From: Nick Partridge <nick.ryan.partridge@gmail.com> Date: Mon, 3 Feb 2020 22:05:32 -0600 Subject: [PATCH 27/60] Vislib replacement toggle (#56439) * Add new vislib replacement plugin shell * Add config to toggle new vislib replacement --- .github/CODEOWNERS | 1 + .i18nrc.json | 6 +- .sass-lint.yml | 1 + .../vis_type_vislib/public/plugin.ts | 32 ++++++-- src/legacy/core_plugins/vis_type_xy/index.ts | 56 +++++++++++++ .../core_plugins/vis_type_xy/package.json | 4 + .../core_plugins/vis_type_xy/public/index.ts | 25 ++++++ .../core_plugins/vis_type_xy/public/legacy.ts | 44 ++++++++++ .../core_plugins/vis_type_xy/public/plugin.ts | 82 +++++++++++++++++++ 9 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 src/legacy/core_plugins/vis_type_xy/index.ts create mode 100644 src/legacy/core_plugins/vis_type_xy/package.json create mode 100644 src/legacy/core_plugins/vis_type_xy/public/index.ts create mode 100644 src/legacy/core_plugins/vis_type_xy/public/legacy.ts create mode 100644 src/legacy/core_plugins/vis_type_xy/public/plugin.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0b0addf117f6f..de7159489689e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -15,6 +15,7 @@ /src/legacy/core_plugins/kibana/public/dev_tools/ @elastic/kibana-app /src/legacy/core_plugins/metrics/ @elastic/kibana-app /src/legacy/core_plugins/vis_type_vislib/ @elastic/kibana-app +/src/legacy/core_plugins/vis_type_xy/ @elastic/kibana-app # Exclude tutorials folder for now because they are not owned by Kibana app and most will move out soon /src/plugins/home/public @elastic/kibana-app /src/plugins/home/server/*.ts @elastic/kibana-app diff --git a/.i18nrc.json b/.i18nrc.json index 1230151212f57..7d7685b5c1ef1 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -22,7 +22,10 @@ "interpreter": "src/legacy/core_plugins/interpreter", "kbn": "src/legacy/core_plugins/kibana", "kbnDocViews": "src/legacy/core_plugins/kbn_doc_views", - "management": ["src/legacy/core_plugins/management", "src/plugins/management"], + "management": [ + "src/legacy/core_plugins/management", + "src/plugins/management" + ], "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", @@ -43,6 +46,7 @@ "visTypeTimeseries": ["src/legacy/core_plugins/vis_type_timeseries", "src/plugins/vis_type_timeseries"], "visTypeVega": "src/legacy/core_plugins/vis_type_vega", "visTypeVislib": "src/legacy/core_plugins/vis_type_vislib", + "visTypeXy": "src/legacy/core_plugins/vis_type_xy", "visualizations": [ "src/plugins/visualizations", "src/legacy/core_plugins/visualizations" diff --git a/.sass-lint.yml b/.sass-lint.yml index fba2c003484f6..9c64c1e5eea56 100644 --- a/.sass-lint.yml +++ b/.sass-lint.yml @@ -3,6 +3,7 @@ files: - 'src/legacy/core_plugins/metrics/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/timelion/**/*.s+(a|c)ss' - 'src/legacy/core_plugins/vis_type_vislib/**/*.s+(a|c)ss' + - 'src/legacy/core_plugins/vis_type_xy/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/rollup/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/security/**/*.s+(a|c)ss' - 'x-pack/legacy/plugins/canvas/**/*.s+(a|c)ss' diff --git a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts index 9bf7ee3d59401..056849a292657 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/plugin.ts @@ -39,6 +39,7 @@ import { createGoalVisTypeDefinition, } from './vis_type_vislib_vis_types'; import { ChartsPluginSetup } from '../../../../plugins/charts/public'; +import { ConfigSchema as VisTypeXyConfigSchema } from '../../vis_type_xy'; export interface VisTypeVislibDependencies { uiSettings: IUiSettingsClient; @@ -72,11 +73,7 @@ export class VisTypeVislibPlugin implements Plugin<Promise<void>, void> { uiSettings: core.uiSettings, charts, }; - - expressions.registerFunction(createVisTypeVislibVisFn); - expressions.registerFunction(createPieVisFn); - - [ + const vislibTypes = [ createHistogramVisTypeDefinition, createLineVisTypeDefinition, createPieVisTypeDefinition, @@ -85,7 +82,30 @@ export class VisTypeVislibPlugin implements Plugin<Promise<void>, void> { createHorizontalBarVisTypeDefinition, createGaugeVisTypeDefinition, createGoalVisTypeDefinition, - ].forEach(vis => visualizations.types.createBaseVisualization(vis(visualizationDependencies))); + ]; + const vislibFns = [createVisTypeVislibVisFn, createPieVisFn]; + + const visTypeXy = core.injectedMetadata.getInjectedVar('visTypeXy') as + | VisTypeXyConfigSchema['visTypeXy'] + | undefined; + + // if visTypeXy plugin is disabled it's config will be undefined + if (!visTypeXy || !visTypeXy.enabled) { + const convertedTypes: any[] = []; + const convertedFns: any[] = []; + + // Register legacy vislib types that have been converted + convertedFns.forEach(expressions.registerFunction); + convertedTypes.forEach(vis => + visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + ); + } + + // Register non-converted types + vislibFns.forEach(expressions.registerFunction); + vislibTypes.forEach(vis => + visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + ); } public start(core: CoreStart, deps: VisTypeVislibPluginStartDependencies) { diff --git a/src/legacy/core_plugins/vis_type_xy/index.ts b/src/legacy/core_plugins/vis_type_xy/index.ts new file mode 100644 index 0000000000000..975399f891503 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_xy/index.ts @@ -0,0 +1,56 @@ +/* + * 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 { resolve } from 'path'; +import { Legacy } from 'kibana'; + +import { LegacyPluginApi, LegacyPluginInitializer } from '../../types'; + +export interface ConfigSchema { + visTypeXy: { + enabled: boolean; + }; +} + +const visTypeXyPluginInitializer: LegacyPluginInitializer = ({ Plugin }: LegacyPluginApi) => + new Plugin({ + id: 'visTypeXy', + require: ['kibana', 'elasticsearch', 'visualizations', 'interpreter', 'data'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + hacks: [resolve(__dirname, 'public/legacy')], + injectDefaultVars(server): ConfigSchema { + const config = server.config(); + + return { + visTypeXy: { + enabled: config.get('visTypeXy.enabled') as boolean, + }, + }; + }, + }, + config(Joi: any) { + return Joi.object({ + enabled: Joi.boolean().default(false), + }).default(); + }, + } as Legacy.PluginSpecOptions); + +// eslint-disable-next-line import/no-default-export +export default visTypeXyPluginInitializer; diff --git a/src/legacy/core_plugins/vis_type_xy/package.json b/src/legacy/core_plugins/vis_type_xy/package.json new file mode 100644 index 0000000000000..920f7dcb44e87 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_xy/package.json @@ -0,0 +1,4 @@ +{ + "name": "visTypeXy", + "version": "kibana" +} diff --git a/src/legacy/core_plugins/vis_type_xy/public/index.ts b/src/legacy/core_plugins/vis_type_xy/public/index.ts new file mode 100644 index 0000000000000..218dc8aa8a683 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_xy/public/index.ts @@ -0,0 +1,25 @@ +/* + * 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 { PluginInitializerContext } from '../../../../core/public'; +import { VisTypeXyPlugin as Plugin } from './plugin'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new Plugin(initializerContext); +} diff --git a/src/legacy/core_plugins/vis_type_xy/public/legacy.ts b/src/legacy/core_plugins/vis_type_xy/public/legacy.ts new file mode 100644 index 0000000000000..e1cee9c30804a --- /dev/null +++ b/src/legacy/core_plugins/vis_type_xy/public/legacy.ts @@ -0,0 +1,44 @@ +/* + * 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 { npSetup, npStart } from 'ui/new_platform'; +import { PluginInitializerContext } from 'kibana/public'; + +import { plugin } from '.'; +import { VisTypeXyPluginSetupDependencies, VisTypeXyPluginStartDependencies } from './plugin'; +import { + setup as visualizationsSetup, + start as visualizationsStart, +} from '../../visualizations/public/np_ready/public/legacy'; + +const setupPlugins: Readonly<VisTypeXyPluginSetupDependencies> = { + expressions: npSetup.plugins.expressions, + visualizations: visualizationsSetup, + charts: npSetup.plugins.charts, +}; + +const startPlugins: Readonly<VisTypeXyPluginStartDependencies> = { + expressions: npStart.plugins.expressions, + visualizations: visualizationsStart, +}; + +const pluginInstance = plugin({} as PluginInitializerContext); + +export const setup = pluginInstance.setup(npSetup.core, setupPlugins); +export const start = pluginInstance.start(npStart.core, startPlugins); diff --git a/src/legacy/core_plugins/vis_type_xy/public/plugin.ts b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts new file mode 100644 index 0000000000000..59bb64b337256 --- /dev/null +++ b/src/legacy/core_plugins/vis_type_xy/public/plugin.ts @@ -0,0 +1,82 @@ +/* + * 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 { + CoreSetup, + CoreStart, + Plugin, + IUiSettingsClient, + PluginInitializerContext, +} from 'kibana/public'; + +import { Plugin as ExpressionsPublicPlugin } from '../../../../plugins/expressions/public'; +import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; +import { ChartsPluginSetup } from '../../../../plugins/charts/public'; + +export interface VisTypeXyDependencies { + uiSettings: IUiSettingsClient; + charts: ChartsPluginSetup; +} + +/** @internal */ +export interface VisTypeXyPluginSetupDependencies { + expressions: ReturnType<ExpressionsPublicPlugin['setup']>; + visualizations: VisualizationsSetup; + charts: ChartsPluginSetup; +} + +/** @internal */ +export interface VisTypeXyPluginStartDependencies { + expressions: ReturnType<ExpressionsPublicPlugin['start']>; + visualizations: VisualizationsStart; +} + +type VisTypeXyCoreSetup = CoreSetup<VisTypeXyPluginStartDependencies>; + +/** @internal */ +export class VisTypeXyPlugin implements Plugin<Promise<void>, void> { + constructor(public initializerContext: PluginInitializerContext) {} + + public async setup( + core: VisTypeXyCoreSetup, + { expressions, visualizations, charts }: VisTypeXyPluginSetupDependencies + ) { + // eslint-disable-next-line no-console + console.warn( + 'The visTypeXy plugin is enabled\n\n', + 'This may negatively alter existing vislib visualization configurations if saved.' + ); + const visualizationDependencies: Readonly<VisTypeXyDependencies> = { + uiSettings: core.uiSettings, + charts, + }; + + const visTypeDefinitions: any[] = []; + const visFunctions: any = []; + + visFunctions.forEach((fn: any) => expressions.registerFunction(fn)); + visTypeDefinitions.forEach((vis: any) => + visualizations.types.createBaseVisualization(vis(visualizationDependencies)) + ); + } + + public start(core: CoreStart, deps: VisTypeXyPluginStartDependencies) { + // nothing to do here + } +} From 186a82669f9c2f48080ccfac4efa515c3065bdb5 Mon Sep 17 00:00:00 2001 From: Nick Partridge <nick.ryan.partridge@gmail.com> Date: Mon, 3 Feb 2020 22:17:27 -0600 Subject: [PATCH 28/60] Kibana property config migrations (#55937) * Move defaultAppId config param into kibanaLegacy * Move disableWelcomeScreen config param into Home plugin * Update api and docs with silent option for renameFromRoot --- ...-plugin-server.configdeprecationfactory.md | 2 +- ...configdeprecationfactory.renamefromroot.md | 3 +- .../config/deprecation/deprecation_factory.ts | 20 ++++++--- src/core/server/config/deprecation/types.ts | 2 +- src/core/server/kibana_config.ts | 2 - src/core/server/mocks.ts | 2 +- .../server/plugins/plugin_context.test.ts | 2 +- src/core/server/plugins/types.ts | 2 +- src/core/server/server.api.md | 2 +- src/legacy/core_plugins/kibana/index.js | 2 - src/legacy/core_plugins/kibana/inject_vars.js | 2 - .../public/dashboard/np_ready/application.ts | 2 + .../dashboard/np_ready/dashboard_app.tsx | 2 - .../np_ready/dashboard_app_controller.tsx | 6 +-- .../public/dashboard/np_ready/legacy_app.js | 4 +- .../kibana/public/dashboard/plugin.ts | 1 + .../kibana/public/home/kibana_services.ts | 9 +++- .../public/home/np_ready/components/home.js | 2 +- .../home/np_ready/components/home.test.js | 1 + .../home/np_ready/components/home_app.js | 4 +- .../core_plugins/kibana/public/home/plugin.ts | 5 +++ .../core_plugins/kibana/public/kibana.js | 4 +- .../public/visualize/kibana_services.ts | 2 + .../public/visualize/np_ready/legacy_app.js | 2 +- .../kibana/public/visualize/plugin.ts | 1 + .../public/new_platform/__mocks__/helpers.ts | 3 ++ .../new_platform/new_platform.karma_mock.js | 12 +++++ src/plugins/home/config.ts | 26 +++++++++++ src/plugins/home/public/index.ts | 5 ++- src/plugins/home/public/plugin.test.ts | 11 +++-- src/plugins/home/public/plugin.ts | 12 +++-- src/plugins/home/server/index.ts | 13 +++++- src/plugins/home/server/plugin.ts | 2 +- src/plugins/kibana_legacy/config.ts | 26 +++++++++++ src/plugins/kibana_legacy/kibana.json | 2 +- src/plugins/kibana_legacy/public/index.ts | 5 +-- src/plugins/kibana_legacy/public/mocks.ts | 44 +++++++++++++++++++ src/plugins/kibana_legacy/public/plugin.ts | 9 +++- src/plugins/kibana_legacy/server/index.ts | 41 +++++++++++++++++ .../public/management_service.test.ts | 7 +-- test/common/config.js | 2 +- .../dashboard_mode/public/dashboard_viewer.js | 8 ++-- 42 files changed, 257 insertions(+), 57 deletions(-) create mode 100644 src/plugins/home/config.ts create mode 100644 src/plugins/kibana_legacy/config.ts create mode 100644 src/plugins/kibana_legacy/public/mocks.ts create mode 100644 src/plugins/kibana_legacy/server/index.ts diff --git a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md index c61907f366301..2ebee16874c80 100644 --- a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md +++ b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md @@ -30,7 +30,7 @@ const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ | Method | Description | | --- | --- | | [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | -| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.<!-- -->This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | +| [renameFromRoot(oldKey, newKey, silent)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.<!-- -->This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | | [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | | [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.<!-- -->This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | diff --git a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.renamefromroot.md b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.renamefromroot.md index 269f242ec35da..40ea891b17c95 100644 --- a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.renamefromroot.md +++ b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.renamefromroot.md @@ -11,7 +11,7 @@ This should be only used when renaming properties from different configuration's <b>Signature:</b> ```typescript -renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; +renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; ``` ## Parameters @@ -20,6 +20,7 @@ renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; | --- | --- | --- | | oldKey | <code>string</code> | | | newKey | <code>string</code> | | +| silent | <code>boolean</code> | | <b>Returns:</b> diff --git a/src/core/server/config/deprecation/deprecation_factory.ts b/src/core/server/config/deprecation/deprecation_factory.ts index 6f7ed4c4e84cc..0b19a99624311 100644 --- a/src/core/server/config/deprecation/deprecation_factory.ts +++ b/src/core/server/config/deprecation/deprecation_factory.ts @@ -26,7 +26,8 @@ const _rename = ( rootPath: string, log: ConfigDeprecationLogger, oldKey: string, - newKey: string + newKey: string, + silent?: boolean ) => { const fullOldPath = getPath(rootPath, oldKey); const oldValue = get(config, fullOldPath); @@ -40,11 +41,16 @@ const _rename = ( const newValue = get(config, fullNewPath); if (newValue === undefined) { set(config, fullNewPath, oldValue); - log(`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`); + + if (!silent) { + log(`"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}"`); + } } else { - log( - `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"` - ); + if (!silent) { + log( + `"${fullOldPath}" is deprecated and has been replaced by "${fullNewPath}". However both key are present, ignoring "${fullOldPath}"` + ); + } } return config; }; @@ -67,11 +73,11 @@ const _unused = ( const rename = (oldKey: string, newKey: string): ConfigDeprecation => (config, rootPath, log) => _rename(config, rootPath, log, oldKey, newKey); -const renameFromRoot = (oldKey: string, newKey: string): ConfigDeprecation => ( +const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation => ( config, rootPath, log -) => _rename(config, '', log, oldKey, newKey); +) => _rename(config, '', log, oldKey, newKey, silent); const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => _unused(config, rootPath, log, unusedKey); diff --git a/src/core/server/config/deprecation/types.ts b/src/core/server/config/deprecation/types.ts index 19fba7800c919..dbfbad771f074 100644 --- a/src/core/server/config/deprecation/types.ts +++ b/src/core/server/config/deprecation/types.ts @@ -102,7 +102,7 @@ export interface ConfigDeprecationFactory { * ] * ``` */ - renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; + renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; /** * Remove a configuration property from inside a plugin's configuration path. * Will log a deprecation warning if the unused key was found and deprecation applied. diff --git a/src/core/server/kibana_config.ts b/src/core/server/kibana_config.ts index d46960289a8d0..17f77a6e9328f 100644 --- a/src/core/server/kibana_config.ts +++ b/src/core/server/kibana_config.ts @@ -25,9 +25,7 @@ export const config = { path: 'kibana', schema: schema.object({ enabled: schema.boolean({ defaultValue: true }), - defaultAppId: schema.string({ defaultValue: 'home' }), index: schema.string({ defaultValue: '.kibana' }), - disableWelcomeScreen: schema.boolean({ defaultValue: false }), autocompleteTerminateAfter: schema.duration({ defaultValue: 100000 }), autocompleteTimeout: schema.duration({ defaultValue: 1000 }), }), diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 50ce507520d04..7d6f09b5232c0 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -43,7 +43,7 @@ import { uuidServiceMock } from './uuid/uuid_service.mock'; export function pluginInitializerContextConfigMock<T>(config: T) { const globalConfig: SharedGlobalConfig = { - kibana: { defaultAppId: 'home-mocks', index: '.kibana-tests' }, + kibana: { index: '.kibana-tests' }, elasticsearch: { shardTimeout: duration('30s'), requestTimeout: duration('30s'), diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 3fcd7fbbbe1ff..823299771544c 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -75,7 +75,7 @@ describe('Plugin Context', () => { .pipe(first()) .toPromise(); expect(configObject).toStrictEqual({ - kibana: { defaultAppId: 'home', index: '.kibana' }, + kibana: { index: '.kibana' }, elasticsearch: { shardTimeout: duration(30, 's'), requestTimeout: duration(30, 's'), diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index a89e2f8c684e4..9ae04787767bb 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -214,7 +214,7 @@ export interface Plugin< export const SharedGlobalConfigKeys = { // We can add more if really needed - kibana: ['defaultAppId', 'index'] as const, + kibana: ['index'] as const, elasticsearch: ['shardTimeout', 'requestTimeout', 'pingTimeout', 'startupTimeout'] as const, path: ['data'] as const, }; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index e4ea06769007a..20d9692391a69 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -509,7 +509,7 @@ export type ConfigDeprecation = (config: Record<string, any>, fromPath: string, // @public export interface ConfigDeprecationFactory { rename(oldKey: string, newKey: string): ConfigDeprecation; - renameFromRoot(oldKey: string, newKey: string): ConfigDeprecation; + renameFromRoot(oldKey: string, newKey: string, silent?: boolean): ConfigDeprecation; unused(unusedKey: string): ConfigDeprecation; unusedFromRoot(unusedKey: string): ConfigDeprecation; } diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 8e0497732e230..8c35044b52c9e 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -42,9 +42,7 @@ export default function(kibana) { config: function(Joi) { return Joi.object({ enabled: Joi.boolean().default(true), - defaultAppId: Joi.string().default('home'), index: Joi.string().default('.kibana'), - disableWelcomeScreen: Joi.boolean().default(false), autocompleteTerminateAfter: Joi.number() .integer() .min(1) diff --git a/src/legacy/core_plugins/kibana/inject_vars.js b/src/legacy/core_plugins/kibana/inject_vars.js index 4bf11f28732ee..01623341e4d38 100644 --- a/src/legacy/core_plugins/kibana/inject_vars.js +++ b/src/legacy/core_plugins/kibana/inject_vars.js @@ -28,8 +28,6 @@ export function injectVars(server) { ); return { - kbnDefaultAppId: serverConfig.get('kibana.defaultAppId'), - disableWelcomeScreen: serverConfig.get('kibana.disableWelcomeScreen'), importAndExportableTypes, autocompleteTerminateAfter: serverConfig.get('kibana.autocompleteTerminateAfter'), autocompleteTimeout: serverConfig.get('kibana.autocompleteTimeout'), diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts index 0d461028d994a..f1fd93fd09b3d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/application.ts @@ -46,6 +46,7 @@ import { IEmbeddableStart } from '../../../../../../plugins/embeddable/public'; import { NavigationPublicPluginStart as NavigationStart } from '../../../../../../plugins/navigation/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../../plugins/data/public'; import { SharePluginStart } from '../../../../../../plugins/share/public'; +import { KibanaLegacyStart } from '../../../../../../plugins/kibana_legacy/public'; export interface RenderDeps { core: LegacyCoreStart; @@ -62,6 +63,7 @@ export interface RenderDeps { embeddables: IEmbeddableStart; localStorage: Storage; share: SharePluginStart; + config: KibanaLegacyStart['config']; } let angularModuleInstance: IModule | null = null; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx index a48c165116304..0537e3f8fc456 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app.tsx @@ -88,7 +88,6 @@ export interface DashboardAppScope extends ng.IScope { export function initDashboardAppDirective(app: any, deps: RenderDeps) { app.directive('dashboardApp', function($injector: IInjector) { const confirmModal = $injector.get<ConfirmModalFn>('confirmModal'); - const config = deps.uiSettings; return { restrict: 'E', @@ -106,7 +105,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $route, $scope, $routeParams, - config, confirmModal, indexPatterns: deps.npDataStart.indexPatterns, kbnUrlStateStorage, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index a8eec9c2504a7..624be02ac3b9d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -90,7 +90,6 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $routeParams: any; indexPatterns: IndexPatternsContract; dashboardConfig: any; - config: any; confirmModal: ConfirmModalFn; history: History; kbnUrlStateStorage: IKbnUrlStateStorage; @@ -109,7 +108,6 @@ export class DashboardAppController { dashboardConfig, localStorage, indexPatterns, - config, confirmModal, savedQueryService, embeddables, @@ -376,7 +374,7 @@ export class DashboardAppController { dashboardStateManager.getQuery() || { query: '', language: - localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), + localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), }, queryFilter.getFilters() ); @@ -493,7 +491,7 @@ export class DashboardAppController { { query: '', language: - localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), + localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage'), }, queryFilter.getGlobalFilters() ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js index abc0c789326f8..b0f70b7a0c68f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/legacy_app.js @@ -246,10 +246,10 @@ export function initDashboardApp(app, deps) { }, }) .when(`dashboard/:tail*?`, { - redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + redirectTo: `/${deps.config.defaultAppId}`, }) .when(`dashboards/:tail*?`, { - redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + redirectTo: `/${deps.config.defaultAppId}`, }); }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index ca4b18a37504c..227bcb53ca0df 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -107,6 +107,7 @@ export class DashboardPlugin implements Plugin { chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, uiSettings: contextCore.uiSettings, + config: kibana_legacy.config, savedQueryService: npDataStart.query.savedQueries, embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 4d9177735556d..90fb32a88d43c 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -29,7 +29,12 @@ import { UiSettingsState, } from 'kibana/public'; import { UiStatsMetricType } from '@kbn/analytics'; -import { Environment, FeatureCatalogueEntry } from '../../../../../plugins/home/public'; +import { + Environment, + FeatureCatalogueEntry, + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; export interface HomeKibanaServices { indexPatternService: any; @@ -51,6 +56,8 @@ export interface HomeKibanaServices { chrome: ChromeStart; telemetryOptInProvider: any; uiSettings: IUiSettingsClient; + config: KibanaLegacySetup['config']; + homeConfig: HomePublicPluginSetup['config']; http: HttpStart; savedObjectsClient: SavedObjectsClientContract; toastNotifications: NotificationsSetup['toasts']; diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js index 5c32a463da115..0c09c6c3c74fc 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.js @@ -48,7 +48,7 @@ export class Home extends Component { super(props); const isWelcomeEnabled = !( - getServices().getInjected('disableWelcomeScreen') || + getServices().homeConfig.disableWelcomeScreen || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' ); const currentOptInStatus = this.props.getOptInStatus(); diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js index 27d4f1a8b1c1f..d25a1f81dae5a 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home.test.js @@ -30,6 +30,7 @@ jest.mock('../../kibana_services', () => ({ getServices: () => ({ getBasePath: () => 'path', getInjected: () => '', + homeConfig: { disableWelcomeScreen: false }, }), })); diff --git a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js index e49f00b949da5..f6c91b412381c 100644 --- a/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/np_ready/components/home_app.js @@ -30,7 +30,7 @@ import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { getServices } from '../../kibana_services'; export function HomeApp({ directories }) { const { - getInjected, + config, savedObjectsClient, getBasePath, addBasePath, @@ -41,7 +41,7 @@ export function HomeApp({ directories }) { const mlEnabled = environment.ml; const apmUiEnabled = environment.apmUi; - const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); + const defaultAppId = config.defaultAppId || 'discover'; const renderTutorialDirectory = props => { return ( diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 502c8f45646cf..aec3835dc075d 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -27,6 +27,7 @@ import { Environment, FeatureCatalogueEntry, HomePublicPluginStart, + HomePublicPluginSetup, } from '../../../../../plugins/home/public'; export interface LegacyAngularInjectedDependencies { @@ -59,6 +60,7 @@ export interface HomePluginSetupDependencies { }; usageCollection: UsageCollectionSetup; kibana_legacy: KibanaLegacySetup; + home: HomePublicPluginSetup; } export class HomePlugin implements Plugin { @@ -69,6 +71,7 @@ export class HomePlugin implements Plugin { setup( core: CoreSetup, { + home, kibana_legacy, usageCollection, __LEGACY: { getAngularDependencies, ...legacyServices }, @@ -95,6 +98,8 @@ export class HomePlugin implements Plugin { getBasePath: core.http.basePath.get, indexPatternService: this.dataStart!.indexPatterns, environment: this.environment!, + config: kibana_legacy.config, + homeConfig: home.config, ...angularDependencies, }); const { renderApp } = await import('./np_ready/application'); diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 50f1702a2a6d0..f2868da947a75 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -20,7 +20,6 @@ // autoloading // preloading (for faster webpack builds) -import chrome from 'ui/chrome'; import routes from 'ui/routes'; import { uiModules } from 'ui/modules'; import { npSetup } from 'ui/new_platform'; @@ -64,8 +63,9 @@ localApplicationService.attachToAngular(routes); routes.enable(); +const { config } = npSetup.plugins.kibana_legacy; routes.otherwise({ - redirectTo: `/${chrome.getInjected('kbnDefaultAppId', 'discover')}`, + redirectTo: `/${config.defaultAppId || 'discover'}`, }); uiModules.get('kibana').run(showAppRedirectNotification); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index f7fd19e8288e7..15e9c73a39eff 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -34,6 +34,7 @@ import { VisualizationsStart } from '../../../visualizations/public'; import { SavedVisualizations } from './np_ready/types'; import { UsageCollectionSetup } from '../../../../../plugins/usage_collection/public'; import { Chrome } from './legacy_imports'; +import { KibanaLegacyStart } from '../../../../../plugins/kibana_legacy/public'; export interface VisualizeKibanaServices { addBasePath: (url: string) => string; @@ -52,6 +53,7 @@ export interface VisualizeKibanaServices { savedVisualizations: SavedVisualizations; share: SharePluginStart; uiSettings: IUiSettingsClient; + config: KibanaLegacyStart['config']; visualizeCapabilities: any; visualizations: VisualizationsStart; usageCollection?: UsageCollectionSetup; diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js index d99771ccc912d..24055b9a2d9ed 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/legacy_app.js @@ -173,7 +173,7 @@ export function initVisualizeApp(app, deps) { }, }) .when(`visualize/:tail*?`, { - redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + redirectTo: `/${deps.config.defaultAppId}`, }); }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 26c6691a3613f..8e7487fee55f6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -108,6 +108,7 @@ export class VisualizePlugin implements Plugin { share, toastNotifications: contextCore.notifications.toasts, uiSettings: contextCore.uiSettings, + config: kibana_legacy.config, visualizeCapabilities: contextCore.application.capabilities.visualize, visualizations, usageCollection, diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts index c89ae9f8b3c9b..439ac9b5713df 100644 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts @@ -27,6 +27,7 @@ import { inspectorPluginMock } from '../../../../../plugins/inspector/public/moc import { uiActionsPluginMock } from '../../../../../plugins/ui_actions/public/mocks'; import { managementPluginMock } from '../../../../../plugins/management/public/mocks'; import { usageCollectionPluginMock } from '../../../../../plugins/usage_collection/public/mocks'; +import { kibanaLegacyPluginMock } from '../../../../../plugins/kibana_legacy/public/mocks'; import { chartPluginMock } from '../../../../../plugins/charts/public/mocks'; /* eslint-enable @kbn/eslint/no-restricted-paths */ @@ -40,6 +41,7 @@ export const pluginsMock = { expressions: expressionsPluginMock.createSetupContract(), uiActions: uiActionsPluginMock.createSetupContract(), usageCollection: usageCollectionPluginMock.createSetupContract(), + kibana_legacy: kibanaLegacyPluginMock.createSetupContract(), }), createStart: () => ({ data: dataPluginMock.createStartContract(), @@ -50,6 +52,7 @@ export const pluginsMock = { expressions: expressionsPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), management: managementPluginMock.createStartContract(), + kibana_legacy: kibanaLegacyPluginMock.createStartContract(), }), }; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index f98b8801d5266..c2c8b5a0fae7a 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -119,6 +119,9 @@ export const npSetup = { kibana_legacy: { registerLegacyApp: () => {}, forwardApp: () => {}, + config: { + defaultAppId: 'home', + }, }, inspector: { registerView: () => undefined, @@ -140,6 +143,9 @@ export const npSetup = { environment: { update: sinon.fake(), }, + config: { + disableWelcomeScreen: false, + }, }, charts: { theme: { @@ -196,6 +202,9 @@ export const npStart = { kibana_legacy: { getApps: () => [], getForwards: () => [], + config: { + defaultAppId: 'home', + }, }, data: { autocomplete: { @@ -297,6 +306,9 @@ export const npStart = { environment: { get: sinon.fake(), }, + config: { + disableWelcomeScreen: false, + }, }, navigation: { ui: { diff --git a/src/plugins/home/config.ts b/src/plugins/home/config.ts new file mode 100644 index 0000000000000..149723a7ee5ae --- /dev/null +++ b/src/plugins/home/config.ts @@ -0,0 +1,26 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + disableWelcomeScreen: schema.boolean({ defaultValue: false }), +}); + +export type ConfigSchema = TypeOf<typeof configSchema>; diff --git a/src/plugins/home/public/index.ts b/src/plugins/home/public/index.ts index ca05c8b5f760e..114d442b40943 100644 --- a/src/plugins/home/public/index.ts +++ b/src/plugins/home/public/index.ts @@ -17,6 +17,8 @@ * under the License. */ +import { PluginInitializerContext } from 'kibana/public'; + export { FeatureCatalogueSetup, FeatureCatalogueStart, @@ -26,4 +28,5 @@ export { export { FeatureCatalogueEntry, FeatureCatalogueCategory, Environment } from './services'; import { HomePublicPlugin } from './plugin'; -export const plugin = () => new HomePublicPlugin(); +export const plugin = (initializerContext: PluginInitializerContext) => + new HomePublicPlugin(initializerContext); diff --git a/src/plugins/home/public/plugin.test.ts b/src/plugins/home/public/plugin.test.ts index 34502d7d2c6cd..fa44a110c63b7 100644 --- a/src/plugins/home/public/plugin.test.ts +++ b/src/plugins/home/public/plugin.test.ts @@ -19,6 +19,9 @@ import { registryMock, environmentMock } from './plugin.test.mocks'; import { HomePublicPlugin } from './plugin'; +import { coreMock } from '../../../core/public/mocks'; + +const mockInitializerContext = coreMock.createPluginInitializerContext(); describe('HomePublicPlugin', () => { beforeEach(() => { @@ -30,13 +33,13 @@ describe('HomePublicPlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { - const setup = await new HomePublicPlugin().setup(); + const setup = await new HomePublicPlugin(mockInitializerContext).setup(); expect(setup).toHaveProperty('featureCatalogue'); expect(setup.featureCatalogue).toHaveProperty('register'); }); test('wires up and returns environment service', async () => { - const setup = await new HomePublicPlugin().setup(); + const setup = await new HomePublicPlugin(mockInitializerContext).setup(); expect(setup).toHaveProperty('environment'); expect(setup.environment).toHaveProperty('update'); }); @@ -44,7 +47,7 @@ describe('HomePublicPlugin', () => { describe('start', () => { test('wires up and returns registry', async () => { - const service = new HomePublicPlugin(); + const service = new HomePublicPlugin(mockInitializerContext); await service.setup(); const core = { application: { capabilities: { catalogue: {} } } } as any; const start = await service.start(core); @@ -55,7 +58,7 @@ describe('HomePublicPlugin', () => { }); test('wires up and returns environment service', async () => { - const service = new HomePublicPlugin(); + const service = new HomePublicPlugin(mockInitializerContext); await service.setup(); const start = await service.start({ application: { capabilities: { catalogue: {} } }, diff --git a/src/plugins/home/public/plugin.ts b/src/plugins/home/public/plugin.ts index 39a7f23826900..fe68dbc3e7e49 100644 --- a/src/plugins/home/public/plugin.ts +++ b/src/plugins/home/public/plugin.ts @@ -17,7 +17,8 @@ * under the License. */ -import { CoreStart, Plugin } from 'src/core/public'; +import { CoreStart, Plugin, PluginInitializerContext } from 'kibana/public'; + import { EnvironmentService, EnvironmentServiceSetup, @@ -26,19 +27,23 @@ import { FeatureCatalogueRegistrySetup, FeatureCatalogueRegistryStart, } from './services'; +import { ConfigSchema } from '../config'; export class HomePublicPlugin implements Plugin<HomePublicPluginSetup, HomePublicPluginStart> { private readonly featuresCatalogueRegistry = new FeatureCatalogueRegistry(); private readonly environmentService = new EnvironmentService(); - public async setup() { + constructor(private readonly initializerContext: PluginInitializerContext<ConfigSchema>) {} + + public setup(): HomePublicPluginSetup { return { featureCatalogue: { ...this.featuresCatalogueRegistry.setup() }, environment: { ...this.environmentService.setup() }, + config: this.initializerContext.config.get(), }; } - public async start(core: CoreStart) { + public start(core: CoreStart): HomePublicPluginStart { return { featureCatalogue: { ...this.featuresCatalogueRegistry.start({ @@ -71,6 +76,7 @@ export interface HomePublicPluginSetup { * @deprecated */ environment: EnvironmentSetup; + config: ConfigSchema; } /** @public */ diff --git a/src/plugins/home/server/index.ts b/src/plugins/home/server/index.ts index 0961c729698b9..02f4c91a414cc 100644 --- a/src/plugins/home/server/index.ts +++ b/src/plugins/home/server/index.ts @@ -20,8 +20,19 @@ export { HomeServerPluginSetup, HomeServerPluginStart } from './plugin'; export { TutorialProvider } from './services'; export { SampleDatasetProvider, SampleDataRegistrySetup } from './services'; -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext, PluginConfigDescriptor } from 'kibana/server'; import { HomeServerPlugin } from './plugin'; +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor<ConfigSchema> = { + exposeToBrowser: { + disableWelcomeScreen: true, + }, + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + renameFromRoot('kibana.disableWelcomeScreen', 'home.disableWelcomeScreen'), + ], +}; export const plugin = (initContext: PluginInitializerContext) => new HomeServerPlugin(initContext); diff --git a/src/plugins/home/server/plugin.ts b/src/plugins/home/server/plugin.ts index 23c236764cddc..d2f2d7041024e 100644 --- a/src/plugins/home/server/plugin.ts +++ b/src/plugins/home/server/plugin.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { CoreSetup, Plugin, PluginInitializerContext } from 'src/core/server'; +import { CoreSetup, Plugin, PluginInitializerContext } from 'kibana/server'; import { TutorialsRegistry, TutorialsRegistrySetup, diff --git a/src/plugins/kibana_legacy/config.ts b/src/plugins/kibana_legacy/config.ts new file mode 100644 index 0000000000000..291f8813ecfb9 --- /dev/null +++ b/src/plugins/kibana_legacy/config.ts @@ -0,0 +1,26 @@ +/* + * 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 { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + defaultAppId: schema.string({ defaultValue: 'home' }), +}); + +export type ConfigSchema = TypeOf<typeof configSchema>; diff --git a/src/plugins/kibana_legacy/kibana.json b/src/plugins/kibana_legacy/kibana.json index 26ee6db3ba06a..b6d11309a4f96 100644 --- a/src/plugins/kibana_legacy/kibana.json +++ b/src/plugins/kibana_legacy/kibana.json @@ -1,6 +1,6 @@ { "id": "kibana_legacy", "version": "kibana", - "server": false, + "server": true, "ui": true } diff --git a/src/plugins/kibana_legacy/public/index.ts b/src/plugins/kibana_legacy/public/index.ts index 4cb30be8917ac..de8788808e74c 100644 --- a/src/plugins/kibana_legacy/public/index.ts +++ b/src/plugins/kibana_legacy/public/index.ts @@ -20,8 +20,7 @@ import { PluginInitializerContext } from 'kibana/public'; import { KibanaLegacyPlugin } from './plugin'; -export function plugin(initializerContext: PluginInitializerContext) { - return new KibanaLegacyPlugin(); -} +export const plugin = (initializerContext: PluginInitializerContext) => + new KibanaLegacyPlugin(initializerContext); export * from './plugin'; diff --git a/src/plugins/kibana_legacy/public/mocks.ts b/src/plugins/kibana_legacy/public/mocks.ts new file mode 100644 index 0000000000000..b6287dd9d9a55 --- /dev/null +++ b/src/plugins/kibana_legacy/public/mocks.ts @@ -0,0 +1,44 @@ +/* + * 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 { KibanaLegacyPlugin } from './plugin'; + +export type Setup = jest.Mocked<ReturnType<KibanaLegacyPlugin['setup']>>; +export type Start = jest.Mocked<ReturnType<KibanaLegacyPlugin['start']>>; + +const createSetupContract = (): Setup => ({ + forwardApp: jest.fn(), + registerLegacyApp: jest.fn(), + config: { + defaultAppId: 'home', + }, +}); + +const createStartContract = (): Start => ({ + getApps: jest.fn(), + getForwards: jest.fn(), + config: { + defaultAppId: 'home', + }, +}); + +export const kibanaLegacyPluginMock = { + createSetupContract, + createStartContract, +}; diff --git a/src/plugins/kibana_legacy/public/plugin.ts b/src/plugins/kibana_legacy/public/plugin.ts index cb95088320d7b..b9a61a1c9b200 100644 --- a/src/plugins/kibana_legacy/public/plugin.ts +++ b/src/plugins/kibana_legacy/public/plugin.ts @@ -17,7 +17,9 @@ * under the License. */ -import { App } from 'kibana/public'; +import { App, PluginInitializerContext } from 'kibana/public'; + +import { ConfigSchema } from '../config'; interface ForwardDefinition { legacyAppId: string; @@ -29,6 +31,8 @@ export class KibanaLegacyPlugin { private apps: App[] = []; private forwards: ForwardDefinition[] = []; + constructor(private readonly initializerContext: PluginInitializerContext<ConfigSchema>) {} + public setup() { return { /** @@ -77,6 +81,8 @@ export class KibanaLegacyPlugin { ) => { this.forwards.push({ legacyAppId, newAppId, ...options }); }, + + config: this.initializerContext.config.get(), }; } @@ -92,6 +98,7 @@ export class KibanaLegacyPlugin { * Just exported for wiring up with legacy platform, should not be used. */ getForwards: () => this.forwards, + config: this.initializerContext.config.get(), }; } } diff --git a/src/plugins/kibana_legacy/server/index.ts b/src/plugins/kibana_legacy/server/index.ts new file mode 100644 index 0000000000000..4d0fe8364a66c --- /dev/null +++ b/src/plugins/kibana_legacy/server/index.ts @@ -0,0 +1,41 @@ +/* + * 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 { CoreSetup, CoreStart, PluginConfigDescriptor } from 'kibana/server'; + +import { configSchema, ConfigSchema } from '../config'; + +export const config: PluginConfigDescriptor<ConfigSchema> = { + exposeToBrowser: { + defaultAppId: true, + }, + schema: configSchema, + deprecations: ({ renameFromRoot }) => [ + // TODO: Remove deprecation once defaultAppId is deleted + renameFromRoot('kibana.defaultAppId', 'kibana_legacy.defaultAppId', true), + ], +}; + +class Plugin { + public setup(core: CoreSetup) {} + + public start(core: CoreStart) {} +} + +export const plugin = () => new Plugin(); diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts index 854406a10335b..b34e76474cec2 100644 --- a/src/plugins/management/public/management_service.test.ts +++ b/src/plugins/management/public/management_service.test.ts @@ -19,12 +19,13 @@ import { ManagementService } from './management_service'; import { coreMock } from '../../../core/public/mocks'; +import { npSetup } from '../../../legacy/ui/public/new_platform/__mocks__'; -const mockKibanaLegacy = { registerLegacyApp: () => {}, forwardApp: () => {} }; +jest.mock('ui/new_platform'); test('Provides default sections', () => { const service = new ManagementService().setup( - mockKibanaLegacy, + npSetup.plugins.kibana_legacy, () => {}, coreMock.createSetup().getStartServices ); @@ -36,7 +37,7 @@ test('Provides default sections', () => { test('Register section, enable and disable', () => { const service = new ManagementService().setup( - mockKibanaLegacy, + npSetup.plugins.kibana_legacy, () => {}, coreMock.createSetup().getStartServices ); diff --git a/test/common/config.js b/test/common/config.js index 29d4bbf10a6ce..faf8cef027170 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -54,7 +54,7 @@ export default function() { `--elasticsearch.hosts=${formatUrl(servers.elasticsearch)}`, `--elasticsearch.username=${kibanaServerTestUser.username}`, `--elasticsearch.password=${kibanaServerTestUser.password}`, - `--kibana.disableWelcomeScreen=true`, + `--home.disableWelcomeScreen=true`, '--telemetry.banner=false', `--server.maxPayloadBytes=1679958`, // newsfeed mock service diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index ebae49f994723..4215f96c8de4a 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -55,10 +55,12 @@ chrome.setRootController('kibana', function() { uiModules.get('kibana').run(showAppRedirectNotification); -// If there is a configured kbnDefaultAppId, and it is a dashboard ID, we'll -// show that dashboard, otherwise, we'll show the default dasbhoard landing page. +/** + * If there is a configured `kibana.defaultAppId`, and it is a dashboard ID, we'll + * show that dashboard, otherwise, we'll show the default dasbhoard landing page. + */ function defaultUrl() { - const defaultAppId = chrome.getInjected('kbnDefaultAppId', ''); + const defaultAppId = npStart.plugins.kibana_legacy.config.defaultAppId || ''; const isDashboardId = defaultAppId.startsWith(dashboardAppIdPrefix()); return isDashboardId ? `/${defaultAppId}` : DashboardConstants.LANDING_PAGE_PATH; } From 0440ae50f7ad0aec051710efde2d26d158a75ecc Mon Sep 17 00:00:00 2001 From: Tyler Smalley <tyler.smalley@elastic.co> Date: Mon, 3 Feb 2020 20:54:59 -0800 Subject: [PATCH 29/60] Updates Monitoring alert Jest snapshots A UI bump caused changes the EuiSuperSelect component which were not reflected in kibana#54306. The EUI change went in after the PR went green, but then failed once it hit master. Signed-off-by: Tyler Smalley <tyler.smalley@elastic.co> --- .../configuration/__snapshots__/configuration.test.tsx.snap | 1 + .../alerts/configuration/__snapshots__/step1.test.tsx.snap | 3 +++ 2 files changed, 4 insertions(+) diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap index f044e001700c5..429d19fbb887e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap @@ -7,6 +7,7 @@ exports[`Configuration shallow view should render step 1 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap index fa03769ea3d09..94d951a94fe29 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap @@ -42,6 +42,7 @@ exports[`Step1 should render normally 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ @@ -135,6 +136,7 @@ exports[`Step1 testing should show a failed test error 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ @@ -220,6 +222,7 @@ exports[`Step1 testing should show a successful test 1`] = ` fullWidth={false} hasDividers={true} isInvalid={false} + isLoading={false} onChange={[Function]} options={ Array [ From 4bb56c80b7175a40eed275ddbe2245a95eebb393 Mon Sep 17 00:00:00 2001 From: Pierre Gayvallet <pierre.gayvallet@elastic.co> Date: Tue, 4 Feb 2020 08:03:36 +0100 Subject: [PATCH 30/60] Add `getServerInfo` API to http setup contract (#56636) * add getServerInfo http setup api * update generated doc --- ...ibana-plugin-server.httpserverinfo.host.md | 13 +++++++ .../kibana-plugin-server.httpserverinfo.md | 22 ++++++++++++ ...ibana-plugin-server.httpserverinfo.name.md | 13 +++++++ ...ibana-plugin-server.httpserverinfo.port.md | 13 +++++++ ...a-plugin-server.httpserverinfo.protocol.md | 13 +++++++ ...n-server.httpservicesetup.getserverinfo.md | 13 +++++++ .../kibana-plugin-server.httpservicesetup.md | 1 + .../core/server/kibana-plugin-server.md | 1 + src/core/server/http/http_server.test.ts | 34 +++++++++++++++++++ src/core/server/http/http_server.ts | 9 ++++- src/core/server/http/http_service.mock.ts | 7 ++++ src/core/server/http/types.ts | 17 ++++++++++ src/core/server/index.ts | 1 + src/core/server/legacy/legacy_service.ts | 1 + src/core/server/mocks.ts | 1 + src/core/server/plugins/plugin_context.ts | 1 + src/core/server/server.api.md | 9 +++++ 17 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 docs/development/core/server/kibana-plugin-server.httpserverinfo.host.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpserverinfo.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpserverinfo.name.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpserverinfo.port.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpserverinfo.protocol.md create mode 100644 docs/development/core/server/kibana-plugin-server.httpservicesetup.getserverinfo.md diff --git a/docs/development/core/server/kibana-plugin-server.httpserverinfo.host.md b/docs/development/core/server/kibana-plugin-server.httpserverinfo.host.md new file mode 100644 index 0000000000000..ee7e1e5b7c9c9 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserverinfo.host.md @@ -0,0 +1,13 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) > [host](./kibana-plugin-server.httpserverinfo.host.md) + +## HttpServerInfo.host property + +The hostname of the server + +<b>Signature:</b> + +```typescript +host: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserverinfo.md b/docs/development/core/server/kibana-plugin-server.httpserverinfo.md new file mode 100644 index 0000000000000..6dbdb11ddb66e --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserverinfo.md @@ -0,0 +1,22 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) + +## HttpServerInfo interface + + +<b>Signature:</b> + +```typescript +export interface HttpServerInfo +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [host](./kibana-plugin-server.httpserverinfo.host.md) | <code>string</code> | The hostname of the server | +| [name](./kibana-plugin-server.httpserverinfo.name.md) | <code>string</code> | The name of the Kibana server | +| [port](./kibana-plugin-server.httpserverinfo.port.md) | <code>number</code> | The port the server is listening on | +| [protocol](./kibana-plugin-server.httpserverinfo.protocol.md) | <code>'http' | 'https' | 'socket'</code> | The protocol used by the server | + diff --git a/docs/development/core/server/kibana-plugin-server.httpserverinfo.name.md b/docs/development/core/server/kibana-plugin-server.httpserverinfo.name.md new file mode 100644 index 0000000000000..8d3a45c90a342 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserverinfo.name.md @@ -0,0 +1,13 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) > [name](./kibana-plugin-server.httpserverinfo.name.md) + +## HttpServerInfo.name property + +The name of the Kibana server + +<b>Signature:</b> + +```typescript +name: string; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserverinfo.port.md b/docs/development/core/server/kibana-plugin-server.httpserverinfo.port.md new file mode 100644 index 0000000000000..5dd5a53830c44 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserverinfo.port.md @@ -0,0 +1,13 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) > [port](./kibana-plugin-server.httpserverinfo.port.md) + +## HttpServerInfo.port property + +The port the server is listening on + +<b>Signature:</b> + +```typescript +port: number; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpserverinfo.protocol.md b/docs/development/core/server/kibana-plugin-server.httpserverinfo.protocol.md new file mode 100644 index 0000000000000..08afb5c3f7213 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpserverinfo.protocol.md @@ -0,0 +1,13 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) > [protocol](./kibana-plugin-server.httpserverinfo.protocol.md) + +## HttpServerInfo.protocol property + +The protocol used by the server + +<b>Signature:</b> + +```typescript +protocol: 'http' | 'https' | 'socket'; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.getserverinfo.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.getserverinfo.md new file mode 100644 index 0000000000000..4501a7e26f75f --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.getserverinfo.md @@ -0,0 +1,13 @@ +<!-- Do not edit this file. It is automatically generated by API Documenter. --> + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) > [getServerInfo](./kibana-plugin-server.httpservicesetup.getserverinfo.md) + +## HttpServiceSetup.getServerInfo property + +Provides common [information](./kibana-plugin-server.httpserverinfo.md) about the running http server. + +<b>Signature:</b> + +```typescript +getServerInfo: () => HttpServerInfo; +``` diff --git a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 2a4b0e09977c1..c2d53ec1eaf52 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -86,6 +86,7 @@ async (context, request, response) => { | [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <code><T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>></code> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | | [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | <code>() => IRouter</code> | Provides ability to declare a handler function for a particular path and HTTP request method. | | [csp](./kibana-plugin-server.httpservicesetup.csp.md) | <code>ICspConfig</code> | The CSP config used for Kibana. | +| [getServerInfo](./kibana-plugin-server.httpservicesetup.getserverinfo.md) | <code>() => HttpServerInfo</code> | Provides common [information](./kibana-plugin-server.httpserverinfo.md) about the running http server. | | [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | <code>boolean</code> | Flag showing whether a server was configured to use TLS connection. | | [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | <code>(handler: AuthenticationHandler) => void</code> | To define custom authentication and/or authorization mechanism for incoming requests. | | [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | <code>(handler: OnPostAuthHandler) => void</code> | To define custom logic to perform for incoming requests. | diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index e7b1334652540..a3abeff44c25c 100644 --- a/docs/development/core/server/kibana-plugin-server.md +++ b/docs/development/core/server/kibana-plugin-server.md @@ -64,6 +64,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- --> | [ErrorHttpResponseOptions](./kibana-plugin-server.errorhttpresponseoptions.md) | HTTP response parameters | | [FakeRequest](./kibana-plugin-server.fakerequest.md) | Fake request object created manually by Kibana plugins. | | [HttpResponseOptions](./kibana-plugin-server.httpresponseoptions.md) | HTTP response parameters | +| [HttpServerInfo](./kibana-plugin-server.httpserverinfo.md) | | | [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) | Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins don't have direct access to <code>hapi</code> server and its primitives anymore. Moreover, plugins shouldn't rely on the fact that HTTP Service uses one or another library under the hood. This gives the platform flexibility to upgrade or changing our internal HTTP stack without breaking plugins. If the HTTP Service lacks functionality you need, we are happy to discuss and support your needs. | | [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | | | [IContextContainer](./kibana-plugin-server.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index f8ef49b0f6d18..a9fc80c86d878 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -62,6 +62,7 @@ beforeAll(() => { beforeEach(() => { config = { + name: 'kibana', host: '127.0.0.1', maxPayload: new ByteSizeValue(1024), port: 10002, @@ -1077,4 +1078,37 @@ describe('setup contract', () => { expect(isTlsEnabled).toBe(false); }); }); + + describe('#getServerInfo', () => { + it('returns correct information', async () => { + let { getServerInfo } = await server.setup(config); + + expect(getServerInfo()).toEqual({ + host: '127.0.0.1', + name: 'kibana', + port: 10002, + protocol: 'http', + }); + + ({ getServerInfo } = await server.setup({ + ...config, + port: 12345, + name: 'custom-name', + host: 'localhost', + })); + + expect(getServerInfo()).toEqual({ + host: 'localhost', + name: 'custom-name', + port: 12345, + protocol: 'http', + }); + }); + + it('returns correct protocol when ssl is enabled', async () => { + const { getServerInfo } = await server.setup(configWithSSL); + + expect(getServerInfo().protocol).toEqual('https'); + }); + }); }); diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index fdc272041ce35..025ab2bf56ac2 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -35,7 +35,7 @@ import { import { IsAuthenticated, AuthStateStorage, GetAuthState } from './auth_state_storage'; import { AuthHeadersStorage, GetAuthHeaders } from './auth_headers_storage'; import { BasePath } from './base_path_service'; -import { HttpServiceSetup } from './types'; +import { HttpServiceSetup, HttpServerInfo } from './types'; /** @internal */ export interface HttpServerSetup { @@ -58,6 +58,7 @@ export interface HttpServerSetup { get: GetAuthState; isAuthenticated: IsAuthenticated; }; + getServerInfo: () => HttpServerInfo; } /** @internal */ @@ -122,6 +123,12 @@ export class HttpServer { isAuthenticated: this.authState.isAuthenticated, }, getAuthHeaders: this.authRequestHeaders.get, + getServerInfo: () => ({ + name: config.name, + host: config.host, + port: config.port, + protocol: this.server!.info.protocol, + }), isTlsEnabled: config.ssl.enabled, // Return server instance with the connection options so that we can properly // bridge core and the "legacy" Kibana internally. Once this bridge isn't diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 2b2d98d937e85..30032ff5da796 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -77,12 +77,19 @@ const createSetupContractMock = () => { auth: createAuthMock(), getAuthHeaders: jest.fn(), isTlsEnabled: false, + getServerInfo: jest.fn(), }; setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() ); setupContract.createRouter.mockImplementation(() => mockRouter.create()); setupContract.getAuthHeaders.mockReturnValue({ authorization: 'authorization-header' }); + setupContract.getServerInfo.mockReturnValue({ + host: 'localhost', + name: 'kibana', + port: 80, + protocol: 'http', + }); return setupContract; }; diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 01b852c26ec93..6327844108055 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -252,6 +252,11 @@ export interface HttpServiceSetup { contextName: T, provider: RequestHandlerContextProvider<T> ) => RequestHandlerContextContainer; + + /** + * Provides common {@link HttpServerInfo | information} about the running http server. + */ + getServerInfo: () => HttpServerInfo; } /** @internal */ @@ -273,3 +278,15 @@ export interface HttpServiceStart { /** Indicates if http server is listening on a given port */ isListening: (port: number) => boolean; } + +/** @public */ +export interface HttpServerInfo { + /** The name of the Kibana server */ + name: string; + /** The hostname of the server */ + host: string; + /** The port the server is listening on */ + port: number; + /** The protocol used by the server */ + protocol: 'http' | 'https' | 'socket'; +} diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 91f38c9f2ddbe..c45acd7f0129a 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -103,6 +103,7 @@ export { GetAuthState, HttpResponseOptions, HttpResponsePayload, + HttpServerInfo, HttpServiceSetup, HttpServiceStart, ErrorHttpResponseOptions, diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index d0e0453564f94..f9b18afadc938 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -292,6 +292,7 @@ export class LegacyService implements CoreService { }, csp: setupDeps.core.http.csp, isTlsEnabled: setupDeps.core.http.isTlsEnabled, + getServerInfo: setupDeps.core.http.getServerInfo, }, savedObjects: { setClientFactoryProvider: setupDeps.core.savedObjects.setClientFactoryProvider, diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 7d6f09b5232c0..97f836f8ef37d 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -105,6 +105,7 @@ function createCoreSetupMock() { get: httpService.auth.get, isAuthenticated: httpService.auth.isAuthenticated, }, + getServerInfo: httpService.getServerInfo, }; httpMock.createRouter.mockImplementation(() => httpService.createRouter('')); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 30e5209b2fc6a..77300900e84f3 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -164,6 +164,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>( auth: { get: deps.http.auth.get, isAuthenticated: deps.http.auth.isAuthenticated }, csp: deps.http.csp, isTlsEnabled: deps.http.isTlsEnabled, + getServerInfo: deps.http.getServerInfo, }, savedObjects: { setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index 20d9692391a69..fb27fcccc2abe 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -736,6 +736,14 @@ export interface HttpResponseOptions { // @public export type HttpResponsePayload = undefined | string | Record<string, any> | Buffer | Stream; +// @public (undocumented) +export interface HttpServerInfo { + host: string; + name: string; + port: number; + protocol: 'http' | 'https' | 'socket'; +} + // @public export interface HttpServiceSetup { // (undocumented) @@ -747,6 +755,7 @@ export interface HttpServiceSetup { createCookieSessionStorageFactory: <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>>; createRouter: () => IRouter; csp: ICspConfig; + getServerInfo: () => HttpServerInfo; isTlsEnabled: boolean; registerAuth: (handler: AuthenticationHandler) => void; registerOnPostAuth: (handler: OnPostAuthHandler) => void; From 79ecc408df62a796f6d1ceecb912e30d430c8d53 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@gmail.com> Date: Tue, 4 Feb 2020 09:18:52 +0100 Subject: [PATCH 31/60] Mention changed SAML ACS endpoint URL in breaking changes doc. (#56613) --- docs/migration/migrate_8_0.asciidoc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/migration/migrate_8_0.asciidoc b/docs/migration/migrate_8_0.asciidoc index 146d4e97b6cf4..a34f956ace263 100644 --- a/docs/migration/migrate_8_0.asciidoc +++ b/docs/migration/migrate_8_0.asciidoc @@ -74,6 +74,12 @@ specified explicitly. *Impact:* Define `xpack.security.authc.saml.realm` when using the SAML authentication provider instead. +[float] +==== `/api/security/v1/saml` endpoint is no longer supported +*Details:* The deprecated `/api/security/v1/saml` endpoint is no longer supported. + +*Impact:* Rely on `/api/security/saml/callback` endpoint when using SAML instead. This change should be reflected in Kibana `server.xsrf.whitelist` config as well as in Elasticsearch and Identity Provider SAML settings. + [float] === `optimize` directory is now in the `data` folder *Details:* Generated bundles have moved to the configured `path.data` folder. From 05edbdac78fae883fada5a5744fdf0f2d5402f41 Mon Sep 17 00:00:00 2001 From: patrykkopycinski <patryk.kopycinski@elastic.co> Date: Tue, 4 Feb 2020 11:22:40 +0100 Subject: [PATCH 32/60] [SIEM] Add eslint-plugin-react-perf (#55960) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .eslintrc.js | 14 ++++++++++++-- package.json | 1 + yarn.lock | 5 +++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 97a35d8b50e56..199f3743fd621 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -643,6 +643,18 @@ module.exports = { // '@typescript-eslint/unbound-method': 'error', }, }, + // { + // // will introduced after the other warns are fixed + // // typescript and javascript for front end react performance + // files: ['x-pack/legacy/plugins/siem/public/**/!(*.test).{js,ts,tsx}'], + // plugins: ['react-perf'], + // rules: { + // // 'react-perf/jsx-no-new-object-as-prop': 'error', + // // 'react-perf/jsx-no-new-array-as-prop': 'error', + // // 'react-perf/jsx-no-new-function-as-prop': 'error', + // // 'react/jsx-no-bind': 'error', + // }, + // }, { // typescript and javascript for front and back end files: ['x-pack/legacy/plugins/siem/**/*.{js,ts,tsx}'], @@ -747,8 +759,6 @@ module.exports = { // will introduced after the other warns are fixed // 'react/sort-comp': 'error', 'react/void-dom-elements-no-children': 'error', - // will introduced after the other warns are fixed - // 'react/jsx-no-bind': 'error', 'react/jsx-no-comment-textnodes': 'error', 'react/jsx-no-literals': 'error', 'react/jsx-no-target-blank': 'error', diff --git a/package.json b/package.json index ff6d32bfc39e5..f9a3bfd99b109 100644 --- a/package.json +++ b/package.json @@ -414,6 +414,7 @@ "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^2.3.0", + "eslint-plugin-react-perf": "^3.2.3", "exit-hook": "^2.2.0", "faker": "1.1.0", "fetch-mock": "^7.3.9", diff --git a/yarn.lock b/yarn.lock index 4b56ec6460775..caa5587a0bbd0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12591,6 +12591,11 @@ eslint-plugin-react-hooks@^2.3.0: resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz#53e073961f1f5ccf8dd19558036c1fac8c29d99a" integrity sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw== +eslint-plugin-react-perf@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-perf/-/eslint-plugin-react-perf-3.2.3.tgz#e28d42d3a1f7ec3c8976a94735d8e17e7d652a45" + integrity sha512-bMiPt7uywwS1Ly25n752NE3Ei0XBZ3igplTkZ8GPJKyZVVUd3cHgzILGeQW2HIeAkzQ9zwk9HM6EcYDipdFk3Q== + eslint-plugin-react@^7.17.0: version "7.17.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz#a31b3e134b76046abe3cd278e7482bd35a1d12d7" From a36ec324a69288c1f4df9204a5c07b0f86be11b9 Mon Sep 17 00:00:00 2001 From: Mikhail Shustov <restrry@gmail.com> Date: Tue, 4 Feb 2020 12:38:15 +0100 Subject: [PATCH 33/60] Start consuming np logging config (#56480) * pass config to the new platform * add default appenders * remove receiveAllAppenders flag it breaks legacy-appender compatibility with legacy flags: silent, quiet, verbose * add integration tests * use console mocks to simplify test setup * update tests * improve names * validate that default appender always presents on root level required during migration period to make sure logs are sent to the LP logging system * do not check condition in the loop * fix integration tests --- ...gacy_object_to_config_adapter.test.ts.snap | 2 + .../config/legacy_object_to_config_adapter.ts | 15 +- .../legacy/integration_tests/logging.test.ts | 239 ++++++++++++++++++ .../__snapshots__/logging_config.test.ts.snap | 2 + .../server/logging/appenders/appenders.ts | 6 - .../logging/integration_tests/logging.test.ts | 114 +++++++++ .../server/logging/integration_tests/utils.ts | 68 +++++ src/core/server/logging/logger.test.ts | 82 ------ src/core/server/logging/logger.ts | 8 +- .../server/logging/logging_config.test.ts | 22 +- src/core/server/logging/logging_config.ts | 39 ++- src/legacy/server/config/schema.js | 4 + 12 files changed, 495 insertions(+), 106 deletions(-) create mode 100644 src/core/server/legacy/integration_tests/logging.test.ts create mode 100644 src/core/server/logging/integration_tests/logging.test.ts create mode 100644 src/core/server/logging/integration_tests/utils.ts diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap index 74ecaa9f09c0e..3b16bed92df97 100644 --- a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap +++ b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap @@ -72,6 +72,7 @@ Object { }, }, }, + "loggers": undefined, "root": Object { "level": "off", }, @@ -90,6 +91,7 @@ Object { }, }, }, + "loggers": undefined, "root": Object { "level": "all", }, diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts index 30bb150e6c15a..3e496648c3af9 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts @@ -19,12 +19,13 @@ import { ConfigPath } from '../../config'; import { ObjectToConfigAdapter } from '../../config/object_to_config_adapter'; +import { LoggingConfigType } from '../../logging/logging_config'; import { LegacyVars } from '../types'; /** * Represents logging config supported by the legacy platform. */ -interface LegacyLoggingConfig { +export interface LegacyLoggingConfig { silent?: boolean; verbose?: boolean; quiet?: boolean; @@ -33,18 +34,24 @@ interface LegacyLoggingConfig { events?: Record<string, string>; } +type MixedLoggingConfig = LegacyLoggingConfig & Partial<LoggingConfigType>; + /** * Represents adapter between config provided by legacy platform and `Config` * supported by the current platform. * @internal */ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { - private static transformLogging(configValue: LegacyLoggingConfig = {}) { + private static transformLogging(configValue: MixedLoggingConfig = {}) { + const { appenders, root, loggers, ...legacyLoggingConfig } = configValue; + const loggingConfig = { appenders: { - default: { kind: 'legacy-appender', legacyLoggingConfig: configValue }, + ...appenders, + default: { kind: 'legacy-appender', legacyLoggingConfig }, }, - root: { level: 'info' }, + root: { level: 'info', ...root }, + loggers, }; if (configValue.silent) { diff --git a/src/core/server/legacy/integration_tests/logging.test.ts b/src/core/server/legacy/integration_tests/logging.test.ts new file mode 100644 index 0000000000000..66234f677903f --- /dev/null +++ b/src/core/server/legacy/integration_tests/logging.test.ts @@ -0,0 +1,239 @@ +/* + * 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 * as kbnTestServer from '../../../../test_utils/kbn_server'; + +import { + getPlatformLogsFromMock, + getLegacyPlatformLogsFromMock, +} from '../../logging/integration_tests/utils'; + +import { LegacyLoggingConfig } from '../config/legacy_object_to_config_adapter'; + +function createRoot(legacyLoggingConfig: LegacyLoggingConfig = {}) { + return kbnTestServer.createRoot({ + migrations: { skip: true }, // otherwise stuck in polling ES + logging: { + // legacy platform config + silent: false, + json: false, + ...legacyLoggingConfig, + events: { + log: ['test-file-legacy'], + }, + // platform config + appenders: { + 'test-console': { + kind: 'console', + layout: { + highlight: false, + kind: 'pattern', + }, + }, + }, + loggers: [ + { + context: 'test-file', + appenders: ['test-console'], + level: 'info', + }, + ], + }, + }); +} + +describe('logging service', () => { + let mockConsoleLog: jest.SpyInstance; + let mockStdout: jest.SpyInstance; + + beforeAll(async () => { + mockConsoleLog = jest.spyOn(global.console, 'log'); + mockStdout = jest.spyOn(global.process.stdout, 'write'); + }); + + afterAll(async () => { + mockConsoleLog.mockRestore(); + mockStdout.mockRestore(); + }); + + describe('compatibility', () => { + describe('uses configured loggers', () => { + let root: ReturnType<typeof createRoot>; + beforeAll(async () => { + root = createRoot(); + + await root.setup(); + await root.start(); + }, 30000); + + afterAll(async () => { + await root.shutdown(); + }); + + beforeEach(() => { + mockConsoleLog.mockClear(); + mockStdout.mockClear(); + }); + + it('when context matches', async () => { + root.logger.get('test-file').info('handled by NP'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + const loggedString = getPlatformLogsFromMock(mockConsoleLog); + expect(loggedString).toMatchInlineSnapshot(` + Array [ + "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] handled by NP", + ] + `); + }); + + it('falls back to the root legacy logger otherwise', async () => { + root.logger.get('test-file-legacy').info('handled by LP'); + + expect(mockStdout).toHaveBeenCalledTimes(1); + + const loggedString = getLegacyPlatformLogsFromMock(mockStdout); + expect(loggedString).toMatchInlineSnapshot(` + Array [ + " log [xx:xx:xx.xxx] [info][test-file-legacy] handled by LP + ", + ] + `); + }); + }); + + describe('logging config respects legacy logging settings', () => { + let root: ReturnType<typeof createRoot>; + + afterEach(async () => { + mockConsoleLog.mockClear(); + mockStdout.mockClear(); + await root.shutdown(); + }); + + it('"silent": true', async () => { + root = createRoot({ silent: true }); + + await root.setup(); + await root.start(); + + const platformLogger = root.logger.get('test-file'); + platformLogger.info('info'); + platformLogger.warn('warn'); + platformLogger.error('error'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(3); + + expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(` + Array [ + "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error", + ] + `); + + mockStdout.mockClear(); + + const legacyPlatformLogger = root.logger.get('test-file-legacy'); + legacyPlatformLogger.info('info'); + legacyPlatformLogger.warn('warn'); + legacyPlatformLogger.error('error'); + + expect(mockStdout).toHaveBeenCalledTimes(0); + }); + + it('"quiet": true', async () => { + root = createRoot({ quiet: true }); + + await root.setup(); + await root.start(); + + const platformLogger = root.logger.get('test-file'); + platformLogger.info('info'); + platformLogger.warn('warn'); + platformLogger.error('error'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(3); + + expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(` + Array [ + "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error", + ] + `); + + mockStdout.mockClear(); + + const legacyPlatformLogger = root.logger.get('test-file-legacy'); + legacyPlatformLogger.info('info'); + legacyPlatformLogger.warn('warn'); + legacyPlatformLogger.error('error'); + + expect(mockStdout).toHaveBeenCalledTimes(1); + expect(getLegacyPlatformLogsFromMock(mockStdout)).toMatchInlineSnapshot(` + Array [ + " log [xx:xx:xx.xxx] [error][test-file-legacy] error + ", + ] + `); + }); + + it('"verbose": true', async () => { + root = createRoot({ verbose: true }); + + await root.setup(); + await root.start(); + + const platformLogger = root.logger.get('test-file'); + platformLogger.info('info'); + platformLogger.warn('warn'); + platformLogger.error('error'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(3); + + expect(getPlatformLogsFromMock(mockConsoleLog)).toMatchInlineSnapshot(` + Array [ + "[xxxx-xx-xxTxx:xx:xx.xxxZ][INFO ][test-file] info", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][WARN ][test-file] warn", + "[xxxx-xx-xxTxx:xx:xx.xxxZ][ERROR][test-file] error", + ] + `); + + mockStdout.mockClear(); + + const legacyPlatformLogger = root.logger.get('test-file-legacy'); + legacyPlatformLogger.info('info'); + legacyPlatformLogger.warn('warn'); + legacyPlatformLogger.error('error'); + + expect(mockStdout).toHaveBeenCalledTimes(3); + expect(getLegacyPlatformLogsFromMock(mockStdout)).toMatchInlineSnapshot(` + Array [ + " log [xx:xx:xx.xxx] [info][test-file-legacy] info + ", + " log [xx:xx:xx.xxx] [warning][test-file-legacy] warn + ", + " log [xx:xx:xx.xxx] [error][test-file-legacy] error + ", + ] + `); + }); + }); + }); +}); diff --git a/src/core/server/logging/__snapshots__/logging_config.test.ts.snap b/src/core/server/logging/__snapshots__/logging_config.test.ts.snap index 10509b20e8942..fe1407563a635 100644 --- a/src/core/server/logging/__snapshots__/logging_config.test.ts.snap +++ b/src/core/server/logging/__snapshots__/logging_config.test.ts.snap @@ -13,6 +13,8 @@ Object { } `; +exports[`\`schema\` throws if \`root\` logger does not have "default" appender configured. 1`] = `"[root]: \\"default\\" appender required for migration period till the next major release"`; + exports[`\`schema\` throws if \`root\` logger does not have appenders configured. 1`] = `"[root.appenders]: array size is [0], but cannot be smaller than [1]"`; exports[`fails if loggers use unknown appenders. 1`] = `"Logger \\"some.nested.context\\" contains unsupported appender key \\"unknown\\"."`; diff --git a/src/core/server/logging/appenders/appenders.ts b/src/core/server/logging/appenders/appenders.ts index 871acb8c465ca..3aa86495e4d82 100644 --- a/src/core/server/logging/appenders/appenders.ts +++ b/src/core/server/logging/appenders/appenders.ts @@ -42,12 +42,6 @@ export type AppenderConfigType = TypeOf<typeof appendersSchema>; */ export interface Appender { append(record: LogRecord): void; - - /** - * Used to signal to `Logger` that log level filtering should be ignored for this appender. Defaults to `false`. - * @deprecated Should be removed once the `LegacyAppender` is removed. - */ - receiveAllLevels?: boolean; } /** diff --git a/src/core/server/logging/integration_tests/logging.test.ts b/src/core/server/logging/integration_tests/logging.test.ts new file mode 100644 index 0000000000000..7142f91300f12 --- /dev/null +++ b/src/core/server/logging/integration_tests/logging.test.ts @@ -0,0 +1,114 @@ +/* + * 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 * as kbnTestServer from '../../../../test_utils/kbn_server'; + +function createRoot() { + return kbnTestServer.createRoot({ + logging: { + silent: true, // set "true" in kbnTestServer + appenders: { + 'test-console': { + kind: 'console', + layout: { + highlight: false, + kind: 'pattern', + pattern: '{level}|{context}|{message}', + }, + }, + }, + loggers: [ + { + context: 'parent', + appenders: ['test-console'], + level: 'warn', + }, + { + context: 'parent.child', + appenders: ['test-console'], + level: 'error', + }, + ], + }, + }); +} + +describe('logging service', () => { + describe('logs according to context hierarchy', () => { + let root: ReturnType<typeof createRoot>; + let mockConsoleLog: jest.SpyInstance; + beforeAll(async () => { + mockConsoleLog = jest.spyOn(global.console, 'log'); + root = createRoot(); + + await root.setup(); + }, 30000); + + beforeEach(() => { + mockConsoleLog.mockClear(); + }); + + afterAll(async () => { + mockConsoleLog.mockRestore(); + await root.shutdown(); + }); + + it('uses the most specific context', () => { + const logger = root.logger.get('parent.child'); + + logger.error('error from "parent.child" context'); + logger.warn('warning from "parent.child" context'); + logger.info('info from "parent.child" context'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(1); + expect(mockConsoleLog).toHaveBeenCalledWith( + 'ERROR|parent.child|error from "parent.child" context' + ); + }); + + it('uses parent context', () => { + const logger = root.logger.get('parent.another-child'); + + logger.error('error from "parent.another-child" context'); + logger.warn('warning from "parent.another-child" context'); + logger.info('info from "parent.another-child" context'); + + expect(mockConsoleLog).toHaveBeenCalledTimes(2); + expect(mockConsoleLog).toHaveBeenNthCalledWith( + 1, + 'ERROR|parent.another-child|error from "parent.another-child" context' + ); + expect(mockConsoleLog).toHaveBeenNthCalledWith( + 2, + 'WARN |parent.another-child|warning from "parent.another-child" context' + ); + }); + + it('falls back to the root settings', () => { + const logger = root.logger.get('fallback'); + + logger.error('error from "fallback" context'); + logger.warn('warning from fallback" context'); + logger.info('info from "fallback" context'); + + // output muted by silent: true + expect(mockConsoleLog).toHaveBeenCalledTimes(0); + }); + }); +}); diff --git a/src/core/server/logging/integration_tests/utils.ts b/src/core/server/logging/integration_tests/utils.ts new file mode 100644 index 0000000000000..81a76ce76ad73 --- /dev/null +++ b/src/core/server/logging/integration_tests/utils.ts @@ -0,0 +1,68 @@ +/* + * 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 Fs from 'fs'; +import Util from 'util'; +const readFile = Util.promisify(Fs.readFile); + +function replaceAllNumbers(input: string) { + return input.replace(/\d/g, 'x'); +} + +function replaceTimestamp(input: string) { + return input.replace(/\[(.*?)\]/, (full, key) => `[${replaceAllNumbers(key)}]`); +} + +function stripColors(input: string) { + return input.replace(/\u001b[^m]+m/g, ''); +} + +function normalizePlatformLogging(input: string) { + return replaceTimestamp(input); +} + +function normalizeLegacyPlatformLogging(input: string) { + return replaceTimestamp(stripColors(input)); +} + +export function getPlatformLogsFromMock(logMock: jest.SpyInstance<string, string[]>) { + return logMock.mock.calls.map(([message]) => message).map(normalizePlatformLogging); +} + +export function getLegacyPlatformLogsFromMock(stdoutMock: jest.SpyInstance<string, Buffer[]>) { + return stdoutMock.mock.calls + .map(([message]) => message) + .map(String) + .map(normalizeLegacyPlatformLogging); +} + +export async function getPlatformLogsFromFile(path: string) { + const fileContent = await readFile(path, 'utf-8'); + return fileContent + .split('\n') + .map(s => normalizePlatformLogging(s)) + .join('\n'); +} + +export async function getLegacyPlatformLogsFromFile(path: string) { + const fileContent = await readFile(path, 'utf-8'); + return fileContent + .split('\n') + .map(s => normalizeLegacyPlatformLogging(s)) + .join('\n'); +} diff --git a/src/core/server/logging/logger.test.ts b/src/core/server/logging/logger.test.ts index eeebb8ad5a0fa..026e24fc5df54 100644 --- a/src/core/server/logging/logger.test.ts +++ b/src/core/server/logging/logger.test.ts @@ -410,85 +410,3 @@ test('passes log record to appenders only if log level is supported.', () => { }); } }); - -test('passes log record to appender with receiveAllLevels: true, regardless if log level is supported', () => { - const receiveAllAppender = { append: jest.fn(), receiveAllLevels: true }; - const warnLogger = new BaseLogger(context, LogLevel.Warn, [receiveAllAppender], factory); - - warnLogger.trace('trace-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(1); - expect(receiveAllAppender.append.mock.calls[0][0]).toMatchObject({ - level: LogLevel.Trace, - message: 'trace-message', - }); - - warnLogger.debug('debug-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(2); - expect(receiveAllAppender.append.mock.calls[1][0]).toMatchObject({ - level: LogLevel.Debug, - message: 'debug-message', - }); - - warnLogger.info('info-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(3); - expect(receiveAllAppender.append.mock.calls[2][0]).toMatchObject({ - level: LogLevel.Info, - message: 'info-message', - }); - - warnLogger.warn('warn-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(4); - expect(receiveAllAppender.append.mock.calls[3][0]).toMatchObject({ - level: LogLevel.Warn, - message: 'warn-message', - }); - - warnLogger.error('error-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(5); - expect(receiveAllAppender.append.mock.calls[4][0]).toMatchObject({ - level: LogLevel.Error, - message: 'error-message', - }); - - warnLogger.fatal('fatal-message'); - expect(receiveAllAppender.append).toHaveBeenCalledTimes(6); - expect(receiveAllAppender.append.mock.calls[5][0]).toMatchObject({ - level: LogLevel.Fatal, - message: 'fatal-message', - }); -}); - -test('passes log record to appender with receiveAllLevels: false, only if log level is supported', () => { - const notReceiveAllAppender = { append: jest.fn(), receiveAllLevels: false }; - const warnLogger = new BaseLogger(context, LogLevel.Warn, [notReceiveAllAppender], factory); - - warnLogger.trace('trace-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(0); - - warnLogger.debug('debug-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(0); - - warnLogger.info('info-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(0); - - warnLogger.warn('warn-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(1); - expect(notReceiveAllAppender.append.mock.calls[0][0]).toMatchObject({ - level: LogLevel.Warn, - message: 'warn-message', - }); - - warnLogger.error('error-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(2); - expect(notReceiveAllAppender.append.mock.calls[1][0]).toMatchObject({ - level: LogLevel.Error, - message: 'error-message', - }); - - warnLogger.fatal('fatal-message'); - expect(notReceiveAllAppender.append).toHaveBeenCalledTimes(3); - expect(notReceiveAllAppender.append.mock.calls[2][0]).toMatchObject({ - level: LogLevel.Fatal, - message: 'fatal-message', - }); -}); diff --git a/src/core/server/logging/logger.ts b/src/core/server/logging/logger.ts index ab6906ff5d684..ac79c1916c07b 100644 --- a/src/core/server/logging/logger.ts +++ b/src/core/server/logging/logger.ts @@ -136,12 +136,12 @@ export class BaseLogger implements Logger { } public log(record: LogRecord) { - const supportedLevel = this.level.supports(record.level); + if (!this.level.supports(record.level)) { + return; + } for (const appender of this.appenders) { - if (supportedLevel || appender.receiveAllLevels) { - appender.append(record); - } + appender.append(record); } } diff --git a/src/core/server/logging/logging_config.test.ts b/src/core/server/logging/logging_config.test.ts index 8eb79ac46e499..b3631abb9ff00 100644 --- a/src/core/server/logging/logging_config.test.ts +++ b/src/core/server/logging/logging_config.test.ts @@ -33,6 +33,16 @@ test('`schema` throws if `root` logger does not have appenders configured.', () ).toThrowErrorMatchingSnapshot(); }); +test('`schema` throws if `root` logger does not have "default" appender configured.', () => { + expect(() => + config.schema.validate({ + root: { + appenders: ['console'], + }, + }) + ).toThrowErrorMatchingSnapshot(); +}); + test('`getParentLoggerContext()` returns correct parent context name.', () => { expect(LoggingConfig.getParentLoggerContext('a.b.c')).toEqual('a.b'); expect(LoggingConfig.getParentLoggerContext('a.b')).toEqual('a'); @@ -46,15 +56,23 @@ test('`getLoggerContext()` returns correct joined context name.', () => { expect(LoggingConfig.getLoggerContext([])).toEqual('root'); }); -test('correctly fills in default `appenders` config.', () => { +test('correctly fills in default config.', () => { const configValue = new LoggingConfig(config.schema.validate({})); - expect(configValue.appenders.size).toBe(1); + expect(configValue.appenders.size).toBe(3); expect(configValue.appenders.get('default')).toEqual({ kind: 'console', layout: { kind: 'pattern', highlight: true }, }); + expect(configValue.appenders.get('console')).toEqual({ + kind: 'console', + layout: { kind: 'pattern', highlight: true }, + }); + expect(configValue.appenders.get('file')).toEqual({ + kind: 'file', + layout: { kind: 'pattern', highlight: false }, + }); }); test('correctly fills in custom `appenders` config.', () => { diff --git a/src/core/server/logging/logging_config.ts b/src/core/server/logging/logging_config.ts index 84d707a3247e6..f1fbf787737b4 100644 --- a/src/core/server/logging/logging_config.ts +++ b/src/core/server/logging/logging_config.ts @@ -72,13 +72,22 @@ export const config = { loggers: schema.arrayOf(createLoggerSchema, { defaultValue: [], }), - root: schema.object({ - appenders: schema.arrayOf(schema.string(), { - defaultValue: [DEFAULT_APPENDER_NAME], - minSize: 1, - }), - level: createLevelSchema, - }), + root: schema.object( + { + appenders: schema.arrayOf(schema.string(), { + defaultValue: [DEFAULT_APPENDER_NAME], + minSize: 1, + }), + level: createLevelSchema, + }, + { + validate(rawConfig) { + if (!rawConfig.appenders.includes(DEFAULT_APPENDER_NAME)) { + return `"${DEFAULT_APPENDER_NAME}" appender required for migration period till the next major release`; + } + }, + } + ), }), }; @@ -118,12 +127,26 @@ export class LoggingConfig { */ public readonly appenders: Map<string, AppenderConfigType> = new Map([ [ - DEFAULT_APPENDER_NAME, + 'default', + { + kind: 'console', + layout: { kind: 'pattern', highlight: true }, + } as AppenderConfigType, + ], + [ + 'console', { kind: 'console', layout: { kind: 'pattern', highlight: true }, } as AppenderConfigType, ], + [ + 'file', + { + kind: 'file', + layout: { kind: 'pattern', highlight: false }, + } as AppenderConfigType, + ], ]); /** diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index f2a14df1d1eb3..a24ffcbaaa49f 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -103,6 +103,10 @@ export default () => logging: Joi.object() .keys({ + appenders: HANDLED_IN_NEW_PLATFORM, + loggers: HANDLED_IN_NEW_PLATFORM, + root: HANDLED_IN_NEW_PLATFORM, + silent: Joi.boolean().default(false), quiet: Joi.boolean().when('silent', { From 55e0e8d746ba360f5f2282187a9f2479badded6c Mon Sep 17 00:00:00 2001 From: patrykkopycinski <patryk.kopycinski@elastic.co> Date: Tue, 4 Feb 2020 13:00:08 +0100 Subject: [PATCH 34/60] [SIEM] Enable flow_target_select_connected unit tests (#55618) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../index.test.tsx | 22 ++++++++++++++----- .../flow_target_select_connected/index.tsx | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx index 006587d8fc294..e71be5a51e505 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx @@ -6,27 +6,37 @@ import { mount } from 'enzyme'; import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { TestProviders } from '../../../../mock'; -import { FlowTargetSelectConnected } from './index'; +import { FlowTargetSelectConnectedComponent } from './index'; import { FlowTarget } from '../../../../graphql/types'; -describe.skip('Flow Target Select Connected', () => { +describe('Flow Target Select Connected', () => { test('renders correctly against snapshot flowTarget source', () => { const wrapper = mount( <TestProviders> - <FlowTargetSelectConnected flowTarget={FlowTarget.source} /> + <MemoryRouter> + <FlowTargetSelectConnectedComponent flowTarget={FlowTarget.source} /> + </MemoryRouter> </TestProviders> ); - expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot(); + expect(wrapper.find('Memo(FlowTargetSelectComponent)').prop('selectedTarget')).toEqual( + FlowTarget.source + ); }); test('renders correctly against snapshot flowTarget destination', () => { const wrapper = mount( <TestProviders> - <FlowTargetSelectConnected flowTarget={FlowTarget.destination} /> + <MemoryRouter> + <FlowTargetSelectConnectedComponent flowTarget={FlowTarget.destination} /> + </MemoryRouter> </TestProviders> ); - expect(wrapper.find('FlowTargetSelectConnected')).toMatchSnapshot(); + + expect(wrapper.find('Memo(FlowTargetSelectComponent)').prop('selectedTarget')).toEqual( + FlowTarget.destination + ); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx index 1b87c36902159..2651c31e0a2c9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.tsx @@ -36,7 +36,7 @@ const getUpdatedFlowTargetPath = ( return `${newPathame}${location.search}`; }; -const FlowTargetSelectConnectedComponent: React.FC<Props> = ({ flowTarget }) => { +export const FlowTargetSelectConnectedComponent: React.FC<Props> = ({ flowTarget }) => { const history = useHistory(); const location = useLocation(); From 661bb6b438138889f45270517cc8779fb20f2cab Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm <matthias.wilhelm@elastic.co> Date: Tue, 4 Feb 2020 13:08:52 +0100 Subject: [PATCH 35/60] [Discover] Migrate get_sort.js test from mocha to TypeScript (#56011) * Migrate get_sort.js test from mocha to jest and convert to TypeScript * Add jest test --- .../__tests__/doc_table/lib/get_sort.js | 104 ------------------ .../discover/np_ready/angular/discover.js | 11 +- .../angular/doc_table/lib/get_sort.d.ts | 27 ----- .../angular/doc_table/lib/get_sort.js | 62 ----------- .../angular/doc_table/lib/get_sort.test.ts | 91 +++++++++++++++ .../angular/doc_table/lib/get_sort.ts | 71 ++++++++++++ .../lib/get_sort_for_search_source.ts | 10 +- .../data/public/search/search_source/types.ts | 7 +- 8 files changed, 177 insertions(+), 206 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js delete mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts delete mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.test.ts create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.ts diff --git a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js deleted file mode 100644 index d5485bca33cf5..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/__tests__/doc_table/lib/get_sort.js +++ /dev/null @@ -1,104 +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 expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -import { getSort } from '../../../np_ready/angular/doc_table/lib/get_sort'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; - -let indexPattern; - -describe('docTable', function() { - beforeEach(ngMock.module('kibana')); - - beforeEach( - ngMock.inject(function(Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - }) - ); - - describe('getSort function', function() { - it('should be a function', function() { - expect(getSort).to.be.a(Function); - }); - - it('should return an array of objects', function() { - expect(getSort([['bytes', 'desc']], indexPattern)).to.eql([{ bytes: 'desc' }]); - - delete indexPattern.timeFieldName; - expect(getSort([['bytes', 'desc']], indexPattern)).to.eql([{ bytes: 'desc' }]); - }); - - it('should passthrough arrays of objects', () => { - expect(getSort([{ bytes: 'desc' }], indexPattern)).to.eql([{ bytes: 'desc' }]); - }); - - it('should return an empty array when passed an unsortable field', function() { - expect(getSort(['non-sortable', 'asc'], indexPattern)).to.eql([]); - expect(getSort(['lol_nope', 'asc'], indexPattern)).to.eql([]); - - delete indexPattern.timeFieldName; - expect(getSort(['non-sortable', 'asc'], indexPattern)).to.eql([]); - }); - - it('should return an empty array ', function() { - expect(getSort([], indexPattern)).to.eql([]); - expect(getSort(['foo'], indexPattern)).to.eql([]); - expect(getSort({ foo: 'bar' }, indexPattern)).to.eql([]); - }); - - it('should return an empty array on non-time patterns', function() { - delete indexPattern.timeFieldName; - - expect(getSort([], indexPattern)).to.eql([]); - expect(getSort(['foo'], indexPattern)).to.eql([]); - expect(getSort({ foo: 'bar' }, indexPattern)).to.eql([]); - }); - }); - - describe('getSort.array function', function() { - it('should have an array method', function() { - expect(getSort.array).to.be.a(Function); - }); - - it('should return an array of arrays for sortable fields', function() { - expect(getSort.array([['bytes', 'desc']], indexPattern)).to.eql([['bytes', 'desc']]); - }); - - it('should return an array of arrays from an array of elasticsearch sort objects', function() { - expect(getSort.array([{ bytes: 'desc' }], indexPattern)).to.eql([['bytes', 'desc']]); - }); - - it('should sort by an empty array when an unsortable field is given', function() { - expect(getSort.array([{ 'non-sortable': 'asc' }], indexPattern)).to.eql([]); - expect(getSort.array([{ lol_nope: 'asc' }], indexPattern)).to.eql([]); - - delete indexPattern.timeFieldName; - expect(getSort.array([{ 'non-sortable': 'asc' }], indexPattern)).to.eql([]); - }); - - it('should return an empty array when passed an empty sort array', () => { - expect(getSort.array([], indexPattern)).to.eql([]); - - delete indexPattern.timeFieldName; - expect(getSort.array([], indexPattern)).to.eql([]); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js index 5e99cab1b3297..2f73af2ab77e4 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/discover.js @@ -28,7 +28,7 @@ import '../components/field_chooser/field_chooser'; import { RequestAdapter } from '../../../../../../../plugins/inspector/public'; // doc table import './doc_table'; -import { getSort } from './doc_table/lib/get_sort'; +import { getSortArray } from './doc_table/lib/get_sort'; import { getSortForSearchSource } from './doc_table/lib/get_sort_for_search_source'; import * as columnActions from './doc_table/actions/columns'; @@ -525,7 +525,7 @@ function discoverController( language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage'), }, - sort: getSort.array(savedSearch.sort, $scope.indexPattern), + sort: getSortArray(savedSearch.sort, $scope.indexPattern), columns: savedSearch.columns.length > 0 ? savedSearch.columns : config.get('defaultColumns').slice(), index: $scope.indexPattern.id, @@ -537,7 +537,7 @@ function discoverController( } $state.index = $scope.indexPattern.id; - $state.sort = getSort.array($state.sort, $scope.indexPattern); + $state.sort = getSortArray($state.sort, $scope.indexPattern); $scope.getBucketIntervalToolTipText = () => { return i18n.translate('kbn.discover.bucketIntervalTooltip', { @@ -619,10 +619,7 @@ function discoverController( if (!sort) return; // get the current sort from searchSource as array of arrays - const currentSort = getSort.array( - $scope.searchSource.getField('sort'), - $scope.indexPattern - ); + const currentSort = getSortArray($scope.searchSource.getField('sort'), $scope.indexPattern); // if the searchSource doesn't know, tell it so if (!angular.equals(sort, currentSort)) $scope.fetch(); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts deleted file mode 100644 index 0bf8a93a88367..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.d.ts +++ /dev/null @@ -1,27 +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 { IIndexPattern } from '../../../../../../../../../plugins/data/public'; -import { SortOrder } from '../components/table_header/helpers'; - -export function getSort( - sort?: SortOrder[], - indexPattern?: IIndexPattern, - defaultSortOrder?: SortOrder -): any; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js deleted file mode 100644 index ce32fdaeda237..0000000000000 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.js +++ /dev/null @@ -1,62 +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 _ from 'lodash'; - -export function isSortable(field, indexPattern) { - return indexPattern.fields.getByName(field) && indexPattern.fields.getByName(field).sortable; -} - -function createSortObject(sortPair, indexPattern) { - if (Array.isArray(sortPair) && sortPair.length === 2 && isSortable(sortPair[0], indexPattern)) { - const [field, direction] = sortPair; - return { [field]: direction }; - } else if (_.isPlainObject(sortPair) && isSortable(Object.keys(sortPair)[0], indexPattern)) { - return sortPair; - } else { - return undefined; - } -} - -/** - * Take a sorting array and make it into an object - * @param {array} sort two dimensional array [[fieldToSort, directionToSort]] - * or an array of objects [{fieldToSort: directionToSort}] - * @param {object} indexPattern used for determining default sort - * @returns {object} a sort object suitable for returning to elasticsearch - */ -export function getSort(sort, indexPattern) { - let sortObjects; - if (Array.isArray(sort)) { - sortObjects = _.compact(sort.map(sortPair => createSortObject(sortPair, indexPattern))); - } - - if (!_.isEmpty(sortObjects)) { - return sortObjects; - } - return []; -} - -getSort.array = function(sort, indexPattern) { - return getSort(sort, indexPattern).map(sortPair => - _(sortPair) - .pairs() - .pop() - ); -}; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.test.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.test.ts new file mode 100644 index 0000000000000..c9cbad245f5e4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.test.ts @@ -0,0 +1,91 @@ +/* + * 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 { getSort, getSortArray } from './get_sort'; +// @ts-ignore +import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import { IndexPattern } from '../../../../kibana_services'; + +describe('docTable', function() { + let indexPattern: IndexPattern; + + beforeEach(() => { + indexPattern = FixturesStubbedLogstashIndexPatternProvider() as IndexPattern; + }); + + describe('getSort function', function() { + test('should be a function', function() { + expect(typeof getSort === 'function').toBeTruthy(); + }); + + test('should return an array of objects', function() { + expect(getSort([['bytes', 'desc']], indexPattern)).toEqual([{ bytes: 'desc' }]); + + delete indexPattern.timeFieldName; + expect(getSort([['bytes', 'desc']], indexPattern)).toEqual([{ bytes: 'desc' }]); + }); + + test('should passthrough arrays of objects', () => { + expect(getSort([{ bytes: 'desc' }], indexPattern)).toEqual([{ bytes: 'desc' }]); + }); + + test('should return an empty array when passed an unsortable field', function() { + expect(getSort([['non-sortable', 'asc']], indexPattern)).toEqual([]); + expect(getSort([['lol_nope', 'asc']], indexPattern)).toEqual([]); + + delete indexPattern.timeFieldName; + expect(getSort([['non-sortable', 'asc']], indexPattern)).toEqual([]); + }); + + test('should return an empty array ', function() { + expect(getSort([], indexPattern)).toEqual([]); + expect(getSort([['foo', 'bar']], indexPattern)).toEqual([]); + expect(getSort([{ foo: 'bar' }], indexPattern)).toEqual([]); + }); + }); + + describe('getSortArray function', function() { + test('should have an array method', function() { + expect(getSortArray).toBeInstanceOf(Function); + }); + + test('should return an array of arrays for sortable fields', function() { + expect(getSortArray([['bytes', 'desc']], indexPattern)).toEqual([['bytes', 'desc']]); + }); + + test('should return an array of arrays from an array of elasticsearch sort objects', function() { + expect(getSortArray([{ bytes: 'desc' }], indexPattern)).toEqual([['bytes', 'desc']]); + }); + + test('should sort by an empty array when an unsortable field is given', function() { + expect(getSortArray([{ 'non-sortable': 'asc' }], indexPattern)).toEqual([]); + expect(getSortArray([{ lol_nope: 'asc' }], indexPattern)).toEqual([]); + + delete indexPattern.timeFieldName; + expect(getSortArray([{ 'non-sortable': 'asc' }], indexPattern)).toEqual([]); + }); + + test('should return an empty array when passed an empty sort array', () => { + expect(getSortArray([], indexPattern)).toEqual([]); + + delete indexPattern.timeFieldName; + expect(getSortArray([], indexPattern)).toEqual([]); + }); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.ts new file mode 100644 index 0000000000000..a8dbaa50e5aa8 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort.ts @@ -0,0 +1,71 @@ +/* + * 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 _ from 'lodash'; +import { IndexPattern } from '../../../../../../../../../plugins/data/public'; + +export type SortPairObj = Record<string, string>; +export type SortPairArr = [string, string]; +export type SortPair = SortPairArr | SortPairObj; +export type SortInput = SortPair | SortPair[]; + +export function isSortable(fieldName: string, indexPattern: IndexPattern) { + const field = indexPattern.getFieldByName(fieldName); + return field && field.sortable; +} + +function createSortObject( + sortPair: SortInput, + indexPattern: IndexPattern +): SortPairObj | undefined { + if ( + Array.isArray(sortPair) && + sortPair.length === 2 && + isSortable(String(sortPair[0]), indexPattern) + ) { + const [field, direction] = sortPair as SortPairArr; + return { [field]: direction }; + } else if (_.isPlainObject(sortPair) && isSortable(Object.keys(sortPair)[0], indexPattern)) { + return sortPair as SortPairObj; + } +} + +/** + * Take a sorting array and make it into an object + * @param {array} sort two dimensional array [[fieldToSort, directionToSort]] + * or an array of objects [{fieldToSort: directionToSort}] + * @param {object} indexPattern used for determining default sort + * @returns Array<{object}> an array of sort objects + */ +export function getSort(sort: SortPair[], indexPattern: IndexPattern): SortPairObj[] { + if (Array.isArray(sort)) { + return sort + .map((sortPair: SortPair) => createSortObject(sortPair, indexPattern)) + .filter(sortPairObj => typeof sortPairObj === 'object') as SortPairObj[]; + } + return []; +} + +/** + * compared to getSort it doesn't return an array of objects, it returns an array of arrays + * [[fieldToSort: directionToSort]] + */ +export function getSortArray(sort: SortPair[], indexPattern: IndexPattern) { + return getSort(sort, indexPattern).map(sortPair => Object.entries(sortPair).pop()); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts index 62a44d30adfd5..6721f7a03584c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/lib/get_sort_for_search_source.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { IndexPattern } from '../../../../kibana_services'; +import { EsQuerySortValue, IndexPattern } from '../../../../kibana_services'; import { SortOrder } from '../components/table_header/helpers'; import { getSort } from './get_sort'; import { getDefaultSort } from './get_default_sort'; @@ -31,8 +31,8 @@ import { getDefaultSort } from './get_default_sort'; export function getSortForSearchSource( sort?: SortOrder[], indexPattern?: IndexPattern, - defaultDirection: 'asc' | 'desc' = 'desc' -) { + defaultDirection: string = 'desc' +): EsQuerySortValue[] { if (!sort || !indexPattern) { return []; } else if (Array.isArray(sort) && sort.length === 0) { @@ -46,8 +46,8 @@ export function getSortForSearchSource( order: sortPair[timeFieldName], numeric_type: 'date_nanos', }, - }; + } as EsQuerySortValue; } - return sortPair; + return sortPair as EsQuerySortValue; }); } diff --git a/src/plugins/data/public/search/search_source/types.ts b/src/plugins/data/public/search/search_source/types.ts index 17337c905db87..268d24aaa6df1 100644 --- a/src/plugins/data/public/search/search_source/types.ts +++ b/src/plugins/data/public/search/search_source/types.ts @@ -26,7 +26,12 @@ export enum SortDirection { desc = 'desc', } -export type EsQuerySortValue = Record<string, SortDirection>; +export interface SortDirectionNumeric { + order: SortDirection; + numeric_type?: 'double' | 'long' | 'date' | 'date_nanos'; +} + +export type EsQuerySortValue = Record<string, SortDirection | SortDirectionNumeric>; export interface SearchSourceFields { type?: string; From d73e15f4147d1de9a7ee15517cbf6a90f4d4b213 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm <matthias.wilhelm@elastic.co> Date: Tue, 4 Feb 2020 14:16:41 +0100 Subject: [PATCH 36/60] [Discover] Inline angular directives only used in this plugin (#56119) * Migrate field_name directive * Migrate collapsible_sidebar directive * Fix FieldName import at table_row.tsx * Migrate css_truncate directive * Migrate fixed_scroll & debounce directives * Migrate render_complete directive * Fix css_truncate test * Use shortenDottedString in the TypesScript version --- .../__tests__/directives}/css_truncate.js | 6 +++-- .../__tests__/directives}/fixed_scroll.js | 7 +++--- .../public/discover/get_inner_angular.ts | 24 ++++++++----------- .../kibana/public/discover/kibana_services.ts | 5 ---- .../np_ready/angular/directives/_index.scss | 3 ++- .../_collapsible_sidebar.scss | 0 .../collapsible_sidebar}/_depth.scss | 0 .../collapsible_sidebar/_index.scss | 2 ++ .../collapsible_sidebar.ts} | 14 ++++++----- .../angular/directives/css_truncate.ts} | 10 ++------ .../directives/debounce/__tests__/debounce.js | 7 ++++-- .../angular}/directives/debounce/debounce.js | 5 ---- .../angular}/directives/debounce/index.js | 0 .../angular}/directives/field_name.js | 6 +---- .../__snapshots__/field_name.test.tsx.snap | 0 .../directives/field_name/field_name.test.tsx | 0 .../directives/field_name/field_name.tsx | 5 ++-- .../directives/field_name/field_type_name.ts | 24 +++++++++---------- .../angular/directives}/fixed_scroll.js | 15 +++++------- .../angular/directives/render_complete.ts} | 9 +++---- .../components/table_header/helpers.tsx | 3 +-- .../np_ready/components/table/table_row.tsx | 2 +- src/legacy/ui/public/_index.scss | 1 - .../ui/public/collapsible_sidebar/_index.scss | 3 --- .../ui/public/collapsible_sidebar/index.js | 20 ---------------- .../ui/public/styles/_legacy/_index.scss | 1 - .../translations/translations/ja-JP.json | 22 ++++++++--------- .../translations/translations/zh-CN.json | 22 ++++++++--------- 28 files changed, 85 insertions(+), 131 deletions(-) rename src/legacy/{ui/public/directives/__tests__ => core_plugins/kibana/public/discover/__tests__/directives}/css_truncate.js (92%) rename src/legacy/{ui/public/directives/__tests__ => core_plugins/kibana/public/discover/__tests__/directives}/fixed_scroll.js (96%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular/directives}/collapsible_sidebar/_collapsible_sidebar.scss (100%) rename src/legacy/{ui/public/styles/_legacy => core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar}/_depth.scss (100%) create mode 100644 src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss rename src/legacy/{ui/public/collapsible_sidebar/collapsible_sidebar.js => core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts} (91%) rename src/legacy/{ui/public/directives/css_truncate.js => core_plugins/kibana/public/discover/np_ready/angular/directives/css_truncate.ts} (89%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/debounce/__tests__/debounce.js (95%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/debounce/debounce.js (92%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/debounce/index.js (100%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/field_name.js (86%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/field_name/__snapshots__/field_name.test.tsx.snap (100%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/field_name/field_name.test.tsx (100%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/field_name/field_name.tsx (91%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular}/directives/field_name/field_type_name.ts (62%) rename src/legacy/{ui/public => core_plugins/kibana/public/discover/np_ready/angular/directives}/fixed_scroll.js (96%) rename src/legacy/{ui/public/render_complete/directive.js => core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts} (81%) delete mode 100644 src/legacy/ui/public/collapsible_sidebar/_index.scss delete mode 100644 src/legacy/ui/public/collapsible_sidebar/index.js diff --git a/src/legacy/ui/public/directives/__tests__/css_truncate.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/css_truncate.js similarity index 92% rename from src/legacy/ui/public/directives/__tests__/css_truncate.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/directives/css_truncate.js index bf102f5a29fdb..8dea9c61475db 100644 --- a/src/legacy/ui/public/directives/__tests__/css_truncate.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/css_truncate.js @@ -20,7 +20,7 @@ import angular from 'angular'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import 'plugins/kibana/discover/legacy'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; let $parentScope; @@ -30,7 +30,9 @@ let $elem; const init = function(expandable) { // Load the application - ngMock.module('kibana'); + pluginInstance.initializeServices(); + pluginInstance.initializeInnerAngular(); + ngMock.module('app/discover'); // Create the scope ngMock.inject(function($rootScope, $compile) { diff --git a/src/legacy/ui/public/directives/__tests__/fixed_scroll.js b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js similarity index 96% rename from src/legacy/ui/public/directives/__tests__/fixed_scroll.js rename to src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js index b35836967c3f3..49a0df54079ea 100644 --- a/src/legacy/ui/public/directives/__tests__/fixed_scroll.js +++ b/src/legacy/core_plugins/kibana/public/discover/__tests__/directives/fixed_scroll.js @@ -18,8 +18,8 @@ */ import expect from '@kbn/expect'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; import ngMock from 'ng_mock'; -import '../../fixed_scroll'; import $ from 'jquery'; import sinon from 'sinon'; @@ -29,8 +29,9 @@ describe('FixedScroll directive', function() { let compile; let flushPendingTasks; const trash = []; - - beforeEach(ngMock.module('kibana')); + beforeEach(() => pluginInstance.initializeServices()); + beforeEach(() => pluginInstance.initializeInnerAngular()); + beforeEach(ngMock.module('app/discover')); beforeEach( ngMock.inject(function($compile, $rootScope, $timeout) { flushPendingTasks = function flushPendingTasks() { diff --git a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts index fea834686eb4f..36a6c8eaef40e 100644 --- a/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts +++ b/src/legacy/core_plugins/kibana/public/discover/get_inner_angular.ts @@ -42,22 +42,10 @@ import { registerListenEventListener } from 'ui/directives/listen/listen'; // @ts-ignore import { KbnAccessibleClickProvider } from 'ui/accessibility/kbn_accessible_click'; // @ts-ignore -import { FieldNameDirectiveProvider } from 'ui/directives/field_name'; -// @ts-ignore -import { CollapsibleSidebarProvider } from 'ui/collapsible_sidebar/collapsible_sidebar'; -// @ts-ignore -import { CssTruncateProvide } from 'ui/directives/css_truncate'; -// @ts-ignore -import { FixedScrollProvider } from 'ui/fixed_scroll'; -// @ts-ignore -import { DebounceProviderTimeout } from 'ui/directives/debounce/debounce'; -// @ts-ignore import { AppStateProvider } from 'ui/state_management/app_state'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore -import { createRenderCompleteDirective } from 'ui/render_complete/directive'; -// @ts-ignore import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; @@ -81,11 +69,19 @@ import { createFieldSearchDirective } from './np_ready/components/field_chooser/ import { createIndexPatternSelectDirective } from './np_ready/components/field_chooser/discover_index_pattern_directive'; import { createStringFieldProgressBarDirective } from './np_ready/components/field_chooser/string_progress_bar'; // @ts-ignore +import { FieldNameDirectiveProvider } from './np_ready/angular/directives/field_name'; +// @ts-ignore import { createFieldChooserDirective } from './np_ready/components/field_chooser/field_chooser'; - // @ts-ignore import { createDiscoverFieldDirective } from './np_ready/components/field_chooser/discover_field'; +import { CollapsibleSidebarProvider } from './np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar'; import { DiscoverStartPlugins } from './plugin'; +import { createCssTruncateDirective } from './np_ready/angular/directives/css_truncate'; +// @ts-ignore +import { FixedScrollProvider } from './np_ready/angular/directives/fixed_scroll'; +// @ts-ignore +import { DebounceProviderTimeout } from './np_ready/angular/directives/debounce/debounce'; +import { createRenderCompleteDirective } from './np_ready/angular/directives/render_complete'; /** * returns the main inner angular module, it contains all the parts of Angular Discover @@ -181,7 +177,7 @@ export function initializeInnerAngularModule( .directive('kbnAccessibleClick', KbnAccessibleClickProvider) .directive('fieldName', FieldNameDirectiveProvider) .directive('collapsibleSidebar', CollapsibleSidebarProvider) - .directive('cssTruncate', CssTruncateProvide) + .directive('cssTruncate', createCssTruncateDirective) .directive('fixedScroll', FixedScrollProvider) .directive('renderComplete', createRenderCompleteDirective) .directive('discoverFieldSearch', createFieldSearchDirective) diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index 9a0b0731b6b11..d1e1dafe7c878 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -47,10 +47,6 @@ export function setServices(newServices: any) { services = newServices; } -// import directives that -import 'ui/directives/css_truncate'; -import 'ui/directives/field_name'; - // EXPORT legacy static dependencies, should be migrated when available in a new version; export { angular }; export { wrapInI18nContext } from 'ui/i18n'; @@ -90,7 +86,6 @@ export { } from '../../../../../plugins/data/public'; export { ElasticSearchHit } from './np_ready/doc_views/doc_views_types'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; -export { FieldName } from 'ui/directives/field_name/field_name'; export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; // @ts-ignore export { buildPointSeriesData } from 'ui/agg_response/point_series/point_series'; diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss index c65243d99c8f4..2bfc74ffa0279 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/_index.scss @@ -1,2 +1,3 @@ @import 'no_results'; -@import 'histogram'; \ No newline at end of file +@import 'histogram'; +@import './collapsible_sidebar/index'; diff --git a/src/legacy/ui/public/collapsible_sidebar/_collapsible_sidebar.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss similarity index 100% rename from src/legacy/ui/public/collapsible_sidebar/_collapsible_sidebar.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_collapsible_sidebar.scss diff --git a/src/legacy/ui/public/styles/_legacy/_depth.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_depth.scss similarity index 100% rename from src/legacy/ui/public/styles/_legacy/_depth.scss rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_depth.scss diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss new file mode 100644 index 0000000000000..1409920d11aa7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/_index.scss @@ -0,0 +1,2 @@ +@import 'depth'; +@import 'collapsible_sidebar'; diff --git a/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts similarity index 91% rename from src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts index 98b8f310bb82f..5b6de7f16d444 100644 --- a/src/legacy/ui/public/collapsible_sidebar/collapsible_sidebar.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/collapsible_sidebar/collapsible_sidebar.ts @@ -19,7 +19,11 @@ import _ from 'lodash'; import $ from 'jquery'; -import { uiModules } from '../modules'; +import { IScope } from 'angular'; + +interface LazyScope extends IScope { + [key: string]: any; +} export function CollapsibleSidebarProvider() { // simply a list of all of all of angulars .col-md-* classes except 12 @@ -29,7 +33,7 @@ export function CollapsibleSidebarProvider() { return { restrict: 'C', - link: function($scope, $elem) { + link: ($scope: LazyScope, $elem: any) => { let isCollapsed = false; const $collapser = $( `<button @@ -48,10 +52,10 @@ export function CollapsibleSidebarProvider() { $collapser.append($icon); const $siblings = $elem.siblings(); - const siblingsClass = listOfWidthClasses.reduce(function(prev, className) { + const siblingsClass = listOfWidthClasses.reduce((prev: string, className: string) => { if (prev) return prev; return $siblings.hasClass(className) && className; - }, false); + }, ''); // If there is are only two elements we can assume the other one will take 100% of the width. const hasSingleSibling = $siblings.length === 1 && siblingsClass; @@ -82,5 +86,3 @@ export function CollapsibleSidebarProvider() { }, }; } - -uiModules.get('kibana').directive('collapsibleSidebar', CollapsibleSidebarProvider); diff --git a/src/legacy/ui/public/directives/css_truncate.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/css_truncate.ts similarity index 89% rename from src/legacy/ui/public/directives/css_truncate.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/css_truncate.ts index 9b2c8a49f719f..6aa645ea9368e 100644 --- a/src/legacy/ui/public/directives/css_truncate.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/css_truncate.ts @@ -16,15 +16,11 @@ * specific language governing permissions and limitations * under the License. */ - -import { uiModules } from '../modules'; -const module = uiModules.get('kibana'); - -export function CssTruncateProvide() { +export function createCssTruncateDirective() { return { restrict: 'A', scope: {}, - link: function($scope, $elem, attrs) { + link: ($scope: any, $elem: any, attrs: any) => { $elem.css({ overflow: 'hidden', 'white-space': 'nowrap', @@ -63,5 +59,3 @@ export function CssTruncateProvide() { }, }; } - -module.directive('cssTruncate', CssTruncateProvide); diff --git a/src/legacy/ui/public/directives/debounce/__tests__/debounce.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js similarity index 95% rename from src/legacy/ui/public/directives/debounce/__tests__/debounce.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js index bfbe809139204..43fa5ffbf299a 100644 --- a/src/legacy/ui/public/directives/debounce/__tests__/debounce.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/__tests__/debounce.js @@ -20,14 +20,17 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; -import { DebounceProvider } from '..'; +import { DebounceProvider } from '../index'; +import { pluginInstance } from 'plugins/kibana/discover/legacy'; let debounce; let debounceFromProvider; let $timeout; function init() { - ngMock.module('kibana'); + pluginInstance.initializeServices(); + pluginInstance.initializeInnerAngular(); + ngMock.module('app/discover'); ngMock.inject(function($injector, _$timeout_, Private) { $timeout = _$timeout_; diff --git a/src/legacy/ui/public/directives/debounce/debounce.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/debounce.js similarity index 92% rename from src/legacy/ui/public/directives/debounce/debounce.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/debounce.js index 7515ff4c00785..54507f673c2d6 100644 --- a/src/legacy/ui/public/directives/debounce/debounce.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/debounce.js @@ -18,12 +18,9 @@ */ import _ from 'lodash'; -import { uiModules } from '../../modules'; // Debounce service, angularized version of lodash debounce // borrowed heavily from https://github.com/shahata/angular-debounce -const module = uiModules.get('kibana'); - export function DebounceProviderTimeout($timeout) { return function(func, wait, options) { let timeout; @@ -70,8 +67,6 @@ export function DebounceProviderTimeout($timeout) { }; } -module.service('debounce', ['$timeout', DebounceProviderTimeout]); - export function DebounceProvider(debounce) { return debounce; } diff --git a/src/legacy/ui/public/directives/debounce/index.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/index.js similarity index 100% rename from src/legacy/ui/public/directives/debounce/index.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/debounce/index.js diff --git a/src/legacy/ui/public/directives/field_name.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js similarity index 86% rename from src/legacy/ui/public/directives/field_name.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js index aff849fc5602f..4bc498928be52 100644 --- a/src/legacy/ui/public/directives/field_name.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name.js @@ -17,9 +17,7 @@ * under the License. */ import { FieldName } from './field_name/field_name'; -import { uiModules } from '../modules'; -import { wrapInI18nContext } from 'ui/i18n'; -const module = uiModules.get('kibana'); +import { wrapInI18nContext } from '../../../kibana_services'; export function FieldNameDirectiveProvider(config, reactDirective) { return reactDirective( @@ -35,5 +33,3 @@ export function FieldNameDirectiveProvider(config, reactDirective) { } ); } - -module.directive('fieldName', FieldNameDirectiveProvider); diff --git a/src/legacy/ui/public/directives/field_name/__snapshots__/field_name.test.tsx.snap b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap similarity index 100% rename from src/legacy/ui/public/directives/field_name/__snapshots__/field_name.test.tsx.snap rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/__snapshots__/field_name.test.tsx.snap diff --git a/src/legacy/ui/public/directives/field_name/field_name.test.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.test.tsx similarity index 100% rename from src/legacy/ui/public/directives/field_name/field_name.test.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.test.tsx diff --git a/src/legacy/ui/public/directives/field_name/field_name.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx similarity index 91% rename from src/legacy/ui/public/directives/field_name/field_name.tsx rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx index 0340ce9cb5d1b..95720bee38df8 100644 --- a/src/legacy/ui/public/directives/field_name/field_name.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_name.tsx @@ -18,9 +18,8 @@ */ import React from 'react'; import classNames from 'classnames'; -// @ts-ignore -import { shortenDottedString } from '../../../../core_plugins/kibana/common/utils/shorten_dotted_string'; -import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; +import { FieldIcon } from '../../../../../../../../../plugins/kibana_react/public'; +import { shortenDottedString } from '../../../../../../../../../plugins/data/common/utils'; import { getFieldTypeName } from './field_type_name'; // property field is provided at discover's field chooser diff --git a/src/legacy/ui/public/directives/field_name/field_type_name.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts similarity index 62% rename from src/legacy/ui/public/directives/field_name/field_type_name.ts rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts index c8c886015cea3..0cf428ee48b9d 100644 --- a/src/legacy/ui/public/directives/field_name/field_type_name.ts +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/field_name/field_type_name.ts @@ -21,52 +21,52 @@ import { i18n } from '@kbn/i18n'; export function getFieldTypeName(type: string) { switch (type) { case 'boolean': - return i18n.translate('common.ui.directives.fieldNameIcons.booleanAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.booleanAriaLabel', { defaultMessage: 'Boolean field', }); case 'conflict': - return i18n.translate('common.ui.directives.fieldNameIcons.conflictFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.conflictFieldAriaLabel', { defaultMessage: 'Conflicting field', }); case 'date': - return i18n.translate('common.ui.directives.fieldNameIcons.dateFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.dateFieldAriaLabel', { defaultMessage: 'Date field', }); case 'geo_point': - return i18n.translate('common.ui.directives.fieldNameIcons.geoPointFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.geoPointFieldAriaLabel', { defaultMessage: 'Geo point field', }); case 'geo_shape': - return i18n.translate('common.ui.directives.fieldNameIcons.geoShapeFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.geoShapeFieldAriaLabel', { defaultMessage: 'Geo shape field', }); case 'ip': - return i18n.translate('common.ui.directives.fieldNameIcons.ipAddressFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.ipAddressFieldAriaLabel', { defaultMessage: 'IP address field', }); case 'murmur3': - return i18n.translate('common.ui.directives.fieldNameIcons.murmur3FieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.murmur3FieldAriaLabel', { defaultMessage: 'Murmur3 field', }); case 'number': - return i18n.translate('common.ui.directives.fieldNameIcons.numberFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.numberFieldAriaLabel', { defaultMessage: 'Number field', }); case 'source': // Note that this type is currently not provided, type for _source is undefined - return i18n.translate('common.ui.directives.fieldNameIcons.sourceFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.sourceFieldAriaLabel', { defaultMessage: 'Source field', }); case 'string': - return i18n.translate('common.ui.directives.fieldNameIcons.stringFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.stringFieldAriaLabel', { defaultMessage: 'String field', }); case 'nested': - return i18n.translate('common.ui.directives.fieldNameIcons.nestedFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.nestedFieldAriaLabel', { defaultMessage: 'Nested field', }); default: - return i18n.translate('common.ui.directives.fieldNameIcons.unknownFieldAriaLabel', { + return i18n.translate('kbn.discover.fieldNameIcons.unknownFieldAriaLabel', { defaultMessage: 'Unknown field', }); } diff --git a/src/legacy/ui/public/fixed_scroll.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/fixed_scroll.js similarity index 96% rename from src/legacy/ui/public/fixed_scroll.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/fixed_scroll.js index 228ba3f8aaa99..bc159c14a16a7 100644 --- a/src/legacy/ui/public/fixed_scroll.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/fixed_scroll.js @@ -19,11 +19,15 @@ import $ from 'jquery'; import _ from 'lodash'; -import { uiModules } from './modules'; -import { DebounceProvider } from 'ui/directives/debounce'; +import { DebounceProvider } from './debounce'; const SCROLLER_HEIGHT = 20; +/** + * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events + * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar + * might be waaaay down the page, like the doc table on Discover. + */ export function FixedScrollProvider(Private) { const debounce = Private(DebounceProvider); @@ -145,10 +149,3 @@ export function FixedScrollProvider(Private) { }, }; } - -/** - * This directive adds a fixed horizontal scrollbar to the bottom of the window that proxies its scroll events - * to the target element's real scrollbar. This is useful when the target element's horizontal scrollbar - * might be waaaay down the page, like the doc table on Discover. - */ -uiModules.get('kibana').directive('fixedScroll', FixedScrollProvider); diff --git a/src/legacy/ui/public/render_complete/directive.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts similarity index 81% rename from src/legacy/ui/public/render_complete/directive.js rename to src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts index 961a07f58293a..7757deb806a18 100644 --- a/src/legacy/ui/public/render_complete/directive.js +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/directives/render_complete.ts @@ -16,18 +16,15 @@ * specific language governing permissions and limitations * under the License. */ - -import { uiModules } from '../modules'; -import { RenderCompleteHelper } from '../../../../plugins/kibana_utils/public'; +import { IScope } from 'angular'; +import { RenderCompleteHelper } from '../../../../../../../../plugins/kibana_utils/public'; export function createRenderCompleteDirective() { return { - controller($scope, $element) { + controller($scope: IScope, $element: JQLite) { const el = $element[0]; const renderCompleteHelper = new RenderCompleteHelper(el); $scope.$on('$destroy', renderCompleteHelper.destroy); }, }; } - -uiModules.get('kibana').directive('renderComplete', createRenderCompleteDirective); diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx index 990ace32e6449..68ba508ffebdd 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/doc_table/components/table_header/helpers.tsx @@ -17,8 +17,7 @@ * under the License. */ import { IndexPattern } from '../../../../../kibana_services'; -// @ts-ignore -import { shortenDottedString } from '../../../../../../../common/utils/shorten_dotted_string'; +import { shortenDottedString } from '../../../../../../../../../../plugins/data/common/utils'; export type SortOrder = [string, string]; export interface ColumnProps { diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx index d1b05f20a6cb0..7a78e89416361 100644 --- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx +++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/table/table_row.tsx @@ -18,7 +18,6 @@ */ import classNames from 'classnames'; import React, { ReactNode } from 'react'; -import { FieldName } from '../../../kibana_services'; import { FieldMapping, DocViewFilterFn } from '../../doc_views/doc_views_types'; import { DocViewTableRowBtnFilterAdd } from './table_row_btn_filter_add'; import { DocViewTableRowBtnFilterRemove } from './table_row_btn_filter_remove'; @@ -27,6 +26,7 @@ import { DocViewTableRowBtnCollapse } from './table_row_btn_collapse'; import { DocViewTableRowBtnFilterExists } from './table_row_btn_filter_exists'; import { DocViewTableRowIconNoMapping } from './table_row_icon_no_mapping'; import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore'; +import { FieldName } from '../../angular/directives/field_name/field_name'; export interface Props { field: string; diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index e4e58019dda69..e990ba2a46de5 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -10,7 +10,6 @@ @import './accessibility/index'; @import './chrome/index'; -@import './collapsible_sidebar/index'; @import './directives/index'; @import './error_auto_create_index/index'; @import './error_url_overflow/index'; diff --git a/src/legacy/ui/public/collapsible_sidebar/_index.scss b/src/legacy/ui/public/collapsible_sidebar/_index.scss deleted file mode 100644 index 84b759f66720e..0000000000000 --- a/src/legacy/ui/public/collapsible_sidebar/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import '../styles/_legacy/depth'; - -@import './collapsible_sidebar'; diff --git a/src/legacy/ui/public/collapsible_sidebar/index.js b/src/legacy/ui/public/collapsible_sidebar/index.js deleted file mode 100644 index 9f818922f7136..0000000000000 --- a/src/legacy/ui/public/collapsible_sidebar/index.js +++ /dev/null @@ -1,20 +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 './collapsible_sidebar'; diff --git a/src/legacy/ui/public/styles/_legacy/_index.scss b/src/legacy/ui/public/styles/_legacy/_index.scss index 106730fecd2b8..a0b1a98b09b7d 100644 --- a/src/legacy/ui/public/styles/_legacy/_index.scss +++ b/src/legacy/ui/public/styles/_legacy/_index.scss @@ -1,6 +1,5 @@ // // // // KIBANA THEME -@import './depth'; @import './base'; @import './mixins'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 3a6ba45413de6..74fbbdd5f967b 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -183,17 +183,6 @@ "common.ui.chrome.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", "common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", "common.ui.chrome.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", - "common.ui.directives.fieldNameIcons.booleanAriaLabel": "ブールフィールド", - "common.ui.directives.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド", - "common.ui.directives.fieldNameIcons.dateFieldAriaLabel": "日付フィールド", - "common.ui.directives.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイント", - "common.ui.directives.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形", - "common.ui.directives.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレスフィールド", - "common.ui.directives.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 フィールド", - "common.ui.directives.fieldNameIcons.numberFieldAriaLabel": "数値フィールド", - "common.ui.directives.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", - "common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", - "common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", "common.ui.directives.paginate.size.allDropDownOptionLabel": "すべて", "common.ui.dualRangeControl.mustSetBothErrorMessage": "下と上の値の両方を設定する必要があります", "common.ui.dualRangeControl.outsideOfRangeErrorMessage": "値は {min} と {max} の間でなければなりません", @@ -1114,6 +1103,17 @@ "kbn.discover.fieldChooser.searchPlaceHolder": "検索フィールド", "kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel": "フィールド設定を非表示", "kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel": "フィールド設定を表示", + "kbn.discover.fieldNameIcons.booleanAriaLabel": "ブールフィールド", + "kbn.discover.fieldNameIcons.conflictFieldAriaLabel": "矛盾フィールド", + "kbn.discover.fieldNameIcons.dateFieldAriaLabel": "日付フィールド", + "kbn.discover.fieldNameIcons.geoPointFieldAriaLabel": "地理ポイント", + "kbn.discover.fieldNameIcons.geoShapeFieldAriaLabel": "地理情報図形", + "kbn.discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP アドレスフィールド", + "kbn.discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 フィールド", + "kbn.discover.fieldNameIcons.numberFieldAriaLabel": "数値フィールド", + "kbn.discover.fieldNameIcons.sourceFieldAriaLabel": "ソースフィールド", + "kbn.discover.fieldNameIcons.stringFieldAriaLabel": "文字列フィールド", + "kbn.discover.fieldNameIcons.unknownFieldAriaLabel": "不明なフィールド", "kbn.discover.histogram.partialData.bucketTooltipText": "選択された時間範囲にはこのバケット全体は含まれていませんが、一部データが含まれている可能性があります。", "kbn.discover.histogramOfFoundDocumentsAriaLabel": "発見されたドキュメントのヒストグラム", "kbn.discover.hitsPluralTitle": "{hits, plural, one {ヒット} other {ヒット}}", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index f7cbaa7d72158..97c4ea7d9f5a9 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -183,17 +183,6 @@ "common.ui.chrome.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", "common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置", "common.ui.chrome.bigUrlWarningNotificationTitle": "URL 过长,Kibana 可能无法工作", - "common.ui.directives.fieldNameIcons.booleanAriaLabel": "布尔字段", - "common.ui.directives.fieldNameIcons.conflictFieldAriaLabel": "冲突字段", - "common.ui.directives.fieldNameIcons.dateFieldAriaLabel": "日期字段", - "common.ui.directives.fieldNameIcons.geoPointFieldAriaLabel": "地理位置点字段", - "common.ui.directives.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状字段", - "common.ui.directives.fieldNameIcons.ipAddressFieldAriaLabel": "IP 地址字段", - "common.ui.directives.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 字段", - "common.ui.directives.fieldNameIcons.numberFieldAriaLabel": "数字字段", - "common.ui.directives.fieldNameIcons.sourceFieldAriaLabel": "源字段", - "common.ui.directives.fieldNameIcons.stringFieldAriaLabel": "字符串字段", - "common.ui.directives.fieldNameIcons.unknownFieldAriaLabel": "未知字段", "common.ui.directives.paginate.size.allDropDownOptionLabel": "全部", "common.ui.dualRangeControl.mustSetBothErrorMessage": "下限值和上限值都须设置", "common.ui.dualRangeControl.outsideOfRangeErrorMessage": "值必须是在 {min} 到 {max} 的范围内", @@ -1114,6 +1103,17 @@ "kbn.discover.fieldChooser.searchPlaceHolder": "搜索字段", "kbn.discover.fieldChooser.toggleFieldFilterButtonHideAriaLabel": "隐藏字段设置", "kbn.discover.fieldChooser.toggleFieldFilterButtonShowAriaLabel": "显示字段设置", + "kbn.discover.fieldNameIcons.booleanAriaLabel": "布尔字段", + "kbn.discover.fieldNameIcons.conflictFieldAriaLabel": "冲突字段", + "kbn.discover.fieldNameIcons.dateFieldAriaLabel": "日期字段", + "kbn.discover.fieldNameIcons.geoPointFieldAriaLabel": "地理位置点字段", + "kbn.discover.fieldNameIcons.geoShapeFieldAriaLabel": "几何形状字段", + "kbn.discover.fieldNameIcons.ipAddressFieldAriaLabel": "IP 地址字段", + "kbn.discover.fieldNameIcons.murmur3FieldAriaLabel": "Murmur3 字段", + "kbn.discover.fieldNameIcons.numberFieldAriaLabel": "数字字段", + "kbn.discover.fieldNameIcons.sourceFieldAriaLabel": "源字段", + "kbn.discover.fieldNameIcons.stringFieldAriaLabel": "字符串字段", + "kbn.discover.fieldNameIcons.unknownFieldAriaLabel": "未知字段", "kbn.discover.histogram.partialData.bucketTooltipText": "选定的时间范围不包括此整个存储桶,其可能包含部分数据。", "kbn.discover.histogramOfFoundDocumentsAriaLabel": "已找到文档的直方图", "kbn.discover.hitsPluralTitle": "{hits, plural, one {次命中} other {次命中}}", From 4f267413c84a6d928076483362de16490c22d3e8 Mon Sep 17 00:00:00 2001 From: Robert Oskamp <robert.oskamp@elastic.co> Date: Tue, 4 Feb 2020 16:08:17 +0100 Subject: [PATCH 37/60] [ML] Functional tests - stabilize job row and analytics result view assertions (#56595) This PR makes assertions of the job row and analytics result views more robust against loading time issues during test execution. --- .../services/machine_learning/data_frame_analytics_table.ts | 2 +- x-pack/test/functional/services/machine_learning/job_table.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts b/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts index 0324b440548bc..1d710a1c4cec7 100644 --- a/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts +++ b/x-pack/test/functional/services/machine_learning/data_frame_analytics_table.ts @@ -92,7 +92,7 @@ export function MachineLearningDataFrameAnalyticsTableProvider({ getService }: F public async openResultsView() { await this.assertJobViewButtonExists(); await testSubjects.click('mlAnalyticsJobViewButton'); - await testSubjects.existOrFail('mlPageDataFrameAnalyticsExploration', { timeout: 5000 }); + await testSubjects.existOrFail('mlPageDataFrameAnalyticsExploration', { timeout: 20 * 1000 }); } public async filterWithSearchString(filter: string) { diff --git a/x-pack/test/functional/services/machine_learning/job_table.ts b/x-pack/test/functional/services/machine_learning/job_table.ts index eed6d180b4dca..153a0ac477b47 100644 --- a/x-pack/test/functional/services/machine_learning/job_table.ts +++ b/x-pack/test/functional/services/machine_learning/job_table.ts @@ -176,6 +176,7 @@ export function MachineLearningJobTableProvider({ getService }: FtrProviderConte } public async assertJobRowFields(jobId: string, expectedRow: object) { + await this.refreshJobList(); const rows = await this.parseJobTable(); const jobRow = rows.filter(row => row.id === jobId)[0]; expect(jobRow).to.eql(expectedRow); From be61507349fa3a08cee7ca75a15d6c2b2ed41a19 Mon Sep 17 00:00:00 2001 From: Alexey Antonov <alexwizp@gmail.com> Date: Tue, 4 Feb 2020 18:25:26 +0300 Subject: [PATCH 38/60] =?UTF-8?q?Move=20kuery=5Fautocomplete=20=E2=87=92?= =?UTF-8?q?=20NP=20(#56607)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move kuery_autocomplete ⇒ NP Closes: #51277 * fix i18n keys * update translations * data_xpack -> data_enhanced * data -> data_enhanced * fix i18nrc * fix PR comments --- x-pack/.i18nrc.json | 2 +- x-pack/index.js | 2 - .../plugins/kuery_autocomplete/index.ts | 22 -------- .../kuery_autocomplete/public/legacy.ts | 27 ---------- .../kuery_autocomplete/public/plugin.ts | 41 --------------- x-pack/plugins/data_enhanced/kibana.json | 13 +++++ .../public/autocomplete/index.ts | 10 ++++ .../__fixtures__/index_pattern_response.json | 0 .../kql_query_suggestion/conjunction.test.ts | 4 +- .../kql_query_suggestion/conjunction.tsx | 18 +++---- .../kql_query_suggestion/field.test.ts | 4 +- .../providers}/kql_query_suggestion/field.tsx | 8 ++- .../providers}/kql_query_suggestion/index.ts | 4 +- .../lib/escape_kuery.test.ts | 0 .../kql_query_suggestion/lib/escape_kuery.ts | 0 .../kql_query_suggestion/operator.test.ts | 4 +- .../kql_query_suggestion/operator.tsx | 50 +++++++++---------- .../sort_prefix_first.test.ts | 0 .../kql_query_suggestion/sort_prefix_first.ts | 0 .../providers}/kql_query_suggestion/types.ts | 2 +- .../kql_query_suggestion/value.test.ts | 6 +-- .../providers}/kql_query_suggestion/value.ts | 4 +- .../data_enhanced}/public/index.ts | 9 ++-- x-pack/plugins/data_enhanced/public/plugin.ts | 33 ++++++++++++ .../data_enhanced}/public/services.ts | 4 +- .../translations/translations/ja-JP.json | 34 ++++++------- .../translations/translations/zh-CN.json | 34 ++++++------- 27 files changed, 152 insertions(+), 183 deletions(-) delete mode 100644 x-pack/legacy/plugins/kuery_autocomplete/index.ts delete mode 100644 x-pack/legacy/plugins/kuery_autocomplete/public/legacy.ts delete mode 100644 x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts create mode 100644 x-pack/plugins/data_enhanced/kibana.json create mode 100644 x-pack/plugins/data_enhanced/public/autocomplete/index.ts rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/__fixtures__/index_pattern_response.json (100%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/conjunction.test.ts (93%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/conjunction.tsx (69%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/field.test.ts (98%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/field.tsx (94%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/index.ts (94%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/lib/escape_kuery.test.ts (100%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/lib/escape_kuery.ts (100%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/operator.test.ts (94%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/operator.tsx (64%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/sort_prefix_first.test.ts (100%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/sort_prefix_first.ts (100%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/types.ts (85%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/value.test.ts (96%) rename x-pack/{legacy/plugins/kuery_autocomplete/public => plugins/data_enhanced/public/autocomplete/providers}/kql_query_suggestion/value.ts (93%) rename x-pack/{legacy/plugins/kuery_autocomplete => plugins/data_enhanced}/public/index.ts (50%) create mode 100644 x-pack/plugins/data_enhanced/public/plugin.ts rename x-pack/{legacy/plugins/kuery_autocomplete => plugins/data_enhanced}/public/services.ts (70%) diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 32ff4908629eb..50348a74f0725 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -18,7 +18,7 @@ "xpack.idxMgmt": "legacy/plugins/index_management", "xpack.indexLifecycleMgmt": "legacy/plugins/index_lifecycle_management", "xpack.infra": "legacy/plugins/infra", - "xpack.kueryAutocomplete": "legacy/plugins/kuery_autocomplete", + "xpack.data": "plugins/data_enhanced", "xpack.lens": "legacy/plugins/lens", "xpack.licenseMgmt": "legacy/plugins/license_management", "xpack.licensing": "plugins/licensing", diff --git a/x-pack/index.js b/x-pack/index.js index 9ab6e2ee322d9..e2d25ce87b26b 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -23,7 +23,6 @@ import { licenseManagement } from './legacy/plugins/license_management'; import { indexManagement } from './legacy/plugins/index_management'; import { indexLifecycleManagement } from './legacy/plugins/index_lifecycle_management'; import { spaces } from './legacy/plugins/spaces'; -import { kueryAutocompleteInitializer } from './legacy/plugins/kuery_autocomplete'; import { canvas } from './legacy/plugins/canvas'; import { infra } from './legacy/plugins/infra'; import { taskManager } from './legacy/plugins/task_manager'; @@ -65,7 +64,6 @@ module.exports = function(kibana) { licenseManagement(kibana), indexManagement(kibana), indexLifecycleManagement(kibana), - kueryAutocompleteInitializer(kibana), infra(kibana), taskManager(kibana), rollup(kibana), diff --git a/x-pack/legacy/plugins/kuery_autocomplete/index.ts b/x-pack/legacy/plugins/kuery_autocomplete/index.ts deleted file mode 100644 index 5f9d74ed799e6..0000000000000 --- a/x-pack/legacy/plugins/kuery_autocomplete/index.ts +++ /dev/null @@ -1,22 +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 { resolve } from 'path'; -import { Legacy } from 'kibana'; - -import { LegacyPluginApi, LegacyPluginInitializer } from '../../../../src/legacy/types'; - -export const kueryAutocompleteInitializer: LegacyPluginInitializer = ({ - Plugin, -}: LegacyPluginApi) => - new Plugin({ - id: 'kuery_autocomplete', - publicDir: resolve(__dirname, 'public'), - uiExports: { - hacks: [resolve(__dirname, 'public/legacy')], - }, - init: (server: Legacy.Server) => ({}), - } as Legacy.PluginSpecOptions); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/legacy.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/legacy.ts deleted file mode 100644 index 303fe8c557fbd..0000000000000 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/legacy.ts +++ /dev/null @@ -1,27 +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 { PluginInitializerContext } from 'src/core/public'; -import { npSetup, npStart } from 'ui/new_platform'; - -import { plugin } from './index'; -import { - KueryAutocompletePluginSetupDependencies, - KueryAutocompletePluginStartDependencies, -} from './plugin'; - -const pluginsSetup: Readonly<KueryAutocompletePluginSetupDependencies> = { - data: npSetup.plugins.data, -}; - -const pluginsStart: Readonly<KueryAutocompletePluginStartDependencies> = { - data: npStart.plugins.data, -}; - -const pluginInstance = plugin({} as PluginInitializerContext); - -export const setup = pluginInstance.setup(npSetup.core, pluginsSetup); -export const start = pluginInstance.start(npStart.core, pluginsStart); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts deleted file mode 100644 index 81737c4636532..0000000000000 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/plugin.ts +++ /dev/null @@ -1,41 +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 { PluginInitializerContext, CoreSetup, CoreStart, Plugin } from 'src/core/public'; -import { Plugin as DataPublicPlugin } from '../../../../../src/plugins/data/public'; -import { setAutocompleteService } from './services'; -import { setupKqlQuerySuggestionProvider } from './kql_query_suggestion'; - -/** @internal */ -export interface KueryAutocompletePluginSetupDependencies { - data: ReturnType<DataPublicPlugin['setup']>; -} - -/** @internal */ -export interface KueryAutocompletePluginStartDependencies { - data: ReturnType<DataPublicPlugin['start']>; -} - -const KUERY_LANGUAGE_NAME = 'kuery'; - -/** @internal */ -export class KueryAutocompletePlugin implements Plugin<Promise<void>, void> { - initializerContext: PluginInitializerContext; - - constructor(initializerContext: PluginInitializerContext) { - this.initializerContext = initializerContext; - } - - public async setup(core: CoreSetup, plugins: KueryAutocompletePluginSetupDependencies) { - const kueryProvider = setupKqlQuerySuggestionProvider(core); - - plugins.data.autocomplete.addQuerySuggestionProvider(KUERY_LANGUAGE_NAME, kueryProvider); - } - - public start(core: CoreStart, plugins: KueryAutocompletePluginStartDependencies) { - setAutocompleteService(plugins.data.autocomplete); - } -} diff --git a/x-pack/plugins/data_enhanced/kibana.json b/x-pack/plugins/data_enhanced/kibana.json new file mode 100644 index 0000000000000..4dbfe958eff68 --- /dev/null +++ b/x-pack/plugins/data_enhanced/kibana.json @@ -0,0 +1,13 @@ +{ + "id": "data_enhanced", + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": [ + "xpack" + ], + "requiredPlugins": [ + "data" + ], + "server": false, + "ui": true +} diff --git a/x-pack/plugins/data_enhanced/public/autocomplete/index.ts b/x-pack/plugins/data_enhanced/public/autocomplete/index.ts new file mode 100644 index 0000000000000..59670a89d2caf --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/autocomplete/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 { + setupKqlQuerySuggestionProvider, + KUERY_LANGUAGE_NAME, +} from './providers/kql_query_suggestion'; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/__fixtures__/index_pattern_response.json rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/__fixtures__/index_pattern_response.json diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts similarity index 93% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts index e8aec0deec6d7..d993c3d8ad51d 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.test.ts @@ -5,8 +5,8 @@ */ import { setupGetConjunctionSuggestions } from './conjunction'; -import { autocomplete, esKuery } from '../../../../../../src/plugins/data/public'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial<esKuery.KueryNode>) => (kueryNode as unknown) as esKuery.KueryNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx similarity index 69% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.tsx rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx index f570586274fdd..fa655562134cc 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/conjunction.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/conjunction.tsx @@ -7,21 +7,21 @@ import React from 'react'; import { $Keys } from 'utility-types'; import { FormattedMessage } from '@kbn/i18n/react'; import { KqlQuerySuggestionProvider } from './types'; -import { autocomplete } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const bothArgumentsText = ( <FormattedMessage - id="xpack.kueryAutocomplete.andOperatorDescription.bothArgumentsText" + id="xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText" defaultMessage="both arguments" - description="Part of xpack.kueryAutocomplete.andOperatorDescription. Full text: 'Requires both arguments to be true'" + description="Part of xpack.data.kueryAutocomplete.andOperatorDescription. Full text: 'Requires both arguments to be true'" /> ); const oneOrMoreArgumentsText = ( <FormattedMessage - id="xpack.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText" + id="xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText" defaultMessage="one or more arguments" - description="Part of xpack.kueryAutocomplete.orOperatorDescription. Full text: 'Requires one or more arguments to be true'" + description="Part of xpack.data.kueryAutocomplete.orOperatorDescription. Full text: 'Requires one or more arguments to be true'" /> ); @@ -29,20 +29,20 @@ const conjunctions: Record<string, JSX.Element> = { and: ( <p> <FormattedMessage - id="xpack.kueryAutocomplete.andOperatorDescription" + id="xpack.data.kueryAutocomplete.andOperatorDescription" defaultMessage="Requires {bothArguments} to be true" values={{ bothArguments: <span className="kbnSuggestionItem__callout">{bothArgumentsText}</span>, }} description="Full text: ' Requires both arguments to be true'. See - 'xpack.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." + 'xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText' for 'both arguments' part." /> </p> ), or: ( <p> <FormattedMessage - id="xpack.kueryAutocomplete.orOperatorDescription" + id="xpack.data.kueryAutocomplete.orOperatorDescription" defaultMessage="Requires {oneOrMoreArguments} to be true" values={{ oneOrMoreArguments: ( @@ -50,7 +50,7 @@ const conjunctions: Record<string, JSX.Element> = { ), }} description="Full text: 'Requires one or more arguments to be true'. See - 'xpack.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." + 'xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText' for 'one or more arguments' part." /> </p> ), diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts similarity index 98% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts index 2fd5cfd17eb69..d05fd49d266f2 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.test.ts @@ -6,8 +6,8 @@ import indexPatternResponse from './__fixtures__/index_pattern_response.json'; import { setupGetFieldSuggestions } from './field'; -import { isFilterable, autocomplete, esKuery } from '../../../../../../src/plugins/data/public'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { isFilterable, autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial<esKuery.KueryNode>) => (kueryNode as unknown) as esKuery.KueryNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx similarity index 94% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.tsx rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx index a8af884c24fc3..f04312b925436 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/field.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/field.tsx @@ -8,14 +8,18 @@ import { flatten } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { escapeKuery } from './lib/escape_kuery'; import { sortPrefixFirst } from './sort_prefix_first'; -import { IFieldType, isFilterable, autocomplete } from '../../../../../../src/plugins/data/public'; +import { + IFieldType, + isFilterable, + autocomplete, +} from '../../../../../../../src/plugins/data/public'; import { KqlQuerySuggestionProvider } from './types'; const getDescription = (field: IFieldType) => { return ( <p> <FormattedMessage - id="xpack.kueryAutocomplete.filterResultsDescription" + id="xpack.data.kueryAutocomplete.filterResultsDescription" defaultMessage="Filter results that contain {fieldName}" values={{ fieldName: <span className="kbnSuggestionItem__callout">{field.name}</span> }} /> diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts similarity index 94% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts index 2cc15fe4c9280..6168ab8fc3462 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/index.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/index.ts @@ -11,13 +11,15 @@ import { setupGetFieldSuggestions } from './field'; import { setupGetValueSuggestions } from './value'; import { setupGetOperatorSuggestions } from './operator'; import { setupGetConjunctionSuggestions } from './conjunction'; -import { esKuery, autocomplete } from '../../../../../../src/plugins/data/public'; +import { esKuery, autocomplete } from '../../../../../../../src/plugins/data/public'; const cursorSymbol = '@kuery-cursor@'; const dedup = (suggestions: autocomplete.QuerySuggestion[]): autocomplete.QuerySuggestion[] => uniq(suggestions, ({ type, text, start, end }) => [type, text, start, end].join('|')); +export const KUERY_LANGUAGE_NAME = 'kuery'; + export const setupKqlQuerySuggestionProvider = ( core: CoreSetup ): autocomplete.QuerySuggestionsGetFn => { diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/lib/escape_kuery.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/lib/escape_kuery.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.test.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/lib/escape_kuery.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/lib/escape_kuery.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/lib/escape_kuery.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts similarity index 94% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts index acafc4e169c8f..7e564b96064ef 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.test.ts @@ -6,8 +6,8 @@ import indexPatternResponse from './__fixtures__/index_pattern_response.json'; import { setupGetOperatorSuggestions } from './operator'; -import { autocomplete, esKuery } from '../../../../../../src/plugins/data/public'; -import { coreMock } from '../../../../../../src/core/public/mocks'; +import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; const mockKueryNode = (kueryNode: Partial<esKuery.KueryNode>) => (kueryNode as unknown) as esKuery.KueryNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.tsx b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx similarity index 64% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.tsx rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx index 6e9010c4310fb..af90e7bfe1172 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/operator.tsx +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/operator.tsx @@ -10,48 +10,48 @@ import { $Keys } from 'utility-types'; import { flatten } from 'lodash'; import { KqlQuerySuggestionProvider } from './types'; -import { autocomplete } from '../../../../../../src/plugins/data/public'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const equalsText = ( <FormattedMessage - id="xpack.kueryAutocomplete.equalOperatorDescription.equalsText" + id="xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText" defaultMessage="equals" - description="Part of xpack.kueryAutocomplete.equalOperatorDescription. Full text: 'equals some value'" + description="Part of xpack.data.kueryAutocomplete.equalOperatorDescription. Full text: 'equals some value'" /> ); const lessThanOrEqualToText = ( <FormattedMessage - id="xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText" + id="xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText" defaultMessage="less than or equal to" - description="Part of xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription. Full text: 'is less than or equal to some value'" + description="Part of xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription. Full text: 'is less than or equal to some value'" /> ); const greaterThanOrEqualToText = ( <FormattedMessage - id="xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText" + id="xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText" defaultMessage="greater than or equal to" - description="Part of xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription. Full text: 'is greater than or equal to some value'" + description="Part of xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription. Full text: 'is greater than or equal to some value'" /> ); const lessThanText = ( <FormattedMessage - id="xpack.kueryAutocomplete.lessThanOperatorDescription.lessThanText" + id="xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText" defaultMessage="less than" - description="Part of xpack.kueryAutocomplete.lessThanOperatorDescription. Full text: 'is less than some value'" + description="Part of xpack.data.kueryAutocomplete.lessThanOperatorDescription. Full text: 'is less than some value'" /> ); const greaterThanText = ( <FormattedMessage - id="xpack.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText" + id="xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText" defaultMessage="greater than" - description="Part of xpack.kueryAutocomplete.greaterThanOperatorDescription. Full text: 'is greater than some value'" + description="Part of xpack.data.kueryAutocomplete.greaterThanOperatorDescription. Full text: 'is greater than some value'" /> ); const existsText = ( <FormattedMessage - id="xpack.kueryAutocomplete.existOperatorDescription.existsText" + id="xpack.data.kueryAutocomplete.existOperatorDescription.existsText" defaultMessage="exists" - description="Part of xpack.kueryAutocomplete.existOperatorDescription. Full text: 'exists in any form'" + description="Part of xpack.data.kueryAutocomplete.existOperatorDescription. Full text: 'exists in any form'" /> ); @@ -59,11 +59,11 @@ const operators = { ':': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.equalOperatorDescription" + id="xpack.data.kueryAutocomplete.equalOperatorDescription" defaultMessage="{equals} some value" values={{ equals: <span className="kbnSuggestionItem__callout">{equalsText}</span> }} description="Full text: 'equals some value'. See - 'xpack.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." + 'xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText' for 'equals' part." /> ), fieldTypes: ['string', 'number', 'date', 'ip', 'geo_point', 'geo_shape', 'boolean'], @@ -71,7 +71,7 @@ const operators = { '<=': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription" + id="xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription" defaultMessage="is {lessThanOrEqualTo} some value" values={{ lessThanOrEqualTo: ( @@ -79,7 +79,7 @@ const operators = { ), }} description="Full text: 'is less than or equal to some value'. See - 'xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText' for 'less than or equal to' part." + 'xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText' for 'less than or equal to' part." /> ), fieldTypes: ['number', 'date', 'ip'], @@ -87,7 +87,7 @@ const operators = { '>=': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription" + id="xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription" defaultMessage="is {greaterThanOrEqualTo} some value" values={{ greaterThanOrEqualTo: ( @@ -95,7 +95,7 @@ const operators = { ), }} description="Full text: 'is greater than or equal to some value'. See - 'xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText' for 'greater than or equal to' part." + 'xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText' for 'greater than or equal to' part." /> ), fieldTypes: ['number', 'date', 'ip'], @@ -103,11 +103,11 @@ const operators = { '<': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.lessThanOperatorDescription" + id="xpack.data.kueryAutocomplete.lessThanOperatorDescription" defaultMessage="is {lessThan} some value" values={{ lessThan: <span className="kbnSuggestionItem__callout">{lessThanText}</span> }} description="Full text: 'is less than some value'. See - 'xpack.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." + 'xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText' for 'less than' part." /> ), fieldTypes: ['number', 'date', 'ip'], @@ -115,13 +115,13 @@ const operators = { '>': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.greaterThanOperatorDescription" + id="xpack.data.kueryAutocomplete.greaterThanOperatorDescription" defaultMessage="is {greaterThan} some value" values={{ greaterThan: <span className="kbnSuggestionItem__callout">{greaterThanText}</span>, }} description="Full text: 'is greater than some value'. See - 'xpack.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." + 'xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText' for 'greater than' part." /> ), fieldTypes: ['number', 'date', 'ip'], @@ -129,11 +129,11 @@ const operators = { ': *': { description: ( <FormattedMessage - id="xpack.kueryAutocomplete.existOperatorDescription" + id="xpack.data.kueryAutocomplete.existOperatorDescription" defaultMessage="{exists} in any form" values={{ exists: <span className="kbnSuggestionItem__callout">{existsText}</span> }} description="Full text: 'exists in any form'. See - 'xpack.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." + 'xpack.data.kueryAutocomplete.existOperatorDescription.existsText' for 'exists' part." /> ), fieldTypes: undefined, diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.test.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts similarity index 100% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/sort_prefix_first.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/sort_prefix_first.ts diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/types.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts similarity index 85% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/types.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts index c51b75e001b9f..8e3146ab09848 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/types.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/types.ts @@ -5,7 +5,7 @@ */ import { CoreSetup } from 'kibana/public'; -import { esKuery, autocomplete } from '../../../../../../src/plugins/data/public'; +import { esKuery, autocomplete } from '../../../../../../../src/plugins/data/public'; export type KqlQuerySuggestionProvider<T = autocomplete.BasicQuerySuggestion> = ( core: CoreSetup diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts similarity index 96% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts index 5ffe30c877868..14eeabda97d1a 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.test.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.test.ts @@ -5,9 +5,9 @@ */ import { setupGetValueSuggestions } from './value'; import indexPatternResponse from './__fixtures__/index_pattern_response.json'; -import { coreMock } from '../../../../../../src/core/public/mocks'; -import { autocomplete, esKuery } from '../../../../../../src/plugins/data/public'; -import { setAutocompleteService } from '../services'; +import { coreMock } from '../../../../../../../src/core/public/mocks'; +import { autocomplete, esKuery } from '../../../../../../../src/plugins/data/public'; +import { setAutocompleteService } from '../../../services'; const mockKueryNode = (kueryNode: Partial<esKuery.KueryNode>) => (kueryNode as unknown) as esKuery.KueryNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.ts b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts similarity index 93% rename from x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.ts rename to x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts index 242b9ccba3508..83b8024d8314d 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/kql_query_suggestion/value.ts +++ b/x-pack/plugins/data_enhanced/public/autocomplete/providers/kql_query_suggestion/value.ts @@ -7,8 +7,8 @@ import { flatten } from 'lodash'; import { escapeQuotes } from './lib/escape_kuery'; import { KqlQuerySuggestionProvider } from './types'; -import { getAutocompleteService } from '../services'; -import { autocomplete } from '../../../../../../src/plugins/data/public'; +import { getAutocompleteService } from '../../../services'; +import { autocomplete } from '../../../../../../../src/plugins/data/public'; const wrapAsSuggestions = (start: number, end: number, query: string, values: string[]) => values diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/index.ts b/x-pack/plugins/data_enhanced/public/index.ts similarity index 50% rename from x-pack/legacy/plugins/kuery_autocomplete/public/index.ts rename to x-pack/plugins/data_enhanced/public/index.ts index c2241f424511f..93b6b7a957182 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/index.ts +++ b/x-pack/plugins/data_enhanced/public/index.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/public'; -import { KueryAutocompletePlugin as Plugin } from './plugin'; +import { DataEnhancedPlugin, DataEnhancedSetup, DataEnhancedStart } from './plugin'; -export function plugin(initializerContext: PluginInitializerContext) { - return new Plugin(initializerContext); -} +export const plugin = () => new DataEnhancedPlugin(); + +export { DataEnhancedSetup, DataEnhancedStart }; diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts new file mode 100644 index 0000000000000..14b5382bc85aa --- /dev/null +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -0,0 +1,33 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'src/core/public'; +import { DataPublicPluginSetup, DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { setAutocompleteService } from './services'; +import { setupKqlQuerySuggestionProvider, KUERY_LANGUAGE_NAME } from './autocomplete'; + +export interface DataEnhancedSetupDependencies { + data: DataPublicPluginSetup; +} +export interface DataEnhancedStartDependencies { + data: DataPublicPluginStart; +} + +export type DataEnhancedSetup = ReturnType<DataEnhancedPlugin['setup']>; +export type DataEnhancedStart = ReturnType<DataEnhancedPlugin['start']>; + +export class DataEnhancedPlugin implements Plugin { + public setup(core: CoreSetup, plugins: DataEnhancedSetupDependencies) { + plugins.data.autocomplete.addQuerySuggestionProvider( + KUERY_LANGUAGE_NAME, + setupKqlQuerySuggestionProvider(core) + ); + } + + public start(core: CoreStart, plugins: DataEnhancedStartDependencies) { + setAutocompleteService(plugins.data.autocomplete); + } +} diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/services.ts b/x-pack/plugins/data_enhanced/public/services.ts similarity index 70% rename from x-pack/legacy/plugins/kuery_autocomplete/public/services.ts rename to x-pack/plugins/data_enhanced/public/services.ts index 1ec48e597f636..847b4c783fd7f 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/services.ts +++ b/x-pack/plugins/data_enhanced/public/services.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createGetterSetter } from '../../../../../src/plugins/kibana_utils/public'; -import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import { createGetterSetter } from '../../../../src/plugins/kibana_utils/public'; +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; export const [getAutocompleteService, setAutocompleteService] = createGetterSetter< DataPublicPluginStart['autocomplete'] diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 74fbbdd5f967b..abf56cd2d05b6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -6753,23 +6753,23 @@ "xpack.infra.waffle.unableToSelectMetricErrorTitle": "メトリックのオプションまたは値を選択できません", "xpack.infra.waffleTime.autoRefreshButtonLabel": "自動更新", "xpack.infra.waffleTime.stopRefreshingButtonLabel": "更新中止", - "xpack.kueryAutocomplete.andOperatorDescription": "{bothArguments}がtrueであることを条件とする", - "xpack.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", - "xpack.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", - "xpack.kueryAutocomplete.equalOperatorDescription.equalsText": "一致する", - "xpack.kueryAutocomplete.existOperatorDescription": "いずれかの形式中に{exists}", - "xpack.kueryAutocomplete.existOperatorDescription.existsText": "存在する", - "xpack.kueryAutocomplete.filterResultsDescription": "{fieldName}を含む結果をフィルタリング", - "xpack.kueryAutocomplete.greaterThanOperatorDescription": "が一部の値{greaterThan}", - "xpack.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "より大きい", - "xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "が一部の値{greaterThanOrEqualTo}", - "xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "よりも大きいまたは等しい", - "xpack.kueryAutocomplete.lessThanOperatorDescription": "が一部の値{lessThan}", - "xpack.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "より小さい", - "xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription": "が一部の値{lessThanOrEqualTo}", - "xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "より小さいまたは等しい", - "xpack.kueryAutocomplete.orOperatorDescription": "{oneOrMoreArguments}がtrueであることを条件とする", - "xpack.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "1つ以上の引数", + "xpack.data.kueryAutocomplete.andOperatorDescription": "{bothArguments}がtrueであることを条件とする", + "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "両方の引数", + "xpack.data.kueryAutocomplete.equalOperatorDescription": "一部の値に{equals}", + "xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText": "一致する", + "xpack.data.kueryAutocomplete.existOperatorDescription": "いずれかの形式中に{exists}", + "xpack.data.kueryAutocomplete.existOperatorDescription.existsText": "存在する", + "xpack.data.kueryAutocomplete.filterResultsDescription": "{fieldName}を含む結果をフィルタリング", + "xpack.data.kueryAutocomplete.greaterThanOperatorDescription": "が一部の値{greaterThan}", + "xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "より大きい", + "xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "が一部の値{greaterThanOrEqualTo}", + "xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "よりも大きいまたは等しい", + "xpack.data.kueryAutocomplete.lessThanOperatorDescription": "が一部の値{lessThan}", + "xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "より小さい", + "xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription": "が一部の値{lessThanOrEqualTo}", + "xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "より小さいまたは等しい", + "xpack.data.kueryAutocomplete.orOperatorDescription": "{oneOrMoreArguments}がtrueであることを条件とする", + "xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "1つ以上の引数", "xpack.lens.app.docLoadingError": "保存されたドキュメントの保存中にエラーが発生", "xpack.lens.app.docSavingError": "ドキュメントの保存中にエラーが発生", "xpack.lens.app.indexPatternLoadingError": "インデックスパターンの読み込み中にエラーが発生", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 97c4ea7d9f5a9..b97982b5c99da 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -6752,23 +6752,23 @@ "xpack.infra.waffle.unableToSelectMetricErrorTitle": "无法选择指标选项或指标值。", "xpack.infra.waffleTime.autoRefreshButtonLabel": "自动刷新", "xpack.infra.waffleTime.stopRefreshingButtonLabel": "停止刷新", - "xpack.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为真", - "xpack.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", - "xpack.kueryAutocomplete.equalOperatorDescription": "{equals}某一值", - "xpack.kueryAutocomplete.equalOperatorDescription.equalsText": "等于", - "xpack.kueryAutocomplete.existOperatorDescription": "以任意形式{exists}", - "xpack.kueryAutocomplete.existOperatorDescription.existsText": "存在", - "xpack.kueryAutocomplete.filterResultsDescription": "筛选包含 {fieldName} 的结果", - "xpack.kueryAutocomplete.greaterThanOperatorDescription": "{greaterThan}某一值", - "xpack.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "大于", - "xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "{greaterThanOrEqualTo}某一值", - "xpack.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "大于或等于", - "xpack.kueryAutocomplete.lessThanOperatorDescription": "{lessThan}某一值", - "xpack.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "小于", - "xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription": "{lessThanOrEqualTo}某一值", - "xpack.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "小于或等于", - "xpack.kueryAutocomplete.orOperatorDescription": "需要{oneOrMoreArguments}为真", - "xpack.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "一个或多个参数", + "xpack.data.kueryAutocomplete.andOperatorDescription": "需要{bothArguments}为真", + "xpack.data.kueryAutocomplete.andOperatorDescription.bothArgumentsText": "两个参数都", + "xpack.data.kueryAutocomplete.equalOperatorDescription": "{equals}某一值", + "xpack.data.kueryAutocomplete.equalOperatorDescription.equalsText": "等于", + "xpack.data.kueryAutocomplete.existOperatorDescription": "以任意形式{exists}", + "xpack.data.kueryAutocomplete.existOperatorDescription.existsText": "存在", + "xpack.data.kueryAutocomplete.filterResultsDescription": "筛选包含 {fieldName} 的结果", + "xpack.data.kueryAutocomplete.greaterThanOperatorDescription": "{greaterThan}某一值", + "xpack.data.kueryAutocomplete.greaterThanOperatorDescription.greaterThanText": "大于", + "xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription": "{greaterThanOrEqualTo}某一值", + "xpack.data.kueryAutocomplete.greaterThanOrEqualOperatorDescription.greaterThanOrEqualToText": "大于或等于", + "xpack.data.kueryAutocomplete.lessThanOperatorDescription": "{lessThan}某一值", + "xpack.data.kueryAutocomplete.lessThanOperatorDescription.lessThanText": "小于", + "xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription": "{lessThanOrEqualTo}某一值", + "xpack.data.kueryAutocomplete.lessThanOrEqualOperatorDescription.lessThanOrEqualToText": "小于或等于", + "xpack.data.kueryAutocomplete.orOperatorDescription": "需要{oneOrMoreArguments}为真", + "xpack.data.kueryAutocomplete.orOperatorDescription.oneOrMoreArgumentsText": "一个或多个参数", "xpack.lens.app.docLoadingError": "加载已保存文档时出错", "xpack.lens.app.docSavingError": "保存文档时出错", "xpack.lens.app.indexPatternLoadingError": "加载索引模式时出错", From bdb08aebb2c34a1ae7c3b25ffd75e6fbacd659d2 Mon Sep 17 00:00:00 2001 From: Aleh Zasypkin <aleh.zasypkin@gmail.com> Date: Tue, 4 Feb 2020 17:18:21 +0100 Subject: [PATCH 39/60] Do not treat HTTP 500 error as possible reason for the expired/missing tokens. (#52631) --- .../authentication/providers/kerberos.test.ts | 38 ---------- .../authentication/providers/saml.test.ts | 76 ------------------- .../authentication/providers/token.test.ts | 30 -------- .../server/authentication/tokens.test.ts | 4 - .../security/server/authentication/tokens.ts | 15 +--- 5 files changed, 2 insertions(+), 161 deletions(-) diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index 27105793fc966..e609afb6ae3f3 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -419,44 +419,6 @@ describe('KerberosAuthenticationProvider', () => { expect(authenticationResult.authResponseHeaders).toEqual({ 'WWW-Authenticate': 'Negotiate' }); }); - it('fails with `Negotiate` challenge if both access and refresh token documents are missing and backend supports Kerberos.', async () => { - const request = httpServerMock.createKibanaRequest({ headers: {} }); - const tokenPair = { accessToken: 'missing-token', refreshToken: 'missing-refresh-token' }; - - mockScopedClusterClient( - mockOptions.client, - sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } }) - ) - .callAsCurrentUser.withArgs('shield.authenticate') - .rejects({ - statusCode: 500, - body: { error: { reason: 'token document is missing and must be present' } }, - }); - - mockScopedClusterClient( - mockOptions.client, - sinon.match({ - headers: { authorization: `Negotiate ${Buffer.from('__fake__').toString('base64')}` }, - }) - ) - .callAsCurrentUser.withArgs('shield.authenticate') - .rejects( - ElasticsearchErrorHelpers.decorateNotAuthorizedError( - new (errors.AuthenticationException as any)('Unauthorized', { - body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, - }) - ) - ); - - mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null); - - const authenticationResult = await provider.authenticate(request, tokenPair); - - expect(authenticationResult.failed()).toBe(true); - expect(authenticationResult.error).toHaveProperty('output.statusCode', 401); - expect(authenticationResult.authResponseHeaders).toEqual({ 'WWW-Authenticate': 'Negotiate' }); - }); - it('succeeds if `authorization` contains a valid token.', async () => { const user = mockAuthenticatedUser(); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 27702f70865ea..a5d1010a1bec8 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -758,82 +758,6 @@ describe('SAMLAuthenticationProvider', () => { ); }); - it('re-capture URL for non-AJAX requests if access token document is missing.', async () => { - const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); - const state = { - username: 'user', - accessToken: 'expired-token', - refreshToken: 'expired-refresh-token', - }; - - mockScopedClusterClient( - mockOptions.client, - sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } }) - ) - .callAsCurrentUser.withArgs('shield.authenticate') - .rejects({ - statusCode: 500, - body: { error: { reason: 'token document is missing and must be present' } }, - }); - - mockOptions.tokens.refresh.withArgs(state.refreshToken).resolves(null); - - const authenticationResult = await provider.authenticate(request, state); - - sinon.assert.notCalled(mockOptions.client.callAsInternalUser); - - expect(authenticationResult.redirected()).toBe(true); - expect(authenticationResult.redirectURL).toBe( - '/mock-server-basepath/api/security/saml/capture-url-fragment' - ); - expect(authenticationResult.state).toEqual({ redirectURL: '/base-path/s/foo/some-path' }); - }); - - it('initiates SAML handshake for non-AJAX requests if access token document is missing and request path is too large.', async () => { - const request = httpServerMock.createKibanaRequest({ - path: `/s/foo/${'some-path'.repeat(10)}`, - }); - const state = { - username: 'user', - accessToken: 'expired-token', - refreshToken: 'expired-refresh-token', - }; - - mockOptions.client.callAsInternalUser.withArgs('shield.samlPrepare').resolves({ - id: 'some-request-id', - redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20', - }); - - mockScopedClusterClient( - mockOptions.client, - sinon.match({ headers: { authorization: `Bearer ${state.accessToken}` } }) - ) - .callAsCurrentUser.withArgs('shield.authenticate') - .rejects({ - statusCode: 500, - body: { error: { reason: 'token document is missing and must be present' } }, - }); - - mockOptions.tokens.refresh.withArgs(state.refreshToken).resolves(null); - - const authenticationResult = await provider.authenticate(request, state); - - sinon.assert.calledWithExactly(mockOptions.client.callAsInternalUser, 'shield.samlPrepare', { - body: { realm: 'test-realm' }, - }); - - expect(mockOptions.logger.warn).toHaveBeenCalledTimes(1); - expect(mockOptions.logger.warn).toHaveBeenCalledWith( - 'Max URL path size should not exceed 100b but it was 107b. URL is not captured.' - ); - - expect(authenticationResult.redirected()).toBe(true); - expect(authenticationResult.redirectURL).toBe( - 'https://idp-host/path/login?SAMLRequest=some%20request%20' - ); - expect(authenticationResult.state).toEqual({ requestId: 'some-request-id', redirectURL: '' }); - }); - it('re-capture URL for non-AJAX requests if refresh token is expired.', async () => { const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' }); const state = { diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index a6850dcdf8321..3d377140cb42e 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -300,36 +300,6 @@ describe('TokenAuthenticationProvider', () => { expect(authenticationResult.error).toEqual(refreshError); }); - it('redirects non-AJAX requests to /login and clears session if token document is missing', async () => { - const request = httpServerMock.createKibanaRequest({ path: '/some-path' }); - const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - - mockScopedClusterClient( - mockOptions.client, - sinon.match({ headers: { authorization: `Bearer ${tokenPair.accessToken}` } }) - ) - .callAsCurrentUser.withArgs('shield.authenticate') - .rejects({ - statusCode: 500, - body: { error: { reason: 'token document is missing and must be present' } }, - }); - - mockOptions.tokens.refresh.withArgs(tokenPair.refreshToken).resolves(null); - - const authenticationResult = await provider.authenticate(request, tokenPair); - - sinon.assert.calledOnce(mockOptions.tokens.refresh); - - expect(request.headers).not.toHaveProperty('authorization'); - expect(authenticationResult.redirected()).toBe(true); - expect(authenticationResult.redirectURL).toBe( - '/base-path/login?next=%2Fbase-path%2Fsome-path' - ); - expect(authenticationResult.user).toBeUndefined(); - expect(authenticationResult.state).toEqual(null); - expect(authenticationResult.error).toBeUndefined(); - }); - it('redirects non-AJAX requests to /login and clears session if token cannot be refreshed', async () => { const request = httpServerMock.createKibanaRequest({ path: '/some-path' }); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; diff --git a/x-pack/plugins/security/server/authentication/tokens.test.ts b/x-pack/plugins/security/server/authentication/tokens.test.ts index 8d15ea69ae392..82f29310c04c0 100644 --- a/x-pack/plugins/security/server/authentication/tokens.test.ts +++ b/x-pack/plugins/security/server/authentication/tokens.test.ts @@ -41,10 +41,6 @@ describe('Tokens', () => { { statusCode: 401 }, ElasticsearchErrorHelpers.decorateNotAuthorizedError(new Error()), new errors.AuthenticationException(), - { - statusCode: 500, - body: { error: { reason: 'token document is missing and must be present' } }, - }, ]; for (const error of expirationErrors) { expect(Tokens.isAccessTokenExpiredError(error)).toBe(true); diff --git a/x-pack/plugins/security/server/authentication/tokens.ts b/x-pack/plugins/security/server/authentication/tokens.ts index 2906f28912d5b..ea7b5d5a9ff38 100644 --- a/x-pack/plugins/security/server/authentication/tokens.ts +++ b/x-pack/plugins/security/server/authentication/tokens.ts @@ -150,21 +150,10 @@ export class Tokens { /** * Tries to determine whether specified error that occurred while trying to authenticate request * using access token happened because access token is expired. We treat all `401 Unauthorized` - * as such. Another use case that we should temporarily support (until elastic/elasticsearch#38866 - * is fixed) is when token document has been removed and ES responds with `500 Internal Server Error`. + * as such. * @param err Error returned from Elasticsearch. */ public static isAccessTokenExpiredError(err?: any) { - const errorStatusCode = getErrorStatusCode(err); - return ( - errorStatusCode === 401 || - (errorStatusCode === 500 && - !!( - err && - err.body && - err.body.error && - err.body.error.reason === 'token document is missing and must be present' - )) - ); + return getErrorStatusCode(err) === 401; } } From 9a53aeadc10b4a692ca85e0a1f2e88ac1b34d9cb Mon Sep 17 00:00:00 2001 From: Oliver Gupte <ogupte@users.noreply.github.com> Date: Tue, 4 Feb 2020 08:35:46 -0800 Subject: [PATCH 40/60] Closes #56575. Removes red border. (#56576) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../apm/public/components/app/ServiceMap/Popover/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index e8e37cfdfb1f0..8e49a02909aab 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -38,8 +38,7 @@ export function Popover({ focusedServiceName }: PopoverProps) { background: 'transparent', height: renderedHeight, position: 'absolute', - width: renderedWidth, - border: '3px dotted red' + width: renderedWidth }; const trigger = <div style={triggerStyle} />; const zoom = cy?.zoom() ?? 1; From 4c8049f979374ea47a4d849f76722d4502ddc175 Mon Sep 17 00:00:00 2001 From: Aaron Caldwell <aaron.caldwell@elastic.co> Date: Tue, 4 Feb 2020 09:52:57 -0700 Subject: [PATCH 41/60] [Maps] Set filterByMapBounds to default constant if no arg passed (#55068) * Set filterByMapBounds to default constant if no arg passed * toggle accidentally refers to props instead of state for filterByBounds. update to state * Review feedback. Remove extra filterByMapBounds Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../layers/sources/es_search_source/create_source_editor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index ad55a279f9cd7..da6248099c9c1 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -227,7 +227,7 @@ export class CreateSourceEditor extends Component { label={i18n.translate('xpack.maps.source.esSearch.extentFilterLabel', { defaultMessage: `Dynamically filter for data in the visible map area`, })} - checked={this.props.filterByMapBounds} + checked={this.state.filterByMapBounds} onChange={this.onFilterByMapBoundsChange} /> </EuiFormRow> From 22e66a1484a258c4150ca23c2b44305a89d80f0e Mon Sep 17 00:00:00 2001 From: Joe Reuter <johannes.reuter@elastic.co> Date: Tue, 4 Feb 2020 18:15:48 +0100 Subject: [PATCH 42/60] Rollup TSVB integration: Add test and fix warning text (#56639) --- .../public/components/index_pattern.js | 2 + .../public/components/yes_no.js | 4 +- .../page_objects/visual_builder_page.ts | 13 +++ .../components/rollup_prompt/rollup_prompt.js | 2 +- .../test/functional/apps/rollup_job/index.js | 1 + .../test/functional/apps/rollup_job/tsvb.js | 105 ++++++++++++++++++ 6 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 x-pack/test/functional/apps/rollup_job/tsvb.js diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js index de8469adfb8a7..f6530eb22332a 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/index_pattern.js @@ -206,6 +206,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model })} > <EuiFieldText + data-test-subj="metricsIndexPatternInterval" isInvalid={!intervalValidation.isValid} disabled={disabled || isEntireTimeRangeActive(model, isTimeSeries)} onChange={handleTextChange(intervalName, AUTO_INTERVAL)} @@ -222,6 +223,7 @@ export const IndexPattern = ({ fields, prefix, onChange, disabled, model: _model })} > <YesNo + data-test-subj="metricsDropLastBucket" value={model[dropBucketName]} name={dropBucketName} onChange={onChange} diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js index fa9b2aa6e45c1..59c0366e3b980 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/yes_no.js @@ -24,7 +24,7 @@ import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; export function YesNo(props) { - const { name, value, disabled } = props; + const { name, value, disabled, 'data-test-subj': dataTestSubj } = props; const handleChange = value => { const { name } = props; return () => { @@ -38,6 +38,7 @@ export function YesNo(props) { <div> <EuiRadio id={htmlId('yes')} + data-test-subj={`${dataTestSubj}-yes`} label={ <FormattedMessage id="visTypeTimeseries.yesButtonLabel" @@ -55,6 +56,7 @@ export function YesNo(props) {   <EuiRadio id={htmlId('no')} + data-test-subj={`${dataTestSubj}-no`} label={ <FormattedMessage id="visTypeTimeseries.noButtonLabel" diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 2fa59d5fd89d8..ee0cafb51d455 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -414,6 +414,19 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await PageObjects.header.waitUntilLoadingHasFinished(); } + public async setIntervalValue(value: string) { + const el = await testSubjects.find('metricsIndexPatternInterval'); + await el.clearValue(); + await el.type(value); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + + public async setDropLastBucket(value: boolean) { + const option = await testSubjects.find(`metricsDropLastBucket-${value ? 'yes' : 'no'}`); + (await option.findByCssSelector('label')).click(); + await PageObjects.header.waitUntilLoadingHasFinished(); + } + public async selectIndexPatternTimeField(timeField: string) { await retry.try(async () => { await comboBox.clearInputField('metricsIndexPatternFieldsSelect'); diff --git a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js index 7a944c5e9a5c0..42c950f0b0d74 100644 --- a/x-pack/legacy/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js +++ b/x-pack/legacy/plugins/rollup/public/index_pattern_creation/components/rollup_prompt/rollup_prompt.js @@ -13,7 +13,7 @@ export const RollupPrompt = () => ( <p> Kibana's support for rollup index patterns is in beta. You might encounter issues using these patterns in saved searches, visualizations, and dashboards. They are not supported in - advanced features, such as TSVB, Timelion, and Machine Learning. + some advanced features, such as Timelion, and Machine Learning. </p> <p> You can match a rollup index pattern against one rollup index and zero or more regular diff --git a/x-pack/test/functional/apps/rollup_job/index.js b/x-pack/test/functional/apps/rollup_job/index.js index 146cc4e8dbdf0..055b239058eac 100644 --- a/x-pack/test/functional/apps/rollup_job/index.js +++ b/x-pack/test/functional/apps/rollup_job/index.js @@ -10,5 +10,6 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./rollup_jobs')); loadTestFile(require.resolve('./hybrid_index_pattern')); + loadTestFile(require.resolve('./tsvb')); }); } diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js new file mode 100644 index 0000000000000..f3782c4c91644 --- /dev/null +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -0,0 +1,105 @@ +/* + * 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 datemath from '@elastic/datemath'; +import expect from '@kbn/expect'; +import mockRolledUpData from './hybrid_index_helper'; + +export default function({ getService, getPageObjects }) { + const es = getService('legacyEs'); + const esArchiver = getService('esArchiver'); + const retry = getService('retry'); + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects([ + 'common', + 'settings', + 'visualize', + 'visualBuilder', + 'timePicker', + ]); + + describe('tsvb integration', function() { + //Since rollups can only be created once with the same name (even if you delete it), + //we add the Date.now() to avoid name collision if you run the tests locally back to back. + const rollupJobName = `tsvb-test-rollup-job-${Date.now()}`; + const rollupSourceIndexName = 'rollup-source-data'; + const rollupTargetIndexName = `rollup-target-data`; + const now = new Date(); + const pastDates = [ + datemath.parse('now-1m', { forceNow: now }), + datemath.parse('now-2m', { forceNow: now }), + datemath.parse('now-3m', { forceNow: now }), + ]; + + before(async () => { + // load visualize to have an index pattern ready, otherwise visualize will redirect + await esArchiver.load('visualize/default'); + }); + + it('create rollup tsvb', async () => { + //Create data for rollup job so it doesn't fail + await es.index({ + index: rollupSourceIndexName, + body: { + '@timestamp': new Date().toISOString(), + }, + }); + + await retry.try(async () => { + //Create a rollup for kibana to recognize + await es.transport.request({ + path: `/_rollup/job/${rollupJobName}`, + method: 'PUT', + body: { + index_pattern: rollupSourceIndexName, + rollup_index: rollupTargetIndexName, + cron: '*/10 * * * * ?', + groups: { + date_histogram: { + fixed_interval: '1000ms', + field: '@timestamp', + time_zone: 'UTC', + }, + }, + timeout: '20s', + page_size: 1000, + }, + }); + }); + + await pastDates.map(async day => { + await es.index(mockRolledUpData(rollupJobName, rollupTargetIndexName, day)); + }); + + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisualBuilder(); + await PageObjects.visualBuilder.checkVisualBuilderIsPresent(); + await PageObjects.timePicker.openQuickSelectTimeMenu(); + await testSubjects.click('superDatePickerCommonlyUsed_Last_24 hours'); + await PageObjects.visualBuilder.clickMetric(); + await PageObjects.visualBuilder.checkMetricTabIsPresent(); + await PageObjects.visualBuilder.clickPanelOptions('metric'); + await PageObjects.visualBuilder.setIndexPatternValue(rollupTargetIndexName); + await PageObjects.visualBuilder.setIntervalValue('1d'); + await PageObjects.visualBuilder.setDropLastBucket(false); + await PageObjects.common.sleep(3000); + const newValue = await PageObjects.visualBuilder.getMetricValue(); + expect(newValue).to.eql('3'); + }); + + after(async () => { + // Delete the rollup job. + await es.transport.request({ + path: `/_rollup/job/${rollupJobName}`, + method: 'DELETE', + }); + + await es.indices.delete({ index: rollupTargetIndexName }); + await es.indices.delete({ index: rollupSourceIndexName }); + await esArchiver.load('empty_kibana'); + }); + }); +} From e510484ddf880a04924921a18b0efea32427d43f Mon Sep 17 00:00:00 2001 From: Lukas Olson <olson.lukas@gmail.com> Date: Tue, 4 Feb 2020 10:58:43 -0700 Subject: [PATCH 43/60] [Search service] Move loadingCount to sync search strategy (#56335) * Move loadingCount to search strategy * Only register observable once Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../create_app_mount_context_search.test.ts | 50 ++++++++----------- .../search/create_app_mount_context_search.ts | 19 ++----- .../public/search/es_client/get_es_client.ts | 6 ++- .../data/public/search/search_service.ts | 10 +--- .../public/search/sync_search_strategy.ts | 17 ++++--- 5 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/plugins/data/public/search/create_app_mount_context_search.test.ts b/src/plugins/data/public/search/create_app_mount_context_search.test.ts index 15b85ee270bed..fa7cdbcda3082 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.test.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.test.ts @@ -18,35 +18,32 @@ */ import { createAppMountSearchContext } from './create_app_mount_context_search'; -import { from, BehaviorSubject } from 'rxjs'; +import { from } from 'rxjs'; describe('Create app mount search context', () => { it('Returns search fn when there are no strategies', () => { - const context = createAppMountSearchContext({}, new BehaviorSubject(0)); + const context = createAppMountSearchContext({}); expect(context.search).toBeDefined(); }); it(`Search throws an error when the strategy doesn't exist`, () => { - const context = createAppMountSearchContext({}, new BehaviorSubject(0)); + const context = createAppMountSearchContext({}); expect(() => context.search({}, {}, 'noexist').toPromise()).toThrowErrorMatchingInlineSnapshot( `"Strategy with name noexist does not exist"` ); }); it(`Search fn is called on appropriate strategy name`, done => { - const context = createAppMountSearchContext( - { - mysearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 98 })), - }), - anothersearch: search => - Promise.resolve({ - search: () => from(Promise.resolve({ percentComplete: 0 })), - }), - }, - new BehaviorSubject(0) - ); + const context = createAppMountSearchContext({ + mysearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 98 })), + }), + anothersearch: search => + Promise.resolve({ + search: () => from(Promise.resolve({ percentComplete: 0 })), + }), + }); context.search({}, {}, 'mysearch').subscribe(response => { expect(response).toEqual({ percentComplete: 98 }); @@ -55,19 +52,16 @@ describe('Create app mount search context', () => { }); it(`Search fn is called with the passed in request object`, done => { - const context = createAppMountSearchContext( - { - mysearch: search => { - return Promise.resolve({ - search: request => { - expect(request).toEqual({ greeting: 'hi' }); - return from(Promise.resolve({})); - }, - }); - }, + const context = createAppMountSearchContext({ + mysearch: search => { + return Promise.resolve({ + search: request => { + expect(request).toEqual({ greeting: 'hi' }); + return from(Promise.resolve({})); + }, + }); }, - new BehaviorSubject(0) - ); + }); context.search({ greeting: 'hi' } as any, {}, 'mysearch').subscribe( response => {}, () => {}, diff --git a/src/plugins/data/public/search/create_app_mount_context_search.ts b/src/plugins/data/public/search/create_app_mount_context_search.ts index f480b8f3e042e..7a617e0bab837 100644 --- a/src/plugins/data/public/search/create_app_mount_context_search.ts +++ b/src/plugins/data/public/search/create_app_mount_context_search.ts @@ -17,8 +17,8 @@ * under the License. */ -import { mergeMap, tap } from 'rxjs/operators'; -import { from, BehaviorSubject } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; +import { from } from 'rxjs'; import { ISearchAppMountContext } from './i_search_app_mount_context'; import { ISearchGeneric } from './i_search'; import { @@ -30,8 +30,7 @@ import { TStrategyTypes } from './strategy_types'; import { DEFAULT_SEARCH_STRATEGY } from '../../common/search'; export const createAppMountSearchContext = ( - searchStrategies: TSearchStrategiesMap, - loadingCount$: BehaviorSubject<number> + searchStrategies: TSearchStrategiesMap ): ISearchAppMountContext => { const getSearchStrategy = <K extends TStrategyTypes = typeof DEFAULT_SEARCH_STRATEGY>( strategyName?: K @@ -47,17 +46,7 @@ export const createAppMountSearchContext = ( const search: ISearchGeneric = (request, options, strategyName) => { const strategyPromise = getSearchStrategy(strategyName); - return from(strategyPromise).pipe( - mergeMap(strategy => { - loadingCount$.next(loadingCount$.getValue() + 1); - return strategy.search(request, options).pipe( - tap( - error => loadingCount$.next(loadingCount$.getValue() - 1), - complete => loadingCount$.next(loadingCount$.getValue() - 1) - ) - ); - }) - ); + return from(strategyPromise).pipe(mergeMap(strategy => strategy.search(request, options))); }; return { search }; diff --git a/src/plugins/data/public/search/es_client/get_es_client.ts b/src/plugins/data/public/search/es_client/get_es_client.ts index 6c271643ba012..93d9d24920271 100644 --- a/src/plugins/data/public/search/es_client/get_es_client.ts +++ b/src/plugins/data/public/search/es_client/get_es_client.ts @@ -25,8 +25,7 @@ import { BehaviorSubject } from 'rxjs'; export function getEsClient( injectedMetadata: CoreStart['injectedMetadata'], http: CoreStart['http'], - packageInfo: PackageInfo, - loadingCount$: BehaviorSubject<number> + packageInfo: PackageInfo ) { const esRequestTimeout = injectedMetadata.getInjectedVar('esRequestTimeout') as number; const esApiVersion = injectedMetadata.getInjectedVar('esApiVersion') as string; @@ -39,6 +38,9 @@ export function getEsClient( apiVersion: esApiVersion, }); + const loadingCount$ = new BehaviorSubject(0); + http.addLoadingCountSource(loadingCount$); + return { search: wrapEsClientMethod(client, 'search', loadingCount$), msearch: wrapEsClientMethod(client, 'msearch', loadingCount$), diff --git a/src/plugins/data/public/search/search_service.ts b/src/plugins/data/public/search/search_service.ts index dad2b3d078aa0..dd67c04f1d7bd 100644 --- a/src/plugins/data/public/search/search_service.ts +++ b/src/plugins/data/public/search/search_service.ts @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ -import { BehaviorSubject } from 'rxjs'; import { Plugin, CoreSetup, @@ -80,22 +79,17 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> { private contextContainer?: IContextContainer<TSearchStrategyProvider<any>>; private esClient?: LegacyApiCaller; private search?: ISearchGeneric; - private readonly loadingCount$ = new BehaviorSubject(0); constructor(private initializerContext: PluginInitializerContext) {} public setup(core: CoreSetup, packageInfo: PackageInfo): ISearchSetup { - core.http.addLoadingCountSource(this.loadingCount$); - const search = (this.search = createAppMountSearchContext( - this.searchStrategies, - this.loadingCount$ - ).search); + const search = (this.search = createAppMountSearchContext(this.searchStrategies).search); core.application.registerMountContext<'search'>('search', () => { return { search }; }); this.contextContainer = core.context.createContextContainer(); - this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo, this.loadingCount$); + this.esClient = getEsClient(core.injectedMetadata, core.http, packageInfo); const registerSearchStrategyProvider: TRegisterSearchStrategyProvider = < T extends TStrategyTypes diff --git a/src/plugins/data/public/search/sync_search_strategy.ts b/src/plugins/data/public/search/sync_search_strategy.ts index 65fe10f39aaa0..b895a177d8608 100644 --- a/src/plugins/data/public/search/sync_search_strategy.ts +++ b/src/plugins/data/public/search/sync_search_strategy.ts @@ -17,7 +17,7 @@ * under the License. */ -import { from } from 'rxjs'; +import { BehaviorSubject, from } from 'rxjs'; import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../common/search'; import { ISearchContext } from './i_search_context'; import { ISearch, ISearchOptions } from './i_search'; @@ -31,11 +31,16 @@ export interface ISyncSearchRequest extends IKibanaSearchRequest { export const syncSearchStrategyProvider: TSearchStrategyProvider<typeof SYNC_SEARCH_STRATEGY> = ( context: ISearchContext -) => { +): ISearchStrategy<typeof SYNC_SEARCH_STRATEGY> => { + const loadingCount$ = new BehaviorSubject(0); + context.core.http.addLoadingCountSource(loadingCount$); + const search: ISearch<typeof SYNC_SEARCH_STRATEGY> = ( request: ISyncSearchRequest, options: ISearchOptions = {} ) => { + loadingCount$.next(loadingCount$.getValue() + 1); + const response: Promise<IKibanaSearchResponse> = context.core.http.fetch({ path: `/internal/search/${request.serverStrategy}`, method: 'POST', @@ -43,12 +48,10 @@ export const syncSearchStrategyProvider: TSearchStrategyProvider<typeof SYNC_SEA signal: options.signal, }); - return from(response); - }; + response.then(() => loadingCount$.next(loadingCount$.getValue() - 1)); - const strategy: ISearchStrategy<typeof SYNC_SEARCH_STRATEGY> = { - search, + return from(response); }; - return strategy; + return { search }; }; From 885b79e76fafba23def567668b5e0b958e895c47 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Tue, 4 Feb 2020 13:29:18 -0500 Subject: [PATCH 44/60] fix open close signal on detail page (#56757) --- .../pages/detection_engine/components/signals/index.tsx | 4 +++- .../detection_engine/routes/__mocks__/request_responses.ts | 2 +- .../routes/signals/open_close_signals_route.ts | 6 +++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index e65adcf3a6920..7eb8e07ada762 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -129,7 +129,9 @@ const SignalsTableComponent: React.FC<SignalsTableComponentProps> = ({ dataProviders: [], indexPattern: indexPatterns, browserFields, - filters: globalFilters, + filters: isEmpty(defaultFilters) + ? globalFilters + : [...(defaultFilters ?? []), ...globalFilters], kqlQuery: globalQuery, kqlMode: globalQuery.language, start: from, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 6a42aed123fa3..19c4279e06b03 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -84,7 +84,7 @@ export const typicalSetStatusSignalByIdsPayload = (): Partial<SignalsStatusRestP }); export const typicalSetStatusSignalByQueryPayload = (): Partial<SignalsStatusRestParams> => ({ - query: { range: { '@timestamp': { gte: 'now-2M', lte: 'now/M' } } }, + query: { bool: { filter: { range: { '@timestamp': { gte: 'now-2M', lte: 'now/M' } } } } }, status: 'closed', }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index 7c49a1942ee91..4755869c3d908 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -33,7 +33,11 @@ export const setSignalsStatusRouteDef = (server: ServerFacade): Hapi.ServerRoute queryObject = { ids: { values: signalIds } }; } if (query) { - queryObject = query; + queryObject = { + bool: { + filter: query, + }, + }; } try { return callWithRequest(request, 'updateByQuery', { From b92e8e264c91a9c8eb8cb52fd7c414d31d4ccf83 Mon Sep 17 00:00:00 2001 From: Caroline Horn <549577+cchaos@users.noreply.github.com> Date: Tue, 4 Feb 2020 13:42:27 -0500 Subject: [PATCH 45/60] Fix: Filter pill base coloring (#56761) --- src/plugins/data/public/ui/filter_bar/filter_view/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx index dd12789d15a9d..ed33afeca69c2 100644 --- a/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx +++ b/src/plugins/data/public/ui/filter_bar/filter_view/index.tsx @@ -57,6 +57,7 @@ export const FilterView: FC<Props> = ({ return ( <EuiBadge title={title} + color="hollow" iconType="cross" iconSide="right" closeButtonProps={{ From b458b3b35239a3c1366045982d34cd3b44fca81e Mon Sep 17 00:00:00 2001 From: Yuliia Naumenko <jo.naumenko@gmail.com> Date: Tue, 4 Feb 2020 11:05:30 -0800 Subject: [PATCH 46/60] Implement UI for Create Alert form (#55232) * Updated Alert ui model, fixed form validation and small issues * Fixed error messages and validation for action params forms * Fixed typecheck error * Moved alert add/edit common fields to alert form * Fixed type checks * Refactored alert add flyout by splitting it to the form and flyout components, added some unit tests * Refactored connector add/edit flyouts and created add connector modal * Refactored add/edit flyout tests * Fixed test * Removed orig files * Removed orig file * Added unit tests for add connector modal dialog * Action Groups idea of implementation * Removed action group tabs and set only first action group as default (temporary till design will be ready for support multiple groups) * Added missing unit tests * Changed design of the email params form * Fixed actions params forms according to latest mockups * Fixed options list for available actions connectors * Fixed modal dialog update on action delete * fixed build fail * Added functionality for action types with Message field to Add variables * Added alertReducer unit tests * Added create alert functional test * Added types for Params * Some design fixes * alerts empty prompt * Fixed failing app on save alert and added possibility to hide Change trigger button * Fixed type check issues * Added connector config types * fixed type check * Fixed merge issues * Fixed type checks * Fixed functional tests and error expression message * Fixed jest tests * Review changes Co-authored-by: dave.snider@gmail.com <dave.snider@gmail.com> --- .../np_ready/public/application/app.tsx | 2 +- .../builtin_action_types/email.test.tsx | 58 +- .../components/builtin_action_types/email.tsx | 307 ++++--- .../builtin_action_types/es_index.test.tsx | 19 +- .../builtin_action_types/es_index.tsx | 35 +- .../builtin_action_types/pagerduty.test.tsx | 36 +- .../builtin_action_types/pagerduty.tsx | 82 +- .../builtin_action_types/server_log.test.tsx | 26 +- .../builtin_action_types/server_log.tsx | 90 +- .../builtin_action_types/slack.test.tsx | 22 +- .../components/builtin_action_types/slack.tsx | 105 ++- .../components/builtin_action_types/types.ts | 130 +++ .../builtin_action_types/webhook.test.tsx | 22 +- .../builtin_action_types/webhook.tsx | 28 +- .../components/builtin_alert_types/index.ts | 2 +- .../threshold/expression.tsx | 153 +--- .../builtin_alert_types/threshold/index.ts | 103 +++ .../threshold/visualization.tsx | 5 +- .../application/constants/action_groups.ts | 11 - .../application/context/alerts_context.tsx | 5 +- .../public/application/lib/alert_api.test.ts | 10 +- .../action_connector_form/_index.scss | 3 + .../action_connector_form.test.tsx | 63 +- .../action_connector_form.tsx | 327 +++---- .../action_type_menu.tsx | 40 +- .../connector_add_flyout.test.tsx | 58 +- .../connector_add_flyout.tsx | 131 ++- .../connector_add_modal.test.tsx | 102 +++ .../connector_add_modal.tsx | 168 ++++ .../connector_edit_flyout.test.tsx | 2 +- .../connector_edit_flyout.tsx | 120 ++- .../connector_reducer.ts | 13 +- .../components/actions_connectors_list.tsx | 7 +- .../sections/alert_add/alert_add.test.tsx | 117 +++ .../sections/alert_add/alert_add.tsx | 759 ++-------------- .../sections/alert_add/alert_form.test.tsx | 200 ++++ .../sections/alert_add/alert_form.tsx | 853 ++++++++++++++++++ .../sections/alert_add/alert_reducer.test.ts | 163 ++++ .../sections/alert_add/alert_reducer.ts | 18 + .../components/alert_details.test.tsx | 32 + .../components/alerts_list.test.tsx | 12 +- .../alerts_list/components/alerts_list.tsx | 230 +++-- .../np_ready/public/types.ts | 25 +- .../triggers_actions_ui/public/index.scss | 3 + .../apps/triggers_actions_ui/alerts.ts | 90 +- .../apps/triggers_actions_ui/connectors.ts | 10 +- .../page_objects/triggers_actions_ui_page.ts | 24 +- 47 files changed, 3160 insertions(+), 1661 deletions(-) create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/types.ts create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/index.ts delete mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/_index.scss create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.test.tsx create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.tsx create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.test.tsx create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.test.tsx create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.tsx create mode 100644 x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.test.ts diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx index 7d9a963c9c6b3..8bc292c58468e 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx @@ -48,7 +48,7 @@ export const App = (appDeps: AppDeps) => { ); }; -export const AppWithoutRouter = ({ sectionsRegex }: any) => { +export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => { const { capabilities } = useAppDependencies(); const canShowAlerts = hasShowAlertsCapability(capabilities); const DEFAULT_SECTION: Section = canShowAlerts ? 'alerts' : 'connectors'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx index 5c924982c3536..49a611167cf16 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionParamsProps } from '../../../types'; +import { EmailActionParams, EmailActionConnector } from './types'; const ACTION_TYPE_ID = '.email'; let actionTypeModel: ActionTypeModel; @@ -40,43 +41,15 @@ describe('connector validation', () => { name: 'email', config: { from: 'test@test.com', - port: '2323', + port: 2323, host: 'localhost', test: 'test', }, - } as ActionConnector; + } as EmailActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { from: [], - service: [], - port: [], - host: [], - user: [], - password: [], - }, - }); - - delete actionConnector.config.test; - actionConnector.config.host = 'elastic.co'; - actionConnector.config.port = 8080; - expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ - errors: { - from: [], - service: [], - port: [], - host: [], - user: [], - password: [], - }, - }); - delete actionConnector.config.host; - delete actionConnector.config.port; - actionConnector.config.service = 'testService'; - expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ - errors: { - from: [], - service: [], port: [], host: [], user: [], @@ -97,12 +70,11 @@ describe('connector validation', () => { config: { from: 'test@test.com', }, - } as ActionConnector; + } as EmailActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { from: [], - service: ['Service is required.'], port: ['Port is required.'], host: ['Host is required.'], user: [], @@ -168,14 +140,13 @@ describe('EmailActionConnectorFields renders', () => { config: { from: 'test@test.com', }, - } as ActionConnector; + } as EmailActionConnector; const wrapper = mountWithIntl( <ConnectorFields action={actionConnector} - errors={{ from: [], service: [], port: [], host: [], user: [], password: [] }} + errors={{ from: [], port: [], host: [], user: [], password: [] }} editActionConfig={() => {}} editActionSecrets={() => {}} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="emailFromInput"]').length > 0).toBeTruthy(); @@ -198,19 +169,22 @@ describe('EmailParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<EmailActionParams> + >; const actionParams = { + cc: [], + bcc: [], to: ['test@test.com'], subject: 'test', message: 'test message', }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} - errors={{}} + actionParams={actionParams} + errors={{ to: [], cc: [], bcc: [], subject: [], message: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="toEmailAddressInput"]').length > 0).toBeTruthy(); @@ -220,8 +194,6 @@ describe('EmailParamsFields renders', () => { .first() .prop('selectedOptions') ).toStrictEqual([{ label: 'test@test.com' }]); - expect(wrapper.find('[data-test-subj="ccEmailAddressInput"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="bccEmailAddressInput"]').length > 0).toBeTruthy(); expect(wrapper.find('[data-test-subj="emailSubjectInput"]').length > 0).toBeTruthy(); expect(wrapper.find('[data-test-subj="emailMessageInput"]').length > 0).toBeTruthy(); }); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx index a6750ccf96deb..f82b2c8c88ada 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx @@ -3,7 +3,8 @@ * 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 } from 'react'; +import React, { Fragment, useState, useEffect } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFieldText, EuiFlexItem, @@ -12,17 +13,22 @@ import { EuiFieldPassword, EuiComboBox, EuiTextArea, + EuiButtonEmpty, EuiSwitch, EuiFormRow, + EuiContextMenuItem, + EuiButtonIcon, + EuiContextMenuPanel, + EuiPopover, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ActionTypeModel, ActionConnectorFieldsProps, - ActionConnector, ValidationResult, ActionParamsProps, } from '../../../types'; +import { EmailActionParams, EmailActionConnector } from './types'; export function getActionType(): ActionTypeModel { const mailformat = /^[^@\s]+@[^@\s]+$/; @@ -35,11 +41,16 @@ export function getActionType(): ActionTypeModel { defaultMessage: 'Send email from your server.', } ), - validateConnector: (action: ActionConnector): ValidationResult => { + actionTypeTitle: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle', + { + defaultMessage: 'Send to email', + } + ), + validateConnector: (action: EmailActionConnector): ValidationResult => { const validationResult = { errors: {} }; const errors = { from: new Array<string>(), - service: new Array<string>(), port: new Array<string>(), host: new Array<string>(), user: new Array<string>(), @@ -66,7 +77,7 @@ export function getActionType(): ActionTypeModel { ) ); } - if (!action.config.port && !action.config.service) { + if (!action.config.port) { errors.port.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText', @@ -76,17 +87,7 @@ export function getActionType(): ActionTypeModel { ) ); } - if (!action.config.service && (!action.config.port || !action.config.host)) { - errors.service.push( - i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText', - { - defaultMessage: 'Service is required.', - } - ) - ); - } - if (!action.config.host && !action.config.service) { + if (!action.config.host) { errors.host.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText', @@ -118,7 +119,7 @@ export function getActionType(): ActionTypeModel { } return validationResult; }, - validateParams: (actionParams: any): ValidationResult => { + validateParams: (actionParams: EmailActionParams): ValidationResult => { const validationResult = { errors: {} }; const errors = { to: new Array<string>(), @@ -143,7 +144,7 @@ export function getActionType(): ActionTypeModel { errors.cc.push(errorText); errors.bcc.push(errorText); } - if (!actionParams.message) { + if (!actionParams.message?.length) { errors.message.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText', @@ -153,7 +154,7 @@ export function getActionType(): ActionTypeModel { ) ); } - if (!actionParams.subject) { + if (!actionParams.subject?.length) { errors.subject.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText', @@ -170,12 +171,9 @@ export function getActionType(): ActionTypeModel { }; } -const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({ - action, - editActionConfig, - editActionSecrets, - errors, -}) => { +const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps< + EmailActionConnector +>> = ({ action, editActionConfig, editActionSecrets, errors }) => { const { from, host, port, secure } = action.config; const { user, password } = action.secrets; @@ -265,7 +263,7 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP isInvalid={errors.port.length > 0 && port !== undefined} fullWidth name="port" - value={port || ''} + value={port} data-test-subj="emailPortInput" onChange={e => { editActionConfig('port', parseInt(e.target.value, 10)); @@ -365,34 +363,79 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP ); }; -const EmailParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, +const EmailParamsFields: React.FunctionComponent<ActionParamsProps<EmailActionParams>> = ({ + actionParams, editAction, index, errors, - hasErrors, + messageVariables, + defaultMessage, }) => { - const { to, cc, bcc, subject, message } = action; + const { to, cc, bcc, subject, message } = actionParams; const toOptions = to ? to.map((label: string) => ({ label })) : []; const ccOptions = cc ? cc.map((label: string) => ({ label })) : []; const bccOptions = bcc ? bcc.map((label: string) => ({ label })) : []; + const [addCC, setAddCC] = useState<boolean>(false); + const [addBCC, setAddBCC] = useState<boolean>(false); + const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState<boolean>(false); + useEffect(() => { + if (defaultMessage && defaultMessage.length > 0) { + editAction('message', defaultMessage, index); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const messageVariablesItems = messageVariables?.map((variable: string) => ( + <EuiContextMenuItem + key={variable} + icon="empty" + onClick={() => { + editAction('message', (message ?? '').concat(` {{${variable}}}`), index); + setIsVariablesPopoverOpen(false); + }} + > + {`{{${variable}}}`} + </EuiContextMenuItem> + )); return ( <Fragment> <EuiFormRow fullWidth error={errors.to} - isInvalid={hasErrors && to !== undefined} + isInvalid={errors.to.length > 0 && to !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientTextFieldLabel', { - defaultMessage: 'To', + defaultMessage: 'To:', } )} + labelAppend={ + <Fragment> + <span> + {!addCC ? ( + <EuiButtonEmpty size="xs" onClick={() => setAddCC(true)}> + <FormattedMessage + defaultMessage="Add CC" + id="xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addCcButton" + /> + </EuiButtonEmpty> + ) : null} + {!addBCC ? ( + <EuiButtonEmpty size="xs" onClick={() => setAddBCC(true)}> + <FormattedMessage + defaultMessage="{titleBcc}" + id="xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.addBccButton" + values={{ titleBcc: !addCC ? '/ BCC' : 'Add BCC' }} + /> + </EuiButtonEmpty> + ) : null} + </span> + </Fragment> + } > <EuiComboBox noSuggestions - isInvalid={hasErrors && to !== undefined} + isInvalid={errors.to.length > 0 && to !== undefined} fullWidth data-test-subj="toEmailAddressInput" selectedOptions={toOptions} @@ -418,126 +461,164 @@ const EmailParamsFields: React.FunctionComponent<ActionParamsProps> = ({ }} /> </EuiFormRow> - <EuiFormRow - fullWidth - error={errors.cc} - isInvalid={hasErrors && cc !== undefined} - label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel', - { - defaultMessage: 'Cc', - } - )} - > - <EuiComboBox - noSuggestions - isInvalid={hasErrors && cc !== undefined} + {addCC ? ( + <EuiFormRow fullWidth - data-test-subj="ccEmailAddressInput" - selectedOptions={ccOptions} - onCreateOption={(searchValue: string) => { - const newOptions = [...ccOptions, { label: searchValue }]; - editAction( - 'cc', - newOptions.map(newOption => newOption.label), - index - ); - }} - onChange={(selectedOptions: Array<{ label: string }>) => { - editAction( - 'cc', - selectedOptions.map(selectedOption => selectedOption.label), - index - ); - }} - onBlur={() => { - if (!cc) { - editAction('cc', [], index); + error={errors.cc} + isInvalid={errors.cc.length > 0 && cc !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientCopyTextFieldLabel', + { + defaultMessage: 'Cc:', } - }} - /> - </EuiFormRow> - <EuiFormRow - fullWidth - error={errors.bcc} - isInvalid={hasErrors && bcc !== undefined} - label={i18n.translate( - 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel', - { - defaultMessage: 'Bcc', - } - )} - > - <EuiComboBox - noSuggestions - isInvalid={hasErrors && bcc !== undefined} + )} + > + <EuiComboBox + noSuggestions + isInvalid={errors.cc.length > 0 && cc !== undefined} + fullWidth + data-test-subj="ccEmailAddressInput" + selectedOptions={ccOptions} + onCreateOption={(searchValue: string) => { + const newOptions = [...ccOptions, { label: searchValue }]; + editAction( + 'cc', + newOptions.map(newOption => newOption.label), + index + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editAction( + 'cc', + selectedOptions.map(selectedOption => selectedOption.label), + index + ); + }} + onBlur={() => { + if (!cc) { + editAction('cc', [], index); + } + }} + /> + </EuiFormRow> + ) : null} + {addBCC ? ( + <EuiFormRow fullWidth - data-test-subj="bccEmailAddressInput" - selectedOptions={bccOptions} - onCreateOption={(searchValue: string) => { - const newOptions = [...bccOptions, { label: searchValue }]; - editAction( - 'bcc', - newOptions.map(newOption => newOption.label), - index - ); - }} - onChange={(selectedOptions: Array<{ label: string }>) => { - editAction( - 'bcc', - selectedOptions.map(selectedOption => selectedOption.label), - index - ); - }} - onBlur={() => { - if (!bcc) { - editAction('bcc', [], index); + error={errors.bcc} + isInvalid={errors.bcc.length > 0 && bcc !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.recipientBccTextFieldLabel', + { + defaultMessage: 'Bcc:', } - }} - /> - </EuiFormRow> + )} + > + <EuiComboBox + noSuggestions + isInvalid={errors.bcc.length > 0 && bcc !== undefined} + fullWidth + data-test-subj="bccEmailAddressInput" + selectedOptions={bccOptions} + onCreateOption={(searchValue: string) => { + const newOptions = [...bccOptions, { label: searchValue }]; + editAction( + 'bcc', + newOptions.map(newOption => newOption.label), + index + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editAction( + 'bcc', + selectedOptions.map(selectedOption => selectedOption.label), + index + ); + }} + onBlur={() => { + if (!bcc) { + editAction('bcc', [], index); + } + }} + /> + </EuiFormRow> + ) : null} <EuiFormRow fullWidth error={errors.subject} - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.subject.length > 0 && subject !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.subjectTextFieldLabel', { - defaultMessage: 'Subject', + defaultMessage: 'Subject:', } )} > <EuiFieldText fullWidth - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.subject.length > 0 && subject !== undefined} name="subject" data-test-subj="emailSubjectInput" value={subject || ''} + placeholder="Text field (placeholder)" onChange={e => { editAction('subject', e.target.value, index); }} + onBlur={() => { + if (!subject) { + editAction('subject', '', index); + } + }} /> </EuiFormRow> <EuiFormRow fullWidth error={errors.message} - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.messageTextAreaFieldLabel', { - defaultMessage: 'Message', + defaultMessage: 'Message:', } )} + labelAppend={ + <EuiPopover + id="singlePanel" + button={ + <EuiButtonIcon + onClick={() => setIsVariablesPopoverOpen(true)} + iconType="indexOpen" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.addVariablePopoverButton', + { + defaultMessage: 'Add variable', + } + )} + /> + } + isOpen={isVariablesPopoverOpen} + closePopover={() => setIsVariablesPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + <EuiContextMenuPanel items={messageVariablesItems} /> + </EuiPopover> + } > <EuiTextArea fullWidth - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} value={message || ''} name="message" data-test-subj="emailMessageInput" onChange={e => { editAction('message', e.target.value, index); }} + onBlur={() => { + if (!message) { + editAction('message', '', index); + } + }} /> </EuiFormRow> </Fragment> diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx index b6a7c4d82aca4..d44787f0c4ed6 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionParamsProps } from '../../../types'; +import { IndexActionParams, EsIndexActionConnector } from './types'; const ACTION_TYPE_ID = '.index'; let actionTypeModel: ActionTypeModel; @@ -38,7 +39,7 @@ describe('index connector validation', () => { config: { index: 'test_es_index', }, - } as ActionConnector; + } as EsIndexActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: {}, @@ -87,14 +88,13 @@ describe('IndexActionConnectorFields renders', () => { config: { index: 'test', }, - } as ActionConnector; + } as EsIndexActionConnector; const wrapper = mountWithIntl( <ConnectorFields action={actionConnector} errors={{}} editActionConfig={() => {}} editActionSecrets={() => {}} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="indexInput"]').length > 0).toBeTruthy(); @@ -113,7 +113,9 @@ describe('IndexParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<IndexActionParams> + >; const actionParams = { index: 'test_index', refresh: false, @@ -121,11 +123,10 @@ describe('IndexParamsFields renders', () => { }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} - errors={{}} + actionParams={actionParams} + errors={{ index: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="indexInput"]').length > 0).toBeTruthy(); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx index aa15195cdc286..6af54d2bf15b4 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; -import { EuiFieldText, EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { EuiFieldText, EuiFormRow, EuiSwitch, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -13,6 +13,7 @@ import { ValidationResult, ActionParamsProps, } from '../../../types'; +import { IndexActionParams, EsIndexActionConnector } from './types'; export function getActionType(): ActionTypeModel { return { @@ -29,17 +30,15 @@ export function getActionType(): ActionTypeModel { }, actionConnectorFields: IndexActionConnectorFields, actionParamsFields: IndexParamsFields, - validateParams: (actionParams: any): ValidationResult => { - const validationResult = { errors: {} }; - return validationResult; + validateParams: (): ValidationResult => { + return { errors: {} }; }, }; } -const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({ - action, - editActionConfig, -}) => { +const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps< + EsIndexActionConnector +>> = ({ action, editActionConfig }) => { const { index } = action.config; return ( <EuiFormRow @@ -69,20 +68,16 @@ const IndexActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP ); }; -const IndexParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, +const IndexParamsFields: React.FunctionComponent<ActionParamsProps<IndexActionParams>> = ({ + actionParams, index, editAction, - errors, - hasErrors, }) => { - const { refresh } = action; + const { refresh } = actionParams; return ( <Fragment> <EuiFormRow fullWidth - error={errors.index} - isInvalid={hasErrors === true && action.index !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.indexFieldLabel', { @@ -92,24 +87,24 @@ const IndexParamsFields: React.FunctionComponent<ActionParamsProps> = ({ > <EuiFieldText fullWidth - isInvalid={hasErrors === true && action.index !== undefined} name="index" data-test-subj="indexInput" - value={action.index || ''} + value={actionParams.index || ''} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { editAction('index', e.target.value, index); }} onBlur={() => { - if (!action.index) { + if (!actionParams.index) { editAction('index', '', index); } }} /> </EuiFormRow> + <EuiSpacer color="subdued" size="m" /> <EuiSwitch data-test-subj="indexRefreshCheckbox" - checked={refresh} - onChange={(e: any) => { + checked={refresh || false} + onChange={e => { editAction('refresh', e.target.checked, index); }} label={ diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx index 582315c95812a..31a69f9fd94ac 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx @@ -3,11 +3,17 @@ * 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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionParamsProps } from '../../../types'; +import { + PagerDutyActionParams, + EventActionOptions, + SeverityActionOptions, + PagerDutyActionConnector, +} from './types'; const ACTION_TYPE_ID = '.pagerduty'; let actionTypeModel: ActionTypeModel; @@ -40,7 +46,7 @@ describe('pagerduty connector validation', () => { config: { apiUrl: 'http:\\test', }, - } as ActionConnector; + } as PagerDutyActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -66,7 +72,7 @@ describe('pagerduty connector validation', () => { config: { apiUrl: 'http:\\test', }, - } as ActionConnector; + } as PagerDutyActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -91,7 +97,9 @@ describe('pagerduty action params validation', () => { }; expect(actionTypeModel.validateParams(actionParams)).toEqual({ - errors: {}, + errors: { + summary: [], + }, }); }); }); @@ -113,14 +121,13 @@ describe('PagerDutyActionConnectorFields renders', () => { config: { apiUrl: 'http:\\test', }, - } as ActionConnector; + } as PagerDutyActionConnector; const wrapper = mountWithIntl( <ConnectorFields action={actionConnector} errors={{ routingKey: [] }} editActionConfig={() => {}} editActionSecrets={() => {}} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="pagerdutyApiUrlInput"]').length > 0).toBeTruthy(); @@ -140,13 +147,15 @@ describe('PagerDutyParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<PagerDutyActionParams> + >; const actionParams = { - eventAction: 'trigger', + eventAction: EventActionOptions.TRIGGER, dedupKey: 'test', summary: '2323', source: 'source', - severity: 'critical', + severity: SeverityActionOptions.CRITICAL, timestamp: '234654564654', component: 'test', group: 'group', @@ -154,11 +163,10 @@ describe('PagerDutyParamsFields renders', () => { }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} - errors={{}} + actionParams={actionParams} + errors={{ summary: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="severitySelect"]').length > 0).toBeTruthy(); @@ -174,6 +182,6 @@ describe('PagerDutyParamsFields renders', () => { expect(wrapper.find('[data-test-subj="componentInput"]').length > 0).toBeTruthy(); expect(wrapper.find('[data-test-subj="groupInput"]').length > 0).toBeTruthy(); expect(wrapper.find('[data-test-subj="sourceInput"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="pagerdutyDescriptionInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="pagerdutySummaryInput"]').length > 0).toBeTruthy(); }); }); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx index 69c7ec166df60..3c1b1d258cfe2 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx @@ -17,10 +17,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { ActionTypeModel, ActionConnectorFieldsProps, - ActionConnector, ValidationResult, ActionParamsProps, } from '../../../types'; +import { PagerDutyActionParams, PagerDutyActionConnector } from './types'; export function getActionType(): ActionTypeModel { return { @@ -32,7 +32,13 @@ export function getActionType(): ActionTypeModel { defaultMessage: 'Send an event in PagerDuty.', } ), - validateConnector: (action: ActionConnector): ValidationResult => { + actionTypeTitle: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.actionTypeTitle', + { + defaultMessage: 'Send to PagerDuty', + } + ), + validateConnector: (action: PagerDutyActionConnector): ValidationResult => { const validationResult = { errors: {} }; const errors = { routingKey: new Array<string>(), @@ -50,8 +56,22 @@ export function getActionType(): ActionTypeModel { } return validationResult; }, - validateParams: (actionParams: any): ValidationResult => { + validateParams: (actionParams: PagerDutyActionParams): ValidationResult => { const validationResult = { errors: {} }; + const errors = { + summary: new Array<string>(), + }; + validationResult.errors = errors; + if (!actionParams.summary?.length) { + errors.summary.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredSummaryText', + { + defaultMessage: 'Summary is required.', + } + ) + ); + } return validationResult; }, actionConnectorFields: PagerDutyActionConnectorFields, @@ -59,12 +79,9 @@ export function getActionType(): ActionTypeModel { }; } -const PagerDutyActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({ - errors, - action, - editActionConfig, - editActionSecrets, -}) => { +const PagerDutyActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps< + PagerDutyActionConnector +>> = ({ errors, action, editActionConfig, editActionSecrets }) => { const { apiUrl } = action.config; const { routingKey } = action.secrets; return ( @@ -137,14 +154,22 @@ const PagerDutyActionConnectorFields: React.FunctionComponent<ActionConnectorFie ); }; -const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, +const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps<PagerDutyActionParams>> = ({ + actionParams, editAction, index, errors, - hasErrors, }) => { - const { eventAction, dedupKey, summary, source, severity, timestamp, component, group } = action; + const { + eventAction, + dedupKey, + summary, + source, + severity, + timestamp, + component, + group, + } = actionParams; const severityOptions = [ { value: 'critical', text: 'Critical' }, { value: 'info', text: 'Info' }, @@ -332,7 +357,7 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps> = ({ id="pagerDutySummary" fullWidth error={errors.summary} - isInvalid={hasErrors === true && summary !== undefined} + isInvalid={errors.summary.length > 0 && summary !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.summaryFieldLabel', { @@ -342,10 +367,10 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps> = ({ > <EuiFieldText fullWidth - isInvalid={hasErrors === true && summary !== undefined} + isInvalid={errors.summary.length > 0 && summary !== undefined} name="summary" value={summary || ''} - data-test-subj="pagerdutyDescriptionInput" + data-test-subj="pagerdutySummaryInput" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { editAction('summary', e.target.value, index); }} @@ -356,6 +381,31 @@ const PagerDutyParamsFields: React.FunctionComponent<ActionParamsProps> = ({ }} /> </EuiFormRow> + <EuiFormRow + id="pagerDutyClass" + fullWidth + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.classFieldLabel', + { + defaultMessage: 'Class', + } + )} + > + <EuiFieldText + fullWidth + name="class" + value={actionParams.class || ''} + data-test-subj="pagerdutyClassInput" + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + editAction('class', e.target.value, index); + }} + onBlur={() => { + if (!actionParams.class) { + editAction('class', '', index); + } + }} + /> + </EuiFormRow> </Fragment> ); }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx index b79be4eef523b..fce8f25e51131 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionConnector, ActionParamsProps } from '../../../types'; +import { ServerLogActionParams, ServerLogLevelOptions } from './types'; const ACTION_TYPE_ID = '.server-log'; let actionTypeModel: ActionTypeModel; @@ -63,18 +64,20 @@ describe('ServerLogParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<ServerLogActionParams> + >; const actionParams = { - message: 'test message', - level: 'trace', + level: ServerLogLevelOptions.TRACE, + message: 'test', }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} + actionParams={actionParams} errors={{ message: [] }} editAction={() => {}} index={0} - hasErrors={false} + defaultMessage={'test default message'} /> ); expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); @@ -92,18 +95,19 @@ describe('ServerLogParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<ServerLogActionParams> + >; const actionParams = { message: 'test message', - level: 'info', + level: ServerLogLevelOptions.INFO, }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} + actionParams={actionParams} errors={{ message: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx index 885061aa81924..8d8045042cfc3 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx @@ -3,10 +3,19 @@ * 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 } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSelect, EuiTextArea, EuiFormRow } from '@elastic/eui'; +import { + EuiSelect, + EuiTextArea, + EuiFormRow, + EuiContextMenuItem, + EuiPopover, + EuiButtonIcon, + EuiContextMenuPanel, +} from '@elastic/eui'; import { ActionTypeModel, ValidationResult, ActionParamsProps } from '../../../types'; +import { ServerLogActionParams } from './types'; export function getActionType(): ActionTypeModel { return { @@ -18,16 +27,22 @@ export function getActionType(): ActionTypeModel { defaultMessage: 'Add a message to a Kibana log.', } ), + actionTypeTitle: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.actionTypeTitle', + { + defaultMessage: 'Send to Server log', + } + ), validateConnector: (): ValidationResult => { return { errors: {} }; }, - validateParams: (actionParams: any): ValidationResult => { + validateParams: (actionParams: ServerLogActionParams): ValidationResult => { const validationResult = { errors: {} }; const errors = { message: new Array<string>(), }; validationResult.errors = errors; - if (!actionParams.message || actionParams.message.length === 0) { + if (!actionParams.message?.length) { errors.message.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServerLogMessageText', @@ -44,14 +59,10 @@ export function getActionType(): ActionTypeModel { }; } -export const ServerLogParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, - editAction, - index, - errors, - hasErrors, -}) => { - const { message, level } = action; +export const ServerLogParamsFields: React.FunctionComponent<ActionParamsProps< + ServerLogActionParams +>> = ({ actionParams, editAction, index, errors, messageVariables, defaultMessage }) => { + const { message, level } = actionParams; const levelOptions = [ { value: 'trace', text: 'Trace' }, { value: 'debug', text: 'Debug' }, @@ -60,10 +71,27 @@ export const ServerLogParamsFields: React.FunctionComponent<ActionParamsProps> = { value: 'error', text: 'Error' }, { value: 'fatal', text: 'Fatal' }, ]; + const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState<boolean>(false); - // Set default value 'info' for level param - editAction('level', 'info', index); - + useEffect(() => { + editAction('level', 'info', index); + if (defaultMessage && defaultMessage.length > 0) { + editAction('message', defaultMessage, index); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const messageVariablesItems = messageVariables?.map((variable: string) => ( + <EuiContextMenuItem + key={variable} + icon="empty" + onClick={() => { + editAction('message', (message ?? '').concat(` {{${variable}}}`), index); + setIsVariablesPopoverOpen(false); + }} + > + {`{{${variable}}}`} + </EuiContextMenuItem> + )); return ( <Fragment> <EuiFormRow @@ -92,23 +120,51 @@ export const ServerLogParamsFields: React.FunctionComponent<ActionParamsProps> = id="loggingMessage" fullWidth error={errors.message} - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel', { defaultMessage: 'Message', } )} + labelAppend={ + <EuiPopover + id="singlePanel" + button={ + <EuiButtonIcon + onClick={() => setIsVariablesPopoverOpen(true)} + iconType="indexOpen" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.addVariablePopoverButton', + { + defaultMessage: 'Add variable', + } + )} + /> + } + isOpen={isVariablesPopoverOpen} + closePopover={() => setIsVariablesPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + <EuiContextMenuPanel items={messageVariablesItems} /> + </EuiPopover> + } > <EuiTextArea fullWidth - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} value={message || ''} name="message" data-test-subj="loggingMessageInput" onChange={e => { editAction('message', e.target.value, index); }} + onBlur={() => { + if (!message) { + editAction('message', '', index); + } + }} /> </EuiFormRow> </Fragment> diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx index 36beea4d2f2be..e4f8599d2e46e 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionParamsProps } from '../../../types'; +import { SlackActionParams, SlackActionConnector } from './types'; const ACTION_TYPE_ID = '.slack'; let actionTypeModel: ActionTypeModel; @@ -38,7 +39,7 @@ describe('slack connector validation', () => { actionTypeId: '.email', name: 'email', config: {}, - } as ActionConnector; + } as SlackActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -54,7 +55,7 @@ describe('slack connector validation', () => { actionTypeId: '.email', name: 'email', config: {}, - } as ActionConnector; + } as SlackActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -91,7 +92,7 @@ describe('SlackActionFields renders', () => { actionTypeId: '.email', name: 'email', config: {}, - } as ActionConnector; + } as SlackActionConnector; const wrapper = mountWithIntl( <ConnectorFields action={actionConnector} @@ -116,23 +117,24 @@ describe('SlackParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<SlackActionParams> + >; const actionParams = { message: 'test message', }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} + actionParams={actionParams} errors={{ message: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); - expect(wrapper.find('[data-test-subj="slackMessageTextarea"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="slackMessageTextArea"]').length > 0).toBeTruthy(); expect( wrapper - .find('[data-test-subj="slackMessageTextarea"]') + .find('[data-test-subj="slackMessageTextArea"]') .first() .prop('value') ).toStrictEqual('test message'); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx index 0ae51725169bf..916715de7ae18 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx @@ -3,25 +3,26 @@ * 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 } from 'react'; +import React, { Fragment, useState, useEffect } from 'react'; import { EuiFieldText, EuiTextArea, - EuiFlexGroup, - EuiFlexItem, EuiButtonIcon, EuiFormRow, EuiLink, + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ActionTypeModel, ActionConnectorFieldsProps, - ActionConnector, ValidationResult, ActionParamsProps, } from '../../../types'; +import { SlackActionParams, SlackActionConnector } from './types'; export function getActionType(): ActionTypeModel { return { @@ -33,7 +34,13 @@ export function getActionType(): ActionTypeModel { defaultMessage: 'Send a message to a Slack channel or user.', } ), - validateConnector: (action: ActionConnector): ValidationResult => { + actionTypeTitle: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle', + { + defaultMessage: 'Send to Slack', + } + ), + validateConnector: (action: SlackActionConnector): ValidationResult => { const validationResult = { errors: {} }; const errors = { webhookUrl: new Array<string>(), @@ -51,13 +58,13 @@ export function getActionType(): ActionTypeModel { } return validationResult; }, - validateParams: (actionParams: any): ValidationResult => { + validateParams: (actionParams: SlackActionParams): ValidationResult => { const validationResult = { errors: {} }; const errors = { message: new Array<string>(), }; validationResult.errors = errors; - if (!actionParams.message || actionParams.message.length === 0) { + if (!actionParams.message?.length) { errors.message.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText', @@ -74,11 +81,9 @@ export function getActionType(): ActionTypeModel { }; } -const SlackActionFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({ - action, - editActionSecrets, - errors, -}) => { +const SlackActionFields: React.FunctionComponent<ActionConnectorFieldsProps< + SlackActionConnector +>> = ({ action, editActionSecrets, errors }) => { const { webhookUrl } = action.secrets; return ( @@ -127,47 +132,87 @@ const SlackActionFields: React.FunctionComponent<ActionConnectorFieldsProps> = ( ); }; -const SlackParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, +const SlackParamsFields: React.FunctionComponent<ActionParamsProps<SlackActionParams>> = ({ + actionParams, editAction, index, errors, - hasErrors, + messageVariables, + defaultMessage, }) => { - const { message } = action; - + const { message } = actionParams; + const [isVariablesPopoverOpen, setIsVariablesPopoverOpen] = useState<boolean>(false); + useEffect(() => { + if (defaultMessage && defaultMessage.length > 0) { + editAction('message', defaultMessage, index); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const messageVariablesItems = messageVariables?.map((variable: string, i: number) => ( + <EuiContextMenuItem + key={variable} + data-test-subj={`variableMenuButton-${i}`} + icon="empty" + onClick={() => { + editAction('message', (message ?? '').concat(` {{${variable}}}`), index); + setIsVariablesPopoverOpen(false); + }} + > + {`{{${variable}}}`} + </EuiContextMenuItem> + )); return ( <Fragment> - <EuiFlexGroup> - <EuiFlexItem> - <EuiButtonIcon - onClick={() => window.alert('Button clicked')} - iconType="indexOpen" - aria-label="Add variable" - /> - </EuiFlexItem> - </EuiFlexGroup> <EuiFormRow id="slackMessage" fullWidth error={errors.message} - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} label={i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.messageTextAreaFieldLabel', { defaultMessage: 'Message', } )} + labelAppend={ + <EuiPopover + id="singlePanel" + button={ + <EuiButtonIcon + data-test-subj="slackAddVariableButton" + onClick={() => setIsVariablesPopoverOpen(true)} + iconType="indexOpen" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.addVariablePopoverButton', + { + defaultMessage: 'Add variable', + } + )} + /> + } + isOpen={isVariablesPopoverOpen} + closePopover={() => setIsVariablesPopoverOpen(false)} + panelPaddingSize="none" + anchorPosition="downLeft" + > + <EuiContextMenuPanel items={messageVariablesItems} /> + </EuiPopover> + } > <EuiTextArea fullWidth - isInvalid={hasErrors && message !== undefined} + isInvalid={errors.message.length > 0 && message !== undefined} name="message" - value={message} - data-test-subj="slackMessageTextarea" + value={message || ''} + data-test-subj="slackMessageTextArea" onChange={e => { editAction('message', e.target.value, index); }} + onBlur={() => { + if (!message) { + editAction('message', '', index); + } + }} /> </EuiFormRow> </Fragment> diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/types.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/types.ts new file mode 100644 index 0000000000000..45a08b2d5263a --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/types.ts @@ -0,0 +1,130 @@ +/* + * 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 { ActionConnector } from '../../../types'; + +export interface EmailActionParams { + to: string[]; + cc: string[]; + bcc: string[]; + subject: string; + message: string; +} + +export enum EventActionOptions { + TRIGGER = 'trigger', + RESOLVE = 'resolve', + ACKNOWLEDGE = 'acknowledge', +} + +export enum SeverityActionOptions { + CRITICAL = 'critical', + ERROR = 'error', + WARNING = 'warning', + INFO = 'info', +} + +export interface PagerDutyActionParams { + eventAction?: EventActionOptions; + dedupKey?: string; + summary?: string; + source?: string; + severity?: SeverityActionOptions; + timestamp?: string; + component?: string; + group?: string; + class?: string; +} + +export interface IndexActionParams { + index?: string; + refresh?: boolean; + executionTimeField?: string; + documents: string[]; +} + +export enum ServerLogLevelOptions { + TRACE = 'trace', + DEBUG = 'debug', + INFO = 'info', + WARN = 'warn', + ERROR = 'error', + FATAL = 'fatal', +} + +export interface ServerLogActionParams { + level?: ServerLogLevelOptions; + message: string; +} + +export interface SlackActionParams { + message: string; +} + +export interface WebhookActionParams { + body?: string; +} + +interface EmailConfig { + from: string; + host: string; + port: number; + secure?: boolean; +} + +interface EmailSecrets { + user: string; + password: string; +} + +export interface EmailActionConnector extends ActionConnector { + config: EmailConfig; + secrets: EmailSecrets; +} + +interface EsIndexConfig { + index?: string; +} + +export interface EsIndexActionConnector extends ActionConnector { + config: EsIndexConfig; +} + +interface PagerDutyConfig { + apiUrl?: string; +} + +interface PagerDutySecrets { + routingKey: string; +} + +export interface PagerDutyActionConnector extends ActionConnector { + config: PagerDutyConfig; + secrets: PagerDutySecrets; +} + +interface SlackSecrets { + webhookUrl: string; +} + +export interface SlackActionConnector extends ActionConnector { + secrets: SlackSecrets; +} + +interface WebhookConfig { + method: string; + url: string; + headers: Record<string, string>; +} + +interface WebhookSecrets { + user: string; + password: string; +} + +export interface WebhookActionConnector extends ActionConnector { + config: WebhookConfig; + secrets: WebhookSecrets; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx index cd342f2e19969..5e6c0404db532 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.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, { FunctionComponent } from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { TypeRegistry } from '../../type_registry'; import { registerBuiltInActionTypes } from './index'; -import { ActionTypeModel, ActionConnector } from '../../../types'; +import { ActionTypeModel, ActionParamsProps } from '../../../types'; +import { WebhookActionParams, WebhookActionConnector } from './types'; const ACTION_TYPE_ID = '.webhook'; let actionTypeModel: ActionTypeModel; @@ -41,9 +42,9 @@ describe('webhook connector validation', () => { config: { method: 'PUT', url: 'http:\\test', - headers: ['content-type: text'], + headers: { 'content-type': 'text' }, }, - } as ActionConnector; + } as WebhookActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -66,7 +67,7 @@ describe('webhook connector validation', () => { config: { method: 'PUT', }, - } as ActionConnector; + } as WebhookActionConnector; expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ errors: { @@ -109,9 +110,9 @@ describe('WebhookActionConnectorFields renders', () => { config: { method: 'PUT', url: 'http:\\test', - headers: ['content-type: text'], + headers: { 'content-type': 'text' }, }, - } as ActionConnector; + } as WebhookActionConnector; const wrapper = mountWithIntl( <ConnectorFields action={actionConnector} @@ -138,17 +139,18 @@ describe('WebhookParamsFields renders', () => { if (!actionTypeModel.actionParamsFields) { return; } - const ParamsFields = actionTypeModel.actionParamsFields; + const ParamsFields = actionTypeModel.actionParamsFields as FunctionComponent< + ActionParamsProps<WebhookActionParams> + >; const actionParams = { body: 'test message', }; const wrapper = mountWithIntl( <ParamsFields - action={actionParams} + actionParams={actionParams} errors={{ body: [] }} editAction={() => {}} index={0} - hasErrors={false} /> ); expect(wrapper.find('[data-test-subj="webhookBodyEditor"]').length > 0).toBeTruthy(); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx index 70a9a6f3d75b3..3000191218932 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx @@ -27,10 +27,10 @@ import { i18n } from '@kbn/i18n'; import { ActionTypeModel, ActionConnectorFieldsProps, - ActionConnector, ValidationResult, ActionParamsProps, } from '../../../types'; +import { WebhookActionParams, WebhookActionConnector } from './types'; const HTTP_VERBS = ['post', 'put']; @@ -44,7 +44,7 @@ export function getActionType(): ActionTypeModel { defaultMessage: 'Send a request to a web service.', } ), - validateConnector: (action: ActionConnector): ValidationResult => { + validateConnector: (action: WebhookActionConnector): ValidationResult => { const validationResult = { errors: {} }; const errors = { url: new Array<string>(), @@ -95,13 +95,13 @@ export function getActionType(): ActionTypeModel { } return validationResult; }, - validateParams: (actionParams: any): ValidationResult => { + validateParams: (actionParams: WebhookActionParams): ValidationResult => { const validationResult = { errors: {} }; const errors = { body: new Array<string>(), }; validationResult.errors = errors; - if (!actionParams.body || actionParams.body.length === 0) { + if (!actionParams.body?.length) { errors.body.push( i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText', @@ -118,12 +118,9 @@ export function getActionType(): ActionTypeModel { }; } -const WebhookActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({ - action, - editActionConfig, - editActionSecrets, - errors, -}) => { +const WebhookActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps< + WebhookActionConnector +>> = ({ action, editActionConfig, editActionSecrets, errors }) => { const [httpHeaderKey, setHttpHeaderKey] = useState<string>(''); const [httpHeaderValue, setHttpHeaderValue] = useState<string>(''); const [hasHeaders, setHasHeaders] = useState<boolean>(false); @@ -453,14 +450,13 @@ const WebhookActionConnectorFields: React.FunctionComponent<ActionConnectorField ); }; -const WebhookParamsFields: React.FunctionComponent<ActionParamsProps> = ({ - action, +const WebhookParamsFields: React.FunctionComponent<ActionParamsProps<WebhookActionParams>> = ({ + actionParams, editAction, index, errors, - hasErrors, }) => { - const { body } = action; + const { body } = actionParams; return ( <Fragment> @@ -472,13 +468,13 @@ const WebhookParamsFields: React.FunctionComponent<ActionParamsProps> = ({ defaultMessage: 'Body', } )} - isInvalid={hasErrors === true} + isInvalid={errors.body.length > 0 && body !== undefined} fullWidth error={errors.body} > <EuiCodeEditor fullWidth - isInvalid={hasErrors === true} + isInvalid={errors.body.length > 0 && body !== undefined} mode="json" width="100%" height="200px" diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts index 6c5d440e47888..436a034e06051 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getActionType as getThresholdAlertType } from './threshold/expression'; +import { getAlertType as getThresholdAlertType } from './threshold'; import { TypeRegistry } from '../../type_registry'; import { AlertTypeModel } from '../../../types'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx index 907a61677b263..73e2e9cbf9b76 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -22,7 +22,7 @@ import { EuiFormRow, EuiCallOut, } from '@elastic/eui'; -import { AlertTypeModel, Alert, ValidationResult } from '../../../../types'; +import { Alert } from '../../../../types'; import { Comparator, AggregationType, GroupByType } from './types'; import { AGGREGATION_TYPES, COMPARATORS } from './constants'; import { @@ -58,99 +58,6 @@ const expressionFieldsWithValidation = [ 'timeWindowSize', ]; -const validateAlertType = (alert: Alert): ValidationResult => { - const { - index, - timeField, - aggType, - aggField, - groupBy, - termSize, - termField, - threshold, - timeWindowSize, - } = alert.params; - const validationResult = { errors: {} }; - const errors = { - aggField: new Array<string>(), - termSize: new Array<string>(), - termField: new Array<string>(), - timeWindowSize: new Array<string>(), - threshold0: new Array<string>(), - threshold1: new Array<string>(), - index: new Array<string>(), - timeField: new Array<string>(), - }; - validationResult.errors = errors; - if (!index) { - errors.index.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText', { - defaultMessage: 'Index is required.', - }) - ); - } - if (!timeField) { - errors.timeField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText', { - defaultMessage: 'Time field is required.', - }) - ); - } - if (aggType && aggregationTypes[aggType].fieldRequired && !aggField) { - errors.aggField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText', { - defaultMessage: 'Aggregation field is required.', - }) - ); - } - if (!termSize) { - errors.termSize.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText', { - defaultMessage: 'Term size is required.', - }) - ); - } - if (groupBy && groupByTypes[groupBy].sizeRequired && !termField) { - errors.termField.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText', { - defaultMessage: 'Term field is required.', - }) - ); - } - if (!timeWindowSize) { - errors.timeWindowSize.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText', { - defaultMessage: 'Time window size is required.', - }) - ); - } - if (threshold && threshold.length > 0 && !threshold[0]) { - errors.threshold0.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text', { - defaultMessage: 'Threshold0, is required.', - }) - ); - } - if (threshold && threshold.length > 1 && !threshold[1]) { - errors.threshold1.push( - i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text', { - defaultMessage: 'Threshold1 is required.', - }) - ); - } - return validationResult; -}; - -export function getActionType(): AlertTypeModel { - return { - id: 'threshold', - name: 'Index Threshold', - iconClass: 'alert', - alertParamsExpression: IndexThresholdAlertTypeExpression, - validate: validateAlertType, - }; -} - export const aggregationTypes: { [key: string]: AggregationType } = { count: { text: 'count()', @@ -275,7 +182,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = setAlertParams, setAlertProperty, errors, - hasErrors, }) => { const { http } = useAppDependencies(); @@ -307,7 +213,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = const [indexPopoverOpen, setIndexPopoverOpen] = useState(false); const [indexPatterns, setIndexPatterns] = useState([]); const [esFields, setEsFields] = useState<Record<string, any>>([]); - const [indexOptions, setIndexOptions] = useState<IOption[]>([]); + const [indexOptions, setIndexOptions] = useState<EuiComboBoxOptionProps[]>([]); const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); const [isIndiciesLoading, setIsIndiciesLoading] = useState<boolean>(false); const [alertThresholdPopoverOpen, setAlertThresholdPopoverOpen] = useState(false); @@ -323,6 +229,13 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = ); const hasExpressionErrors = !!Object.keys(errors).find( + errorKey => + expressionFieldsWithValidation.includes(errorKey) && + errors[errorKey].length >= 1 && + alert.params[errorKey] !== undefined + ); + + const canShowVizualization = !!Object.keys(errors).find( errorKey => expressionFieldsWithValidation.includes(errorKey) && errors[errorKey].length >= 1 ); @@ -433,7 +346,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = defaultMessage="Indices to query" /> } - isInvalid={hasErrors && index !== undefined} + isInvalid={errors.index.length > 0 && index !== undefined} error={errors.index} helpText={ <FormattedMessage @@ -446,7 +359,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = fullWidth async isLoading={isIndiciesLoading} - isInvalid={hasErrors && index !== undefined} + isInvalid={errors.index.length > 0 && index !== undefined} noSuggestions={!indexOptions.length} options={indexOptions} data-test-subj="thresholdIndexesComboBox" @@ -466,8 +379,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = // reset time field and expression fields if indices are deleted if (indices.length === 0) { setTimeFieldOptions([firstFieldOption]); - setAlertParams('timeFields', []); - setDefaultExpressionValues(); return; } @@ -475,7 +386,6 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = const timeFields = getTimeFieldOptions(currentEsFields as any); setEsFields(currentEsFields); - setAlertParams('timeFields', timeFields); setTimeFieldOptions([firstFieldOption, ...timeFields]); }} onSearchChange={async search => { @@ -493,7 +403,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = </EuiFlexItem> <EuiFlexItem grow={false}> <EuiFormRow - id="timeField" + id="thresholdTimeField" fullWidth label={ <FormattedMessage @@ -501,15 +411,15 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = defaultMessage="Time field" /> } - isInvalid={hasErrors && timeField !== undefined} + isInvalid={errors.timeField.length > 0 && timeField !== undefined} error={errors.timeField} > <EuiSelect options={timeFieldOptions} - isInvalid={hasErrors && timeField !== undefined} + isInvalid={errors.timeField.length > 0 && timeField !== undefined} fullWidth - name="watchTimeField" - data-test-subj="watchTimeFieldSelect" + name="thresholdTimeField" + data-test-subj="thresholdAlertTimeFieldSelect" value={timeField} onChange={e => { setAlertParams('timeField', e.target.value); @@ -542,6 +452,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = id="indexPopover" button={ <EuiExpression + data-test-subj="selectIndexExpression" description={i18n.translate( 'xpack.triggersActionsUI.sections.alertAdd.threshold.indexLabel', { @@ -672,12 +583,12 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = <EuiFlexGroup> <EuiFlexItem grow={false} className="watcherThresholdAlertAggFieldContainer"> <EuiFormRow - isInvalid={hasErrors && aggField !== undefined} + isInvalid={errors.aggField.length > 0 && aggField !== undefined} error={errors.aggField} > <EuiComboBox singleSelection={{ asPlainText: true }} - isInvalid={hasErrors && aggField !== undefined} + isInvalid={errors.aggField.length > 0 && aggField !== undefined} placeholder={firstFieldOption.text} options={esFields.reduce((esFieldOptions: any[], field: any) => { if ( @@ -777,9 +688,9 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = {groupByTypes[groupBy || DEFAULT_VALUES.GROUP_BY].sizeRequired ? ( <Fragment> <EuiFlexItem grow={false}> - <EuiFormRow isInvalid={hasErrors} error={errors.termSize}> + <EuiFormRow isInvalid={errors.termSize.length > 0} error={errors.termSize}> <EuiFieldNumber - isInvalid={hasErrors} + isInvalid={errors.termSize.length > 0} value={termSize || 0} onChange={e => { const { value } = e.target; @@ -792,12 +703,12 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = </EuiFlexItem> <EuiFlexItem grow={false}> <EuiFormRow - isInvalid={hasErrors && termField !== undefined} + isInvalid={errors.timeField.length > 0 && termField !== undefined} error={errors.termField} > <EuiSelect value={termField || ''} - isInvalid={hasErrors && termField !== undefined} + isInvalid={errors.timeField.length > 0 && termField !== undefined} onChange={e => { setAlertParams('termField', e.target.value); }} @@ -896,14 +807,17 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = className="alertThresholdWatchInBetweenComparatorText" > <EuiText>{andThresholdText}</EuiText> - {hasErrors && <EuiSpacer />} + {errors[`threshold${i}`].length > 0 && <EuiSpacer />} </EuiFlexItem> ) : null} <EuiFlexItem grow={false}> - <EuiFormRow isInvalid={hasErrors} error={errors[`threshold${i}`]}> + <EuiFormRow + isInvalid={errors[`threshold${i}`].length > 0} + error={errors[`threshold${i}`]} + > <EuiFieldNumber data-test-subj="alertThresholdInput" - isInvalid={hasErrors} + isInvalid={errors[`threshold${i}`].length > 0} value={!threshold || threshold[i] === null ? 0 : threshold[i]} min={0} step={0.1} @@ -963,9 +877,12 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = </EuiPopoverTitle> <EuiFlexGroup> <EuiFlexItem grow={false}> - <EuiFormRow isInvalid={hasErrors} error={errors.timeWindowSize}> + <EuiFormRow + isInvalid={errors.timeWindowSize.length > 0} + error={errors.timeWindowSize} + > <EuiFieldNumber - isInvalid={hasErrors} + isInvalid={errors.timeWindowSize.length > 0} min={1} value={timeWindowSize ? parseInt(timeWindowSize, 10) : 1} onChange={e => { @@ -990,7 +907,7 @@ export const IndexThresholdAlertTypeExpression: React.FunctionComponent<Props> = </EuiPopover> </EuiFlexItem> </EuiFlexGroup> - {hasExpressionErrors ? null : ( + {canShowVizualization ? null : ( <Fragment> <ThresholdVisualization alert={alert} /> </Fragment> diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/index.ts new file mode 100644 index 0000000000000..a0a5b05d49680 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/index.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { Alert, AlertTypeModel, ValidationResult } from '../../../../types'; +import { IndexThresholdAlertTypeExpression, aggregationTypes, groupByTypes } from './expression'; + +export function getAlertType(): AlertTypeModel { + return { + id: 'threshold', + name: 'Index Threshold', + iconClass: 'alert', + alertParamsExpression: IndexThresholdAlertTypeExpression, + validate: (alert: Alert): ValidationResult => { + const { + index, + timeField, + aggType, + aggField, + groupBy, + termSize, + termField, + threshold, + timeWindowSize, + } = alert.params; + const validationResult = { errors: {} }; + const errors = { + aggField: new Array<string>(), + termSize: new Array<string>(), + termField: new Array<string>(), + timeWindowSize: new Array<string>(), + threshold0: new Array<string>(), + threshold1: new Array<string>(), + index: new Array<string>(), + timeField: new Array<string>(), + }; + validationResult.errors = errors; + if (!index || index.length === 0) { + errors.index.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText', { + defaultMessage: 'Index is required.', + }) + ); + } + if (!timeField) { + errors.timeField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText', { + defaultMessage: 'Time field is required.', + }) + ); + } + if (aggType && aggregationTypes[aggType].fieldRequired && !aggField) { + errors.aggField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText', { + defaultMessage: 'Aggregation field is required.', + }) + ); + } + if (!termSize) { + errors.termSize.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText', { + defaultMessage: 'Term size is required.', + }) + ); + } + if (groupBy && groupByTypes[groupBy].sizeRequired && !termField) { + errors.termField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText', { + defaultMessage: 'Term field is required.', + }) + ); + } + if (!timeWindowSize) { + errors.timeWindowSize.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText', + { + defaultMessage: 'Time window size is required.', + } + ) + ); + } + if (threshold && threshold.length > 0 && !threshold[0]) { + errors.threshold0.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text', { + defaultMessage: 'Threshold0, is required.', + }) + ); + } + if (threshold && threshold.length > 1 && !threshold[1]) { + errors.threshold1.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text', { + defaultMessage: 'Threshold1 is required.', + }) + ); + } + return validationResult; + }, + defaultActionMessage: 'Alert [{{ctx.metadata.name}}] has exceeded the threshold', + }; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx index 9ba79bba8fef6..f91c3cf9a7708 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx @@ -280,17 +280,18 @@ export const ThresholdVisualization: React.FunctionComponent<Props> = ({ alert } </Chart> ) : ( <EuiCallOut + size="s" title={ <FormattedMessage id="xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.noDataTitle" - defaultMessage="No data" + defaultMessage="No data matches that query" /> } color="warning" > <FormattedMessage id="xpack.triggersActionsUI.sections.alertAdd.thresholdPreviewChart.dataDoesNotExistTextMessage" - defaultMessage="Your index and condition did not return any data." + defaultMessage="Check your time range and filters to make sure they are correct" /> </EuiCallOut> )} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts deleted file mode 100644 index 83a03010d55ad..0000000000000 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts +++ /dev/null @@ -1,11 +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. - */ - -export enum ACTION_GROUPS { - ALERT = 'alert', - WARNING = 'warning', - UNACKNOWLEDGED = 'unacknowledged', -} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx index 06be1bb7c5851..e019319d843a8 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx @@ -7,8 +7,9 @@ import React, { useContext, createContext } from 'react'; export interface AlertsContextValue { - alertFlyoutVisible: boolean; - setAlertFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>; + addFlyoutVisible: boolean; + setAddFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>; + reloadAlerts: () => Promise<void>; } const AlertsContext = createContext<AlertsContextValue>(null as any); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts index 35d1a095188de..9e64fd86f9873 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts @@ -35,6 +35,8 @@ describe('loadAlertTypes', () => { { id: 'test', name: 'Test', + actionVariables: ['var1'], + actionGroups: ['default'], }, ]; http.get.mockResolvedValueOnce(resolvedValue); @@ -300,6 +302,7 @@ describe('createAlert', () => { test('should call create alert API', async () => { const alertToCreate = { name: 'test', + consumer: 'alerting', tags: ['foo'], enabled: true, alertTypeId: 'test', @@ -309,7 +312,6 @@ describe('createAlert', () => { actions: [], params: {}, throttle: null, - consumer: '', createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, @@ -331,7 +333,7 @@ describe('createAlert', () => { Array [ "/api/alert", Object { - "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"consumer\\":\\"\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"name\\":\\"test\\",\\"consumer\\":\\"alerting\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"actions\\":[],\\"params\\":{},\\"throttle\\":null,\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); @@ -342,6 +344,7 @@ describe('updateAlert', () => { test('should call alert update API', async () => { const alertToUpdate = { throttle: '1m', + consumer: 'alerting', name: 'test', tags: ['foo'], schedule: { @@ -349,7 +352,6 @@ describe('updateAlert', () => { }, params: {}, actions: [], - consumer: '', createdAt: new Date('1970-01-01T00:00:00.000Z'), updatedAt: new Date('1970-01-01T00:00:00.000Z'), apiKey: null, @@ -373,7 +375,7 @@ describe('updateAlert', () => { Array [ "/api/alert/123", Object { - "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"consumer\\":\\"\\",\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", + "body": "{\\"throttle\\":\\"1m\\",\\"consumer\\":\\"alerting\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"schedule\\":{\\"interval\\":\\"1m\\"},\\"params\\":{},\\"actions\\":[],\\"createdAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"updatedAt\\":\\"1970-01-01T00:00:00.000Z\\",\\"apiKey\\":null,\\"apiKeyOwner\\":null}", }, ] `); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/_index.scss b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/_index.scss new file mode 100644 index 0000000000000..f8fa882cd617d --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/_index.scss @@ -0,0 +1,3 @@ +.actConnectorModal { + z-index: 9000; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx index f27f7d8c3054d..7bccf7a4328b6 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -6,9 +6,6 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; -import { ReactWrapper } from 'enzyme'; -import { act } from 'react-dom/test-utils'; -import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult, ActionConnector } from '../../../types'; import { ActionConnectorForm } from './action_connector_form'; @@ -16,8 +13,7 @@ import { AppContextProvider } from '../../app_context'; const actionTypeRegistry = actionTypeRegistryMock.create(); describe('action_connector_form', () => { - let wrapper: ReactWrapper<any>; - + let deps: any; beforeAll(async () => { const mockes = coreMock.createSetup(); const [ @@ -27,7 +23,7 @@ describe('action_connector_form', () => { application: { capabilities }, }, ] = await mockes.getStartServices(); - const deps = { + deps = { chrome, docLinks, toastNotifications: mockes.notifications.toasts, @@ -48,7 +44,9 @@ describe('action_connector_form', () => { actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: {} as any, }; + }); + it('renders action_connector_form', () => { const actionType = { id: 'my-action-type', iconClass: 'test', @@ -71,50 +69,19 @@ describe('action_connector_form', () => { config: {}, secrets: {}, } as ActionConnector; - - await act(async () => { - wrapper = mountWithIntl( - <AppContextProvider appDeps={deps}> - <ActionsConnectorsContextProvider - value={{ - addFlyoutVisible: true, - setAddFlyoutVisibility: () => {}, - editFlyoutVisible: false, - setEditFlyoutVisibility: () => {}, - actionTypesIndex: { - 'my-action-type': { - id: 'my-action-type', - name: 'my-action-type-name', - enabled: true, - }, - }, - reloadConnectors: () => { - return new Promise<void>(() => {}); - }, - }} - > - <ActionConnectorForm - actionTypeName={'my-action-type-name'} - initialConnector={initialConnector} - setFlyoutVisibility={() => {}} - /> - </ActionsConnectorsContextProvider> - </AppContextProvider> - ); - }); - - await waitForRender(wrapper); - }); - - it('renders action_connector_form', () => { + const wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <ActionConnectorForm + actionTypeName={'my-action-type-name'} + connector={initialConnector} + dispatch={() => {}} + serverError={null} + errors={{ name: [] }} + /> + </AppContextProvider> + ); const connectorNameField = wrapper.find('[data-test-subj="nameInput"]'); expect(connectorNameField.exists()).toBeTruthy(); expect(connectorNameField.first().prop('value')).toBe(''); }); }); - -async function waitForRender(wrapper: ReactWrapper<any, any>) { - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); -} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx index 852e713b38ed7..c29064efcde35 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx @@ -3,49 +3,59 @@ * 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, useReducer } from 'react'; +import React, { Fragment } from 'react'; import { - EuiButton, - EuiFlexGroup, - EuiFlexItem, EuiForm, EuiCallOut, EuiLink, EuiText, EuiSpacer, - EuiButtonEmpty, - EuiFlyoutFooter, EuiFieldText, - EuiFlyoutBody, EuiFormRow, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { createActionConnector, updateActionConnector } from '../../lib/action_connector_api'; import { useAppDependencies } from '../../app_context'; -import { connectorReducer } from './connector_reducer'; -import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ReducerAction } from './connector_reducer'; import { ActionConnector, IErrorObject } from '../../../types'; -import { hasSaveActionsCapability } from '../../lib/capabilities'; + +export function validateBaseProperties(actionObject: ActionConnector) { + const validationResult = { errors: {} }; + const verrors = { + name: new Array<string>(), + }; + validationResult.errors = verrors; + if (!actionObject.name) { + verrors.name.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.error.requiredNameText', + { + defaultMessage: 'Name is required.', + } + ) + ); + } + return validationResult; +} interface ActionConnectorProps { - initialConnector: ActionConnector; + connector: ActionConnector; + dispatch: React.Dispatch<ReducerAction>; actionTypeName: string; - setFlyoutVisibility: React.Dispatch<React.SetStateAction<boolean>>; + serverError: { + body: { message: string; error: string }; + } | null; + errors: IErrorObject; } export const ActionConnectorForm = ({ - initialConnector, + connector, + dispatch, actionTypeName, - setFlyoutVisibility, + serverError, + errors, }: ActionConnectorProps) => { - const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies(); - - const { reloadConnectors } = useActionsConnectorsContext(); - const canSave = hasSaveActionsCapability(capabilities); - - // hooks - const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); + const { actionTypeRegistry } = useAppDependencies(); const setActionProperty = (key: string, value: any) => { dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); @@ -59,207 +69,86 @@ export const ActionConnectorForm = ({ dispatch({ command: { type: 'setSecretsProperty' }, payload: { key, value } }); }; - const [isSaving, setIsSaving] = useState<boolean>(false); - const [serverError, setServerError] = useState<{ - body: { message: string; error: string }; - } | null>(null); - - const actionTypeRegistered = actionTypeRegistry.get(initialConnector.actionTypeId); - if (!actionTypeRegistered) return null; - - function validateBaseProperties(actionObject: ActionConnector) { - const validationResult = { errors: {} }; - const errors = { - name: new Array<string>(), - }; - validationResult.errors = errors; - if (!actionObject.name) { - errors.name.push( - i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorForm.error.requiredNameText', - { - defaultMessage: 'Name is required.', - } - ) - ); - } - return validationResult; - } + const actionTypeRegistered = actionTypeRegistry.get(connector.actionTypeId); + if (!actionTypeRegistered) + return ( + <Fragment> + <EuiCallOut + title={i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionTypeConfigurationWarningTitleText', + { + defaultMessage: 'Action type not registered', + } + )} + color="warning" + iconType="help" + > + <EuiText> + <p> + <FormattedMessage + id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningDescriptionText" + defaultMessage="To create this connector, you must configure at least one {actionType} account. {docLink}" + values={{ + actionType: actionTypeName, + docLink: ( + <EuiLink target="_blank"> + <FormattedMessage + id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningHelpLinkText" + defaultMessage="Learn more." + /> + </EuiLink> + ), + }} + /> + </p> + </EuiText> + </EuiCallOut> + <EuiSpacer /> + </Fragment> + ); const FieldsComponent = actionTypeRegistered.actionConnectorFields; - const errors = { - ...actionTypeRegistered.validateConnector(connector).errors, - ...validateBaseProperties(connector).errors, - } as IErrorObject; - const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); - - async function onActionConnectorSave(): Promise<any> { - let message: string; - let savedConnector: ActionConnector | undefined; - let error; - if (connector.id === undefined) { - await createActionConnector({ http, connector }) - .then(res => { - savedConnector = res; - }) - .catch(errorRes => { - error = errorRes; - }); - - message = 'Created'; - } else { - await updateActionConnector({ http, connector, id: connector.id }) - .then(res => { - savedConnector = res; - }) - .catch(errorRes => { - error = errorRes; - }); - message = 'Updated'; - } - if (error) { - return { - error, - }; - } - toastNotifications.addSuccess( - i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorForm.updateSuccessNotificationText', - { - defaultMessage: "{message} '{connectorName}'", - values: { - message, - connectorName: savedConnector ? savedConnector.name : '', - }, - } - ) - ); - return savedConnector; - } return ( - <Fragment> - <EuiFlyoutBody> - <EuiForm isInvalid={serverError !== null} error={serverError?.body.message}> - <EuiFormRow - id="actionName" - fullWidth - label={ - <FormattedMessage - id="xpack.triggersActionsUI.sections.actionConnectorForm.actionNameLabel" - defaultMessage="Connector name" - /> + <EuiForm isInvalid={serverError !== null} error={serverError?.body.message}> + <EuiFormRow + id="actionName" + fullWidth + label={ + <FormattedMessage + id="xpack.triggersActionsUI.sections.actionConnectorForm.actionNameLabel" + defaultMessage="Connector name" + /> + } + isInvalid={errors.name.length > 0 && connector.name !== undefined} + error={errors.name} + > + <EuiFieldText + fullWidth + isInvalid={errors.name.length > 0 && connector.name !== undefined} + name="name" + placeholder="Untitled" + data-test-subj="nameInput" + value={connector.name || ''} + onChange={e => { + setActionProperty('name', e.target.value); + }} + onBlur={() => { + if (!connector.name) { + setActionProperty('name', ''); } - isInvalid={errors.name.length > 0 && connector.name !== undefined} - error={errors.name} - > - <EuiFieldText - fullWidth - isInvalid={errors.name.length > 0 && connector.name !== undefined} - name="name" - placeholder="Untitled" - data-test-subj="nameInput" - value={connector.name || ''} - onChange={e => { - setActionProperty('name', e.target.value); - }} - onBlur={() => { - if (!connector.name) { - setActionProperty('name', ''); - } - }} - /> - </EuiFormRow> - <EuiSpacer size="m" /> - {FieldsComponent !== null ? ( - <FieldsComponent - action={connector} - errors={errors} - editActionConfig={setActionConfigProperty} - editActionSecrets={setActionSecretsProperty} - hasErrors={hasErrors} - > - {initialConnector.actionTypeId === null ? ( - <Fragment> - <EuiCallOut - title={i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionTypeConfigurationWarningTitleText', - { - defaultMessage: 'Action type may not be configured', - } - )} - color="warning" - iconType="help" - > - <EuiText> - <p> - <FormattedMessage - id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningDescriptionText" - defaultMessage="To create this connector, you must configure at least one {actionType} account. {docLink}" - values={{ - actionType: actionTypeName, - docLink: ( - <EuiLink target="_blank"> - <FormattedMessage - id="xpack.triggersActionsUI.sections.actionConnectorForm.actions.actionConfigurationWarningHelpLinkText" - defaultMessage="Learn more." - /> - </EuiLink> - ), - }} - /> - </p> - </EuiText> - </EuiCallOut> - <EuiSpacer /> - </Fragment> - ) : null} - </FieldsComponent> - ) : null} - </EuiForm> - </EuiFlyoutBody> - <EuiFlyoutFooter> - <EuiFlexGroup justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={() => setFlyoutVisibility(false)}> - {i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorForm.cancelButtonLabel', - { - defaultMessage: 'Cancel', - } - )} - </EuiButtonEmpty> - </EuiFlexItem> - {canSave ? ( - <EuiFlexItem grow={false}> - <EuiButton - fill - color="secondary" - data-test-subj="saveActionButton" - type="submit" - iconType="check" - isDisabled={hasErrors} - isLoading={isSaving} - onClick={async () => { - setIsSaving(true); - const savedAction = await onActionConnectorSave(); - setIsSaving(false); - if (savedAction && savedAction.error) { - return setServerError(savedAction.error); - } - setFlyoutVisibility(false); - reloadConnectors(); - }} - > - <FormattedMessage - id="xpack.triggersActionsUI.sections.actionConnectorForm.saveButtonLabel" - defaultMessage="Save" - /> - </EuiButton> - </EuiFlexItem> - ) : null} - </EuiFlexGroup> - </EuiFlyoutFooter> - </Fragment> + }} + /> + </EuiFormRow> + <EuiSpacer size="m" /> + {FieldsComponent !== null ? ( + <FieldsComponent + action={connector} + errors={errors} + editActionConfig={setActionConfigProperty} + editActionSecrets={setActionSecretsProperty} + /> + ) : null} + </EuiForm> ); }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx index 19373fda79b9e..f89d61acb59ca 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx @@ -3,18 +3,8 @@ * 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 } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiCard, - EuiIcon, - EuiFlexGrid, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiButtonEmpty, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { EuiFlexItem, EuiCard, EuiIcon, EuiFlexGrid } from '@elastic/eui'; import { ActionType } from '../../../types'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { useAppDependencies } from '../../app_context'; @@ -25,7 +15,7 @@ interface Props { export const ActionTypeMenu = ({ onActionTypeChange }: Props) => { const { actionTypeRegistry } = useAppDependencies(); - const { actionTypesIndex, setAddFlyoutVisibility } = useActionsConnectorsContext(); + const { actionTypesIndex } = useActionsConnectorsContext(); if (!actionTypesIndex) { return null; } @@ -45,7 +35,7 @@ export const ActionTypeMenu = ({ onActionTypeChange }: Props) => { const cardNodes = actionTypes .sort((a, b) => a.name.localeCompare(b.name)) - .map((item, index): any => { + .map((item, index) => { return ( <EuiFlexItem key={index}> <EuiCard @@ -59,25 +49,5 @@ export const ActionTypeMenu = ({ onActionTypeChange }: Props) => { ); }); - return ( - <Fragment> - <EuiFlyoutBody> - <EuiFlexGrid columns={2}>{cardNodes}</EuiFlexGrid> - </EuiFlyoutBody> - <EuiFlyoutFooter> - <EuiFlexGroup justifyContent="spaceBetween"> - <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={() => setAddFlyoutVisibility(false)}> - {i18n.translate( - 'xpack.triggersActionsUI.sections.actionConnectorForm.cancelButtonLabel', - { - defaultMessage: 'Cancel', - } - )} - </EuiButtonEmpty> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFlyoutFooter> - </Fragment> - ); + return <EuiFlexGrid columns={2}>{cardNodes}</EuiFlexGrid>; }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx index a03296c7c3679..52b9d1c0ed1bb 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -6,17 +6,16 @@ import * as React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { coreMock } from '../../../../../../../../../src/core/public/mocks'; -import { ReactWrapper } from 'enzyme'; -import { act } from 'react-dom/test-utils'; import { ConnectorAddFlyout } from './connector_add_flyout'; import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ValidationResult } from '../../../types'; import { AppContextProvider } from '../../app_context'; +import { AppDeps } from '../../app'; const actionTypeRegistry = actionTypeRegistryMock.create(); describe('connector_add_flyout', () => { - let wrapper: ReactWrapper<any>; + let deps: AppDeps | null; beforeAll(async () => { const mockes = coreMock.createSetup(); @@ -27,7 +26,7 @@ describe('connector_add_flyout', () => { application: { capabilities }, }, ] = await mockes.getStartServices(); - const deps = { + deps = { chrome, docLinks, toastNotifications: mockes.notifications.toasts, @@ -48,31 +47,6 @@ describe('connector_add_flyout', () => { actionTypeRegistry: actionTypeRegistry as any, alertTypeRegistry: {} as any, }; - - await act(async () => { - wrapper = mountWithIntl( - <AppContextProvider appDeps={deps}> - <ActionsConnectorsContextProvider - value={{ - addFlyoutVisible: true, - setAddFlyoutVisibility: state => {}, - editFlyoutVisible: false, - setEditFlyoutVisibility: state => {}, - actionTypesIndex: { - 'my-action-type': { id: 'my-action-type', name: 'test', enabled: true }, - }, - reloadConnectors: () => { - return new Promise<void>(() => {}); - }, - }} - > - <ConnectorAddFlyout /> - </ActionsConnectorsContextProvider> - </AppContextProvider> - ); - }); - - await waitForRender(wrapper); }); it('renders action type menu on flyout open', () => { @@ -93,13 +67,27 @@ describe('connector_add_flyout', () => { actionTypeRegistry.get.mockReturnValueOnce(actionType); actionTypeRegistry.has.mockReturnValue(true); + const wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <ActionsConnectorsContextProvider + value={{ + addFlyoutVisible: true, + setAddFlyoutVisibility: state => {}, + editFlyoutVisible: false, + setEditFlyoutVisibility: state => {}, + actionTypesIndex: { + 'my-action-type': { id: 'my-action-type', name: 'test', enabled: true }, + }, + reloadConnectors: () => { + return new Promise<void>(() => {}); + }, + }} + > + <ConnectorAddFlyout /> + </ActionsConnectorsContextProvider> + </AppContextProvider> + ); expect(wrapper.find('ActionTypeMenu')).toHaveLength(1); expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeTruthy(); }); }); - -async function waitForRender(wrapper: ReactWrapper<any, any>) { - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); -} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx index a3ec7ab4b3ab9..8a50513f158a1 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.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, { useCallback, useState, Fragment } from 'react'; +import React, { useCallback, useState, Fragment, useReducer } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -13,21 +13,57 @@ import { EuiFlexItem, EuiIcon, EuiText, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiButton, + EuiFlyoutBody, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; import { ActionTypeMenu } from './action_type_menu'; -import { ActionConnectorForm } from './action_connector_form'; -import { ActionType, ActionConnector } from '../../../types'; +import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; +import { ActionType, ActionConnector, IErrorObject } from '../../../types'; import { useAppDependencies } from '../../app_context'; +import { connectorReducer } from './connector_reducer'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; +import { createActionConnector } from '../../lib/action_connector_api'; export const ConnectorAddFlyout = () => { - const { actionTypeRegistry } = useAppDependencies(); - const { addFlyoutVisible, setAddFlyoutVisibility } = useActionsConnectorsContext(); + let hasErrors = false; + const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies(); const [actionType, setActionType] = useState<ActionType | undefined>(undefined); + + // hooks + const initialConnector = { + actionTypeId: actionType?.id ?? '', + config: {}, + secrets: {}, + } as ActionConnector; + const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); + const setActionProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }; + const setConnector = (value: any) => { + dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } }); + }; + + const { + addFlyoutVisible, + setAddFlyoutVisibility, + reloadConnectors, + } = useActionsConnectorsContext(); + const [isSaving, setIsSaving] = useState<boolean>(false); + const closeFlyout = useCallback(() => { setAddFlyoutVisibility(false); setActionType(undefined); - }, [setAddFlyoutVisibility, setActionType]); + setConnector(initialConnector); + }, [setAddFlyoutVisibility, initialConnector]); + + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); + const canSave = hasSaveActionsCapability(capabilities); if (!addFlyoutVisible) { return null; @@ -35,6 +71,7 @@ export const ConnectorAddFlyout = () => { function onActionTypeChange(newActionType: ActionType) { setActionType(newActionType); + setActionProperty('actionTypeId', newActionType.id); } let currentForm; @@ -43,21 +80,45 @@ export const ConnectorAddFlyout = () => { currentForm = <ActionTypeMenu onActionTypeChange={onActionTypeChange} />; } else { actionTypeModel = actionTypeRegistry.get(actionType.id); - const initialConnector = { - actionTypeId: actionType.id, - config: {}, - secrets: {}, - } as ActionConnector; + + const errors = { + ...actionTypeModel?.validateConnector(connector).errors, + ...validateBaseProperties(connector).errors, + } as IErrorObject; + hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); currentForm = ( <ActionConnectorForm actionTypeName={actionType.name} - initialConnector={initialConnector} - setFlyoutVisibility={closeFlyout} + connector={connector} + dispatch={dispatch} + serverError={serverError} + errors={errors} /> ); } + const onActionConnectorSave = async (): Promise<ActionConnector | undefined> => + await createActionConnector({ http, connector }) + .then(savedConnector => { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.sections.addConnectorForm.updateSuccessNotificationText', + { + defaultMessage: "Created '{connectorName}'", + values: { + connectorName: savedConnector.name, + }, + } + ) + ); + return savedConnector; + }) + .catch(errorRes => { + setServerError(errorRes); + return undefined; + }); + return ( <EuiFlyout onClose={closeFlyout} aria-labelledby="flyoutActionAddTitle" size="m"> <EuiFlyoutHeader hasBorder> @@ -98,7 +159,49 @@ export const ConnectorAddFlyout = () => { </EuiFlexItem> </EuiFlexGroup> </EuiFlyoutHeader> - {currentForm} + <EuiFlyoutBody>{currentForm}</EuiFlyoutBody> + + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={closeFlyout}> + {i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorAdd.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + </EuiButtonEmpty> + </EuiFlexItem> + {canSave && actionTypeModel && actionType ? ( + <EuiFlexItem grow={false}> + <EuiButton + fill + color="secondary" + data-test-subj="saveNewActionButton" + type="submit" + iconType="check" + isDisabled={hasErrors} + isLoading={isSaving} + onClick={async () => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + closeFlyout(); + reloadConnectors(); + } + }} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.actionConnectorAdd.saveButtonLabel" + defaultMessage="Save" + /> + </EuiButton> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + </EuiFlyoutFooter> </EuiFlyout> ); }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.test.tsx new file mode 100644 index 0000000000000..cc87b16035ad3 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.test.tsx @@ -0,0 +1,102 @@ +/* + * 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 * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ConnectorAddModal } from './connector_add_modal'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult } from '../../../types'; +import { AppContextProvider } from '../../app_context'; +import { AppDeps } from '../../app'; +const actionTypeRegistry = actionTypeRegistryMock.create(); + +describe('connector_add_modal', () => { + let deps: AppDeps | null; + + beforeAll(async () => { + const mocks = coreMock.createSetup(); + const [ + { + chrome, + docLinks, + application: { capabilities }, + }, + ] = await mocks.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mocks.notifications.toasts, + injectedMetadata: mocks.injectedMetadata, + http: mocks.http, + uiSettings: mocks.uiSettings, + capabilities: { + ...capabilities, + actions: { + delete: true, + save: true, + show: true, + }, + }, + legacy: { + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + }); + it('renders connector modal form if addModalVisible is true', () => { + const actionTypeModel = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValueOnce(actionTypeModel); + actionTypeRegistry.has.mockReturnValue(true); + + const actionType = { + id: 'my-action-type', + name: 'test', + enabled: true, + }; + + const wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <ActionsConnectorsContextProvider + value={{ + addFlyoutVisible: true, + setAddFlyoutVisibility: state => {}, + editFlyoutVisible: false, + setEditFlyoutVisibility: state => {}, + actionTypesIndex: { + 'my-action-type': { id: 'my-action-type', name: 'test', enabled: true }, + }, + reloadConnectors: () => { + return new Promise<void>(() => {}); + }, + }} + > + <ConnectorAddModal + addModalVisible={true} + setAddModalVisibility={() => {}} + actionType={actionType} + /> + </ActionsConnectorsContextProvider> + </AppContextProvider> + ); + expect(wrapper.find('EuiModalHeader')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="saveActionButtonModal"]').exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.tsx new file mode 100644 index 0000000000000..2ce282e946a38 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_modal.tsx @@ -0,0 +1,168 @@ +/* + * 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, { useCallback, useReducer, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiTitle, EuiFlexItem, EuiIcon, EuiFlexGroup } from '@elastic/eui'; +import { + EuiModal, + EuiButton, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalBody, + EuiModalFooter, +} from '@elastic/eui'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; +import { ActionType, ActionConnector, IErrorObject } from '../../../types'; +import { connectorReducer } from './connector_reducer'; +import { createActionConnector } from '../../lib/action_connector_api'; +import { useAppDependencies } from '../../app_context'; + +export const ConnectorAddModal = ({ + actionType, + addModalVisible, + setAddModalVisibility, + postSaveEventHandler, +}: { + actionType: ActionType; + addModalVisible: boolean; + setAddModalVisibility: React.Dispatch<React.SetStateAction<boolean>>; + postSaveEventHandler?: (savedAction: ActionConnector) => void; +}) => { + let hasErrors = false; + const { http, toastNotifications, actionTypeRegistry } = useAppDependencies(); + const initialConnector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + const [isSaving, setIsSaving] = useState<boolean>(false); + + const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); + const setConnector = (value: any) => { + dispatch({ command: { type: 'setConnector' }, payload: { key: 'connector', value } }); + }; + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); + + const closeModal = useCallback(() => { + setAddModalVisibility(false); + setConnector(initialConnector); + setServerError(null); + }, [initialConnector, setAddModalVisibility]); + + if (!addModalVisible) { + return null; + } + const actionTypeModel = actionTypeRegistry.get(actionType.id); + const errors = { + ...actionTypeModel?.validateConnector(connector).errors, + ...validateBaseProperties(connector).errors, + } as IErrorObject; + hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + const onActionConnectorSave = async (): Promise<ActionConnector | undefined> => + await createActionConnector({ http, connector }) + .then(savedConnector => { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.sections.addModalConnectorForm.updateSuccessNotificationText', + { + defaultMessage: "Created '{connectorName}'", + values: { + connectorName: savedConnector.name, + }, + } + ) + ); + return savedConnector; + }) + .catch(errorRes => { + setServerError(errorRes); + return undefined; + }); + + return ( + <EuiOverlayMask className="actConnectorModal"> + <EuiModal onClose={closeModal}> + <EuiModalHeader> + <EuiModalHeaderTitle> + <EuiFlexGroup gutterSize="m" alignItems="center"> + {actionTypeModel && actionTypeModel.iconClass ? ( + <EuiFlexItem grow={false}> + <EuiIcon type={actionTypeModel.iconClass} size="xl" /> + </EuiFlexItem> + ) : null} + <EuiFlexItem> + <EuiTitle size="s"> + <h3 id="flyoutTitle"> + <FormattedMessage + defaultMessage="{actionTypeName} connector" + id="xpack.triggersActionsUI.sections.addModalConnectorForm.flyoutTitle" + values={{ + actionTypeName: actionType.name, + }} + /> + </h3> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + </EuiModalHeaderTitle> + </EuiModalHeader> + + <EuiModalBody> + <ActionConnectorForm + connector={connector} + actionTypeName={actionType.name} + dispatch={dispatch} + serverError={serverError} + errors={errors} + /> + </EuiModalBody> + + <EuiModalFooter> + <EuiButtonEmpty onClick={closeModal}> + {i18n.translate( + 'xpack.triggersActionsUI.sections.addModalConnectorForm.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + </EuiButtonEmpty> + + <EuiButton + fill + color="secondary" + data-test-subj="saveActionButtonModal" + type="submit" + iconType="check" + isDisabled={hasErrors} + isLoading={isSaving} + onClick={async () => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + if (postSaveEventHandler) { + postSaveEventHandler(savedAction); + } + closeModal(); + } + }} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.addModalConnectorForm.saveButtonLabel" + defaultMessage="Save" + /> + </EuiButton> + </EuiModalFooter> + </EuiModal> + </EuiOverlayMask> + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx index 0dc38523bfab8..9687707d0876a 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -91,7 +91,7 @@ describe('connector_edit_flyout', () => { }, }} > - <ConnectorEditFlyout connector={connector} /> + <ConnectorEditFlyout initialConnector={connector} /> </ActionsConnectorsContextProvider> </AppContextProvider> ); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx index 408989609d2ec..9c1f2ddc7f7f6 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.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, { useCallback } from 'react'; +import React, { useCallback, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -12,26 +12,73 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, + EuiButton, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; -import { ActionConnectorForm } from './action_connector_form'; +import { ActionConnectorForm, validateBaseProperties } from './action_connector_form'; import { useAppDependencies } from '../../app_context'; -import { ActionConnectorTableItem } from '../../../types'; +import { ActionConnectorTableItem, ActionConnector, IErrorObject } from '../../../types'; +import { connectorReducer } from './connector_reducer'; +import { updateActionConnector } from '../../lib/action_connector_api'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; export interface ConnectorEditProps { - connector: ActionConnectorTableItem; + initialConnector: ActionConnectorTableItem; } -export const ConnectorEditFlyout = ({ connector }: ConnectorEditProps) => { - const { actionTypeRegistry } = useAppDependencies(); - const { editFlyoutVisible, setEditFlyoutVisibility } = useActionsConnectorsContext(); +export const ConnectorEditFlyout = ({ initialConnector }: ConnectorEditProps) => { + let hasErrors = false; + const { http, toastNotifications, capabilities, actionTypeRegistry } = useAppDependencies(); + const canSave = hasSaveActionsCapability(capabilities); + const { + editFlyoutVisible, + setEditFlyoutVisibility, + reloadConnectors, + } = useActionsConnectorsContext(); const closeFlyout = useCallback(() => setEditFlyoutVisibility(false), [setEditFlyoutVisibility]); + const [{ connector }, dispatch] = useReducer(connectorReducer, { + connector: { ...initialConnector, secrets: {} }, + }); + const [isSaving, setIsSaving] = useState<boolean>(false); + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); if (!editFlyoutVisible) { return null; } const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); + const errors = { + ...actionTypeModel?.validateConnector(connector).errors, + ...validateBaseProperties(connector).errors, + } as IErrorObject; + hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + const onActionConnectorSave = async (): Promise<ActionConnector | undefined> => + await updateActionConnector({ http, connector, id: connector.id }) + .then(savedConnector => { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.updateSuccessNotificationText', + { + defaultMessage: "Updated '{connectorName}'", + values: { + connectorName: savedConnector.name, + }, + } + ) + ); + return savedConnector; + }) + .catch(errorRes => { + setServerError(errorRes); + return undefined; + }); return ( <EuiFlyout onClose={closeFlyout} aria-labelledby="flyoutActionAddTitle" size="m"> @@ -54,15 +101,56 @@ export const ConnectorEditFlyout = ({ connector }: ConnectorEditProps) => { </EuiFlexItem> </EuiFlexGroup> </EuiFlyoutHeader> - <ActionConnectorForm - key={connector.id} - initialConnector={{ - ...connector, - secrets: {}, - }} - actionTypeName={connector.actionType} - setFlyoutVisibility={setEditFlyoutVisibility} - /> + <EuiFlyoutBody> + <ActionConnectorForm + connector={connector} + serverError={serverError} + errors={errors} + actionTypeName={connector.actionType} + dispatch={dispatch} + /> + </EuiFlyoutBody> + <EuiFlyoutFooter> + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem grow={false}> + <EuiButtonEmpty onClick={closeFlyout}> + {i18n.translate( + 'xpack.triggersActionsUI.sections.editConnectorForm.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + </EuiButtonEmpty> + </EuiFlexItem> + {canSave && actionTypeModel ? ( + <EuiFlexItem grow={false}> + <EuiButton + fill + color="secondary" + data-test-subj="saveEditedActionButton" + type="submit" + iconType="check" + isDisabled={hasErrors} + isLoading={isSaving} + onClick={async () => { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction) { + closeFlyout(); + reloadConnectors(); + } + }} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.editConnectorForm.saveButtonLabel" + defaultMessage="Save" + /> + </EuiButton> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + </EuiFlyoutFooter> </EuiFlyout> ); }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts index 4a2610f965735..4d094cd869420 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts @@ -6,7 +6,7 @@ import { isEqual } from 'lodash'; interface CommandType { - type: 'setProperty' | 'setConfigProperty' | 'setSecretsProperty'; + type: 'setConnector' | 'setProperty' | 'setConfigProperty' | 'setSecretsProperty'; } export interface ActionState { @@ -26,6 +26,17 @@ export const connectorReducer = (state: ActionState, action: ReducerAction) => { const { connector } = state; switch (command.type) { + case 'setConnector': { + const { key, value } = payload; + if (key === 'connector') { + return { + ...state, + connector: value, + }; + } else { + return state; + } + } case 'setProperty': { const { key, value } = payload; if (isEqual(connector[key], value)) { diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx index e98c3b2c08749..bed285f668e01 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -384,7 +384,12 @@ export const ActionsConnectorsList: React.FunctionComponent = () => { }} > <ConnectorAddFlyout /> - {editedConnectorItem ? <ConnectorEditFlyout connector={editedConnectorItem} /> : null} + {editedConnectorItem ? ( + <ConnectorEditFlyout + key={editedConnectorItem.id} + initialConnector={editedConnectorItem} + /> + ) : null} </ActionsConnectorsContextProvider> </section> ); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.test.tsx new file mode 100644 index 0000000000000..bf94ffc73b85c --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.test.tsx @@ -0,0 +1,117 @@ +/* + * 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 * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from 'react-dom/test-utils'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { AlertAdd } from './alert_add'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult } from '../../../types'; +import { AppContextProvider } from '../../app_context'; +import { AppDeps } from '../../app'; +import { AlertsContextProvider } from '../../context/alerts_context'; +import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; +import { ReactWrapper } from 'enzyme'; +const actionTypeRegistry = actionTypeRegistryMock.create(); +const alertTypeRegistry = alertTypeRegistryMock.create(); + +describe('alert_add', () => { + let deps: AppDeps | null; + let wrapper: ReactWrapper<any>; + + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [ + { + chrome, + docLinks, + application: { capabilities }, + }, + ] = await mockes.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + capabilities: { + ...capabilities, + alerting: { + delete: true, + save: true, + show: true, + }, + }, + legacy: { + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + const alertType = { + id: 'my-alert-type', + iconClass: 'test', + name: 'test-alert', + validate: (): ValidationResult => { + return { errors: {} }; + }, + alertParamsExpression: () => <React.Fragment />, + }; + + const actionTypeModel = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValueOnce(actionTypeModel); + actionTypeRegistry.has.mockReturnValue(true); + alertTypeRegistry.list.mockReturnValue([alertType]); + alertTypeRegistry.get.mockReturnValue(alertType); + alertTypeRegistry.has.mockReturnValue(true); + actionTypeRegistry.list.mockReturnValue([actionTypeModel]); + actionTypeRegistry.has.mockReturnValue(true); + + await act(async () => { + wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <AlertsContextProvider + value={{ + addFlyoutVisible: true, + setAddFlyoutVisibility: state => {}, + reloadAlerts: () => { + return new Promise<void>(() => {}); + }, + }} + > + <AlertAdd /> + </AlertsContextProvider> + </AppContextProvider> + ); + }); + await waitForRender(wrapper); + }); + + it('renders alert add flyout', () => { + expect(wrapper.find('[data-test-subj="addAlertFlyoutTitle"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="saveAlertButton"]').exists()).toBeTruthy(); + }); +}); + +async function waitForRender(wrapper: ReactWrapper<any, any>) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx index f11b0b948b2a7..d73feff938076 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx @@ -3,584 +3,120 @@ * 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, useCallback, useReducer, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { useCallback, useReducer, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiButton, + EuiTitle, + EuiFlyoutHeader, + EuiFlyout, + EuiFlyoutFooter, EuiFlexGroup, EuiFlexItem, - EuiIcon, - EuiTitle, - EuiForm, - EuiSpacer, EuiButtonEmpty, - EuiFlyoutFooter, + EuiButton, EuiFlyoutBody, - EuiFlyoutHeader, - EuiFlyout, - EuiFieldText, - EuiFlexGrid, - EuiFormRow, - EuiComboBox, - EuiKeyPadMenuItem, - EuiTabs, - EuiTab, - EuiLink, - EuiFieldNumber, - EuiSelect, - EuiIconTip, EuiPortal, - EuiAccordion, - EuiButtonIcon, } from '@elastic/eui'; -import { useAppDependencies } from '../../app_context'; -import { createAlert } from '../../lib/alert_api'; -import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; +import { i18n } from '@kbn/i18n'; import { useAlertsContext } from '../../context/alerts_context'; +import { Alert, AlertAction, IErrorObject } from '../../../types'; +import { AlertForm, validateBaseProperties } from './alert_form'; import { alertReducer } from './alert_reducer'; -import { - AlertTypeModel, - Alert, - IErrorObject, - ActionTypeModel, - AlertAction, - ActionTypeIndex, - ActionConnector, -} from '../../../types'; -import { ACTION_GROUPS } from '../../constants/action_groups'; -import { getTimeOptions } from '../../lib/get_time_options'; -import { SectionLoading } from '../../components/section_loading'; - -interface Props { - refreshList: () => Promise<void>; -} - -function validateBaseProperties(alertObject: Alert) { - const validationResult = { errors: {} }; - const errors = { - name: new Array<string>(), - interval: new Array<string>(), - alertTypeId: new Array<string>(), - actionConnectors: new Array<string>(), - }; - validationResult.errors = errors; - if (!alertObject.name) { - errors.name.push( - i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredNameText', { - defaultMessage: 'Name is required.', - }) - ); - } - if (!(alertObject.schedule && alertObject.schedule.interval)) { - errors.interval.push( - i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredIntervalText', { - defaultMessage: 'Check interval is required.', - }) - ); - } - if (!alertObject.alertTypeId) { - errors.alertTypeId.push( - i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredAlertTypeIdText', { - defaultMessage: 'Alert trigger is required.', - }) - ); - } - return validationResult; -} +import { useAppDependencies } from '../../app_context'; +import { createAlert } from '../../lib/alert_api'; -export const AlertAdd = ({ refreshList }: Props) => { +export const AlertAdd = () => { const { http, toastNotifications, alertTypeRegistry, actionTypeRegistry } = useAppDependencies(); - const initialAlert = { + const initialAlert = ({ params: {}, + consumer: 'alerting', alertTypeId: null, schedule: { interval: '1m', }, actions: [], tags: [], - }; + } as unknown) as Alert; - const { alertFlyoutVisible, setAlertFlyoutVisibility } = useAlertsContext(); - // hooks - const [alertType, setAlertType] = useState<AlertTypeModel | undefined>(undefined); const [{ alert }, dispatch] = useReducer(alertReducer, { alert: initialAlert }); const [isSaving, setIsSaving] = useState<boolean>(false); - const [isLoadingActionTypes, setIsLoadingActionTypes] = useState<boolean>(false); - const [selectedTabId, setSelectedTabId] = useState<string>('alert'); - const [actionTypesIndex, setActionTypesIndex] = useState<ActionTypeIndex | undefined>(undefined); - const [alertInterval, setAlertInterval] = useState<number | null>(null); - const [alertIntervalUnit, setAlertIntervalUnit] = useState<string>('m'); - const [alertThrottle, setAlertThrottle] = useState<number | null>(null); - const [alertThrottleUnit, setAlertThrottleUnit] = useState<string>(''); - const [serverError, setServerError] = useState<{ - body: { message: string; error: string }; - } | null>(null); - const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState<boolean>(true); - const [connectors, setConnectors] = useState<ActionConnector[]>([]); - - useEffect(() => { - (async () => { - try { - setIsLoadingActionTypes(true); - const actionTypes = await loadActionTypes({ http }); - const index: ActionTypeIndex = {}; - for (const actionTypeItem of actionTypes) { - index[actionTypeItem.id] = actionTypeItem; - } - setActionTypesIndex(index); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadActionTypesMessage', - { defaultMessage: 'Unable to load action types' } - ), - }); - } finally { - setIsLoadingActionTypes(false); - } - })(); - }, [toastNotifications, http]); - - useEffect(() => { - dispatch({ - command: { type: 'setAlert' }, - payload: { - key: 'alert', - value: { - params: {}, - alertTypeId: null, - schedule: { - interval: '1m', - }, - actions: [], - tags: [], - }, - }, - }); - }, [alertFlyoutVisible]); - - useEffect(() => { - loadConnectors(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [alertFlyoutVisible]); - - const setAlertProperty = (key: string, value: any) => { - dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); - }; - const setAlertParams = (key: string, value: any) => { - dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } }); + const setAlert = (value: any) => { + dispatch({ command: { type: 'setAlert' }, payload: { key: 'alert', value } }); }; - const setActionParamsProperty = (key: string, value: any, index: number) => { - dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); - }; - - const setActionProperty = (key: string, value: any, index: number) => { - dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } }); - }; + const { addFlyoutVisible, setAddFlyoutVisibility, reloadAlerts } = useAlertsContext(); const closeFlyout = useCallback(() => { - setAlertFlyoutVisibility(false); - setAlertType(undefined); - setIsAddActionPanelOpen(true); - setSelectedTabId('alert'); + setAddFlyoutVisibility(false); + setAlert(initialAlert); setServerError(null); - }, [setAlertFlyoutVisibility]); - - if (!alertFlyoutVisible) { - return null; - } + }, [initialAlert, setAddFlyoutVisibility]); - const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); - async function loadConnectors() { - try { - const actionsResponse = await loadAllActions({ http }); - setConnectors(actionsResponse.data); - } catch (e) { - toastNotifications.addDanger({ - title: i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadActionsMessage', - { - defaultMessage: 'Unable to load connectors', - } - ), - }); - } + if (!addFlyoutVisible) { + return null; } - const AlertParamsExpressionComponent = alertType ? alertType.alertParamsExpression : null; - + const alertType = alertTypeRegistry.get(alert.alertTypeId); const errors = { ...(alertType ? alertType.validate(alert).errors : []), ...validateBaseProperties(alert).errors, } as IErrorObject; const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); - const actionErrors = alert.actions.reduce((acc: any, alertAction: AlertAction) => { - const actionTypeConnectors = connectors.find(field => field.id === alertAction.id); - if (!actionTypeConnectors) { - return []; - } - const actionType = actionTypeRegistry.get(actionTypeConnectors.actionTypeId); - if (!actionType) { - return []; - } - const actionValidationErrors = actionType.validateParams(alertAction.params); - acc[alertAction.id] = actionValidationErrors; - return acc; - }, {}); - - const hasActionErrors = !!Object.keys(actionErrors).find(actionError => { - return !!Object.keys(actionErrors[actionError]).find((actionErrorKey: string) => { - return actionErrors[actionError][actionErrorKey].length >= 1; - }); - }); - - const tabs = [ - { - id: ACTION_GROUPS.ALERT, - name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.alertTabText', { - defaultMessage: 'Alert', - }), - }, - { - id: ACTION_GROUPS.WARNING, - name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.warningTabText', { - defaultMessage: 'Warning', - }), - }, - { - id: ACTION_GROUPS.UNACKNOWLEDGED, - name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.unacknowledgedTabText', { - defaultMessage: 'If unacknowledged', - }), - disabled: false, + const actionsErrors = alert.actions.reduce( + (acc: Record<string, { errors: IErrorObject }>, alertAction: AlertAction) => { + const actionType = actionTypeRegistry.get(alertAction.actionTypeId); + if (!actionType) { + return { ...acc }; + } + const actionValidationErrors = actionType.validateParams(alertAction.params); + return { ...acc, [alertAction.id]: actionValidationErrors }; }, - ]; + {} + ) as Record<string, { errors: IErrorObject }>; + + const hasActionErrors = !!Object.entries(actionsErrors) + .map(([, actionErrors]) => actionErrors) + .find((actionErrors: { errors: IErrorObject }) => { + return !!Object.keys(actionErrors.errors).find( + errorKey => actionErrors.errors[errorKey].length >= 1 + ); + }); - async function onSaveAlert(): Promise<any> { + async function onSaveAlert(): Promise<Alert | undefined> { try { const newAlert = await createAlert({ http, alert }); toastNotifications.addSuccess( - i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', { + i18n.translate('xpack.triggersActionsUI.sections.alertForm.saveSuccessNotificationText', { defaultMessage: "Saved '{alertName}'", values: { - alertName: newAlert.id, + alertName: newAlert.name, }, }) ); return newAlert; - } catch (error) { - return { - error, - }; - } - } - - function addActionType(actionTypeModel: ActionTypeModel) { - setIsAddActionPanelOpen(false); - const actionTypeConnectors = connectors.filter( - field => field.actionTypeId === actionTypeModel.id - ); - if (actionTypeConnectors.length > 0) { - alert.actions.push({ id: actionTypeConnectors[0].id, group: selectedTabId, params: {} }); + } catch (errorRes) { + setServerError(errorRes); + return undefined; } } - const alertTypeNodes = alertTypeRegistry.list().map(function(item, index) { - return ( - <EuiKeyPadMenuItem - key={index} - label={item.name} - onClick={() => { - setAlertProperty('alertTypeId', item.id); - setAlertType(item); - }} - > - <EuiIcon size="xl" type={item.iconClass} /> - </EuiKeyPadMenuItem> - ); - }); - - const actionTypeNodes = actionTypeRegistry.list().map(function(item, index) { - return ( - <EuiKeyPadMenuItem - key={index} - label={actionTypesIndex ? actionTypesIndex[item.id].name : item.id} - onClick={() => addActionType(item)} - > - <EuiIcon size="xl" type={item.iconClass} /> - </EuiKeyPadMenuItem> - ); - }); - - const alertTabs = tabs.map(function(tab, index): any { - return ( - <EuiTab - onClick={() => { - setSelectedTabId(tab.id); - if (!alert.actions.find((action: AlertAction) => action.group === tab.id)) { - setIsAddActionPanelOpen(true); - } else { - setIsAddActionPanelOpen(false); - } - }} - isSelected={tab.id === selectedTabId} - disabled={tab.disabled} - key={index} - > - {tab.name} - </EuiTab> - ); - }); - - const alertTypeDetails = ( - <Fragment> - <EuiFlexGroup alignItems="center" gutterSize="s"> - <EuiFlexItem> - <EuiTitle size="s"> - <h5 id="selectedAlertTypeTitle"> - <FormattedMessage - defaultMessage="Trigger: {alertType}" - id="xpack.triggersActionsUI.sections.alertAdd.selectedAlertTypeTitle" - values={{ alertType: alertType ? alertType.name : '' }} - /> - </h5> - </EuiTitle> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiLink - onClick={() => { - setAlertProperty('alertTypeId', null); - setAlertType(undefined); - }} - > - <FormattedMessage - defaultMessage="Change" - id="xpack.triggersActionsUI.sections.alertAdd.changeAlertTypeLink" - /> - </EuiLink> - </EuiFlexItem> - </EuiFlexGroup> - {AlertParamsExpressionComponent ? ( - <AlertParamsExpressionComponent - alert={alert} - errors={errors} - setAlertParams={setAlertParams} - setAlertProperty={setAlertProperty} - hasErrors={hasErrors} - /> - ) : null} - </Fragment> - ); - - const getSelectedOptions = (actionItemId: string) => { - const val = connectors.find(connector => connector.id === actionItemId); - if (!val) { - return []; - } - return [ - { - label: val.name, - value: val.name, - id: actionItemId, - }, - ]; - }; - - const actionsListForGroup = ( - <Fragment> - {alert.actions.map((actionItem: AlertAction, index: number) => { - const actionConnector = connectors.find(field => field.id === actionItem.id); - if (!actionConnector) { - return null; - } - const optionsList = connectors - .filter(field => field.actionTypeId === actionConnector.actionTypeId) - .map(({ name, id }) => ({ - label: name, - key: id, - id, - })); - const actionTypeRegisterd = actionTypeRegistry.get(actionConnector.actionTypeId); - if (actionTypeRegisterd === null || actionItem.group !== selectedTabId) return null; - const ParamsFieldsComponent = actionTypeRegisterd.actionParamsFields; - const actionParamsErrors = - Object.keys(actionErrors).length > 0 ? actionErrors[actionItem.id] : []; - const hasActionParamsErrors = !!Object.keys(actionParamsErrors).find( - errorKey => actionParamsErrors[errorKey].length >= 1 - ); - return ( - <EuiAccordion - initialIsOpen={true} - key={index} - id={index.toString()} - className="euiAccordionForm" - buttonContentClassName="euiAccordionForm__button" - data-test-subj="alertActionAccordion" - buttonContent={ - <EuiFlexGroup gutterSize="s" alignItems="center"> - <EuiFlexItem grow={false}> - <EuiIcon type={actionTypeRegisterd.iconClass} size="m" /> - </EuiFlexItem> - <EuiFlexItem> - <EuiTitle size="s"> - <h5> - <FormattedMessage - defaultMessage="Action: {actionConnectorName}" - id="xpack.triggersActionsUI.sections.alertAdd.selectAlertActionTypeEditTitle" - values={{ actionConnectorName: actionConnector.name }} - /> - </h5> - </EuiTitle> - </EuiFlexItem> - </EuiFlexGroup> - } - extraAction={ - <EuiButtonIcon - iconType="cross" - color="danger" - className="euiAccordionForm__extraAction" - aria-label={i18n.translate( - 'xpack.triggersActionsUI.sections.alertAdd.accordion.deleteIconAriaLabel', - { - defaultMessage: 'Delete', - } - )} - onClick={() => { - const updatedActions = alert.actions.filter( - (item: AlertAction) => item.id !== actionItem.id - ); - setAlertProperty('actions', updatedActions); - }} - /> - } - paddingSize="l" - > - <EuiFormRow - label={ - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.actionIdLabel" - defaultMessage="{connectorInstance} instance" - values={{ - connectorInstance: actionTypesIndex - ? actionTypesIndex[actionConnector.actionTypeId].name - : actionConnector.actionTypeId, - }} - /> - } - // errorKey="name" - // isShowingErrors={hasErrors} - // errors={errors} - > - <EuiComboBox - fullWidth - singleSelection={{ asPlainText: true }} - options={optionsList} - selectedOptions={getSelectedOptions(actionItem.id)} - onChange={selectedOptions => { - setActionProperty('id', selectedOptions[0].id, index); - }} - isClearable={false} - /> - </EuiFormRow> - <EuiSpacer size="s" /> - {ParamsFieldsComponent ? ( - <ParamsFieldsComponent - action={actionItem.params} - index={index} - errors={actionParamsErrors.errors} - editAction={setActionParamsProperty} - hasErrors={hasActionParamsErrors} - /> - ) : null} - </EuiAccordion> - ); - })} - <EuiSpacer size="m" /> - {!isAddActionPanelOpen ? ( - <EuiButton - iconType="plusInCircle" - data-test-subj="addAlertActionButton" - onClick={() => setIsAddActionPanelOpen(true)} - > - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.addActionButtonLabel" - defaultMessage="Add action" - /> - </EuiButton> - ) : null} - </Fragment> - ); - - let alertTypeArea; - if (alertType) { - alertTypeArea = <Fragment>{alertTypeDetails}</Fragment>; - } else { - alertTypeArea = ( - <Fragment> - <EuiTitle size="s"> - <h5 id="alertTypeTitle"> - <FormattedMessage - defaultMessage="Select a trigger" - id="xpack.triggersActionsUI.sections.alertAdd.selectAlertTypeTitle" - /> - </h5> - </EuiTitle> - <EuiSpacer /> - <EuiFlexGroup gutterSize="s" wrap> - {alertTypeNodes} - </EuiFlexGroup> - </Fragment> - ); - } - - const labelForAlertChecked = ( - <> - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.checkFieldLabel" - defaultMessage="Check every" - />{' '} - <EuiIconTip - position="right" - type="questionInCircle" - content={i18n.translate('xpack.triggersActionsUI.sections.alertAdd.checkWithTooltip', { - defaultMessage: 'This is some help text here for check alert.', - })} - /> - </> - ); - - const labelForAlertRenotify = ( - <> - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.renotifyFieldLabel" - defaultMessage="Re-notify every" - />{' '} - <EuiIconTip - position="right" - type="questionInCircle" - content={i18n.translate('xpack.triggersActionsUI.sections.alertAdd.renotifyWithTooltip', { - defaultMessage: 'This is some help text here for re-notify alert.', - })} - /> - </> - ); - return ( <EuiPortal> <EuiFlyout - ownFocus onClose={closeFlyout} aria-labelledby="flyoutAlertAddTitle" size="m" maxWidth={620} + ownFocus > <EuiFlyoutHeader hasBorder> - <EuiTitle size="s"> + <EuiTitle size="s" data-test-subj="addAlertFlyoutTitle"> <h3 id="flyoutTitle"> <FormattedMessage defaultMessage="Create Alert" @@ -590,188 +126,12 @@ export const AlertAdd = ({ refreshList }: Props) => { </EuiTitle> </EuiFlyoutHeader> <EuiFlyoutBody> - <EuiForm isInvalid={serverError !== null} error={serverError?.body.message}> - <EuiFlexGrid columns={2}> - <EuiFlexItem> - <EuiFormRow - fullWidth - id="alertName" - label={ - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.alertNameLabel" - defaultMessage="Name" - /> - } - isInvalid={hasErrors && alert.name !== undefined} - error={errors.name} - > - <EuiFieldText - fullWidth - isInvalid={hasErrors && alert.name !== undefined} - compressed - name="name" - data-test-subj="alertNameInput" - value={alert.name || ''} - onChange={e => { - setAlertProperty('name', e.target.value); - }} - onBlur={() => { - if (!alert.name) { - setAlertProperty('name', ''); - } - }} - /> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem> - <EuiFormRow - fullWidth - label={i18n.translate( - 'xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel', - { - defaultMessage: 'Tags (optional)', - } - )} - > - <EuiComboBox - noSuggestions - fullWidth - compressed - data-test-subj="tagsComboBox" - selectedOptions={tagsOptions} - onCreateOption={(searchValue: string) => { - const newOptions = [...tagsOptions, { label: searchValue }]; - setAlertProperty( - 'tags', - newOptions.map(newOption => newOption.label) - ); - }} - onChange={(selectedOptions: Array<{ label: string }>) => { - setAlertProperty( - 'tags', - selectedOptions.map(selectedOption => selectedOption.label) - ); - }} - onBlur={() => { - if (!alert.tags) { - setAlertProperty('tags', []); - } - }} - /> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGrid> - <EuiSpacer size="m" /> - <EuiFlexGrid columns={2}> - <EuiFlexItem> - <EuiFormRow fullWidth compressed label={labelForAlertChecked}> - <EuiFlexGroup gutterSize="s"> - <EuiFlexItem> - <EuiFieldNumber - fullWidth - min={1} - compressed - value={alertInterval || 1} - name="interval" - data-test-subj="intervalInput" - onChange={e => { - const interval = - e.target.value !== '' ? parseInt(e.target.value, 10) : null; - setAlertInterval(interval); - setAlertProperty('schedule', { - interval: `${e.target.value}${alertIntervalUnit}`, - }); - }} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiSelect - fullWidth - compressed - value={alertIntervalUnit} - options={getTimeOptions((alertInterval ? alertInterval : 1).toString())} - onChange={(e: any) => { - setAlertIntervalUnit(e.target.value); - setAlertProperty('schedule', { - interval: `${alertInterval}${e.target.value}`, - }); - }} - /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFormRow> - </EuiFlexItem> - <EuiFlexItem> - <EuiFormRow fullWidth label={labelForAlertRenotify}> - <EuiFlexGroup gutterSize="s"> - <EuiFlexItem> - <EuiFieldNumber - fullWidth - min={1} - compressed - value={alertThrottle || ''} - name="throttle" - data-test-subj="throttleInput" - onChange={e => { - const throttle = - e.target.value !== '' ? parseInt(e.target.value, 10) : null; - setAlertThrottle(throttle); - setAlertProperty('throttle', `${e.target.value}${alertThrottleUnit}`); - }} - /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiSelect - compressed - value={alert.renotifyIntervalUnit} - options={getTimeOptions(alert.renotifyIntervalSize)} - onChange={(e: any) => { - setAlertThrottleUnit(e.target.value); - setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); - }} - /> - </EuiFlexItem> - </EuiFlexGroup> - </EuiFormRow> - </EuiFlexItem> - </EuiFlexGrid> - <EuiSpacer size="m" /> - <EuiTabs>{alertTabs}</EuiTabs> - <EuiSpacer size="m" /> - {alertTypeArea} - <EuiSpacer size="xl" /> - {actionsListForGroup} - {isAddActionPanelOpen ? ( - <Fragment> - <EuiTitle size="s"> - <h5 id="alertActionTypeTitle"> - <FormattedMessage - defaultMessage="Select an action" - id="xpack.triggersActionsUI.sections.alertAdd.selectAlertActionTypeTitle" - /> - </h5> - </EuiTitle> - <EuiSpacer /> - <EuiFlexGroup gutterSize="s" wrap> - {isLoadingActionTypes ? ( - <SectionLoading> - <FormattedMessage - id="xpack.triggersActionsUI.sections.alertAdd.loadingActionTypesDescription" - defaultMessage="Loading action types…" - /> - </SectionLoading> - ) : ( - actionTypeNodes - )} - </EuiFlexGroup> - </Fragment> - ) : null} - </EuiForm> + <AlertForm alert={alert} dispatch={dispatch} errors={errors} serverError={serverError} /> </EuiFlyoutBody> <EuiFlyoutFooter> <EuiFlexGroup justifyContent="spaceBetween"> <EuiFlexItem grow={false}> - <EuiButtonEmpty onClick={closeFlyout}> + <EuiButtonEmpty data-test-subj="cancelSaveAlertButton" onClick={closeFlyout}> {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { defaultMessage: 'Cancel', })} @@ -781,7 +141,7 @@ export const AlertAdd = ({ refreshList }: Props) => { <EuiButton fill color="secondary" - data-test-subj="saveActionButton" + data-test-subj="saveAlertButton" type="submit" iconType="check" isDisabled={hasErrors || hasActionErrors} @@ -790,11 +150,10 @@ export const AlertAdd = ({ refreshList }: Props) => { setIsSaving(true); const savedAlert = await onSaveAlert(); setIsSaving(false); - if (savedAlert && savedAlert.error) { - return setServerError(savedAlert.error); + if (savedAlert) { + closeFlyout(); + reloadAlerts(); } - closeFlyout(); - refreshList(); }} > <FormattedMessage diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.test.tsx new file mode 100644 index 0000000000000..90a8a47071f2a --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.test.tsx @@ -0,0 +1,200 @@ +/* + * 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, { Fragment } from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; +import { ValidationResult, Alert } from '../../../types'; +import { AlertForm } from './alert_form'; +import { AppContextProvider } from '../../app_context'; +import { AppDeps } from '../../app'; +const actionTypeRegistry = actionTypeRegistryMock.create(); +const alertTypeRegistry = alertTypeRegistryMock.create(); +describe('alert_form', () => { + let deps: AppDeps | null; + const alertType = { + id: 'my-alert-type', + iconClass: 'test', + name: 'test-alert', + validate: (): ValidationResult => { + return { errors: {} }; + }, + alertParamsExpression: () => <Fragment />, + }; + + const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [ + { + chrome, + docLinks, + application: { capabilities }, + }, + ] = await mockes.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + capabilities: { + ...capabilities, + siem: { + 'alerting:show': true, + 'alerting:save': true, + 'alerting:delete': false, + }, + }, + legacy: { + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + }); + + describe('alert_form create alert', () => { + let wrapper: ReactWrapper<any>; + + beforeAll(async () => { + alertTypeRegistry.list.mockReturnValue([alertType]); + alertTypeRegistry.has.mockReturnValue(true); + actionTypeRegistry.list.mockReturnValue([actionType]); + actionTypeRegistry.has.mockReturnValue(true); + + const initialAlert = ({ + name: 'test', + params: {}, + consumer: 'alerting', + schedule: { + interval: '1m', + }, + actions: [], + tags: [], + muteAll: false, + enabled: false, + mutedInstanceIds: [], + } as unknown) as Alert; + + await act(async () => { + wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <AlertForm + alert={initialAlert} + dispatch={() => {}} + errors={{ name: [] }} + serverError={null} + /> + </AppContextProvider> + ); + }); + + await waitForRender(wrapper); + }); + + it('renders alert name', () => { + const alertNameField = wrapper.find('[data-test-subj="alertNameInput"]'); + expect(alertNameField.exists()).toBeTruthy(); + expect(alertNameField.first().prop('value')).toBe('test'); + }); + + it('renders registered selected alert type', () => { + const alertTypeSelectOptions = wrapper.find('[data-test-subj="my-alert-type-SelectOption"]'); + expect(alertTypeSelectOptions.exists()).toBeTruthy(); + }); + + it('renders registered action types', () => { + const alertTypeSelectOptions = wrapper.find( + '[data-test-subj=".server-log-ActionTypeSelectOption"]' + ); + expect(alertTypeSelectOptions.exists()).toBeFalsy(); + }); + }); + + describe('alert_form edit alert', () => { + let wrapper: ReactWrapper<any>; + + beforeAll(async () => { + alertTypeRegistry.list.mockReturnValue([alertType]); + alertTypeRegistry.get.mockReturnValue(alertType); + alertTypeRegistry.has.mockReturnValue(true); + actionTypeRegistry.list.mockReturnValue([actionType]); + actionTypeRegistry.has.mockReturnValue(true); + + const initialAlert = ({ + name: 'test', + alertTypeId: alertType.id, + params: {}, + consumer: 'alerting', + schedule: { + interval: '1m', + }, + actions: [], + tags: [], + muteAll: false, + enabled: false, + mutedInstanceIds: [], + } as unknown) as Alert; + + await act(async () => { + wrapper = mountWithIntl( + <AppContextProvider appDeps={deps}> + <AlertForm + alert={initialAlert} + dispatch={() => {}} + errors={{ name: [] }} + serverError={null} + /> + </AppContextProvider> + ); + }); + + await waitForRender(wrapper); + }); + + it('renders alert name', () => { + const alertNameField = wrapper.find('[data-test-subj="alertNameInput"]'); + expect(alertNameField.exists()).toBeTruthy(); + expect(alertNameField.first().prop('value')).toBe('test'); + }); + + it('renders registered selected alert type', () => { + const alertTypeSelectOptions = wrapper.find('[data-test-subj="selectedAlertTypeTitle"]'); + expect(alertTypeSelectOptions.exists()).toBeTruthy(); + }); + + it('renders registered action types', () => { + const actionTypeSelectOptions = wrapper.find( + '[data-test-subj="my-action-type-ActionTypeSelectOption"]' + ); + expect(actionTypeSelectOptions.exists()).toBeTruthy(); + }); + }); + + async function waitForRender(wrapper: ReactWrapper<any, any>) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); + } +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.tsx new file mode 100644 index 0000000000000..78aca3ec78e66 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_form.tsx @@ -0,0 +1,853 @@ +/* + * 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, { Fragment, useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiForm, + EuiSpacer, + EuiFieldText, + EuiFlexGrid, + EuiFormRow, + EuiComboBox, + EuiKeyPadMenuItem, + EuiLink, + EuiFieldNumber, + EuiSelect, + EuiIconTip, + EuiAccordion, + EuiButtonIcon, + EuiEmptyPrompt, + EuiButtonEmpty, +} from '@elastic/eui'; +import { useAppDependencies } from '../../app_context'; +import { loadAlertTypes } from '../../lib/alert_api'; +import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; +import { AlertReducerAction } from './alert_reducer'; +import { + AlertTypeModel, + Alert, + IErrorObject, + ActionTypeModel, + AlertAction, + ActionTypeIndex, + ActionConnector, + AlertTypeIndex, +} from '../../../types'; +import { getTimeOptions } from '../../lib/get_time_options'; +import { SectionLoading } from '../../components/section_loading'; +import { ConnectorAddModal } from '../action_connector_form/connector_add_modal'; + +export function validateBaseProperties(alertObject: Alert) { + const validationResult = { errors: {} }; + const errors = { + name: new Array<string>(), + interval: new Array<string>(), + alertTypeId: new Array<string>(), + actionConnectors: new Array<string>(), + }; + validationResult.errors = errors; + if (!alertObject.name) { + errors.name.push( + i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredNameText', { + defaultMessage: 'Name is required.', + }) + ); + } + if (!alertObject.schedule.interval) { + errors.interval.push( + i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredIntervalText', { + defaultMessage: 'Check interval is required.', + }) + ); + } + if (!alertObject.alertTypeId) { + errors.alertTypeId.push( + i18n.translate('xpack.triggersActionsUI.sections.alertForm.error.requiredAlertTypeIdText', { + defaultMessage: 'Alert trigger is required.', + }) + ); + } + return validationResult; +} + +interface AlertFormProps { + alert: Alert; + canChangeTrigger?: boolean; // to hide Change trigger button + dispatch: React.Dispatch<AlertReducerAction>; + errors: IErrorObject; + serverError: { + body: { message: string; error: string }; + } | null; +} + +interface ActiveActionConnectorState { + actionTypeId: string; + index: number; +} + +export const AlertForm = ({ + alert, + canChangeTrigger = true, + dispatch, + errors, + serverError, +}: AlertFormProps) => { + const { http, toastNotifications, alertTypeRegistry, actionTypeRegistry } = useAppDependencies(); + const [alertTypeModel, setAlertTypeModel] = useState<AlertTypeModel | null>( + alertTypeRegistry.get(alert.alertTypeId) + ); + + const [addModalVisible, setAddModalVisibility] = useState<boolean>(false); + const [isLoadingActionTypes, setIsLoadingActionTypes] = useState<boolean>(false); + const [actionTypesIndex, setActionTypesIndex] = useState<ActionTypeIndex | undefined>(undefined); + const [alertTypesIndex, setAlertTypesIndex] = useState<AlertTypeIndex | undefined>(undefined); + const [alertInterval, setAlertInterval] = useState<number | null>(null); + const [alertIntervalUnit, setAlertIntervalUnit] = useState<string>('m'); + const [alertThrottle, setAlertThrottle] = useState<number | null>(null); + const [alertThrottleUnit, setAlertThrottleUnit] = useState<string>('m'); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState<boolean>(true); + const [connectors, setConnectors] = useState<ActionConnector[]>([]); + const [defaultActionGroup, setDefaultActionGroup] = useState<string | undefined>(undefined); + const [activeActionItem, setActiveActionItem] = useState<ActiveActionConnectorState | undefined>( + undefined + ); + + // load action types + useEffect(() => { + (async () => { + try { + setIsLoadingActionTypes(true); + const actionTypes = await loadActionTypes({ http }); + const index: ActionTypeIndex = {}; + for (const actionTypeItem of actionTypes) { + index[actionTypeItem.id] = actionTypeItem; + } + setActionTypesIndex(index); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionTypesMessage', + { defaultMessage: 'Unable to load action types' } + ), + }); + } finally { + setIsLoadingActionTypes(false); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // load alert types + useEffect(() => { + (async () => { + try { + const alertTypes = await loadAlertTypes({ http }); + // temp hack of API result + alertTypes.push({ + id: 'threshold', + actionGroups: ['Alert', 'Warning', 'If unacknowledged'], + name: 'threshold', + actionVariables: ['ctx.metadata.name', 'ctx.metadata.test'], + }); + const index: AlertTypeIndex = {}; + for (const alertTypeItem of alertTypes) { + index[alertTypeItem.id] = alertTypeItem; + } + setAlertTypesIndex(index); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.unableToLoadAlertTypesMessage', + { defaultMessage: 'Unable to load alert types' } + ), + }); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + loadConnectors(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const setAlertProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }; + + const setAlertParams = (key: string, value: any) => { + dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } }); + }; + + const setScheduleProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setScheduleProperty' }, payload: { key, value } }); + }; + + const setActionParamsProperty = (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); + }; + + const setActionProperty = (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } }); + }; + + const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; + + async function loadConnectors() { + try { + const actionsResponse = await loadAllActions({ http }); + setConnectors(actionsResponse.data); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.unableToLoadActionsMessage', + { + defaultMessage: 'Unable to load connectors', + } + ), + }); + } + } + + const actionsErrors = alert.actions.reduce( + (acc: Record<string, { errors: IErrorObject }>, alertAction: AlertAction) => { + const actionType = actionTypeRegistry.get(alertAction.actionTypeId); + if (!actionType) { + return { ...acc }; + } + const actionValidationErrors = actionType.validateParams(alertAction.params); + return { ...acc, [alertAction.id]: actionValidationErrors }; + }, + {} + ); + + const AlertParamsExpressionComponent = alertTypeModel + ? alertTypeModel.alertParamsExpression + : null; + + function addActionType(actionTypeModel: ActionTypeModel) { + setIsAddActionPanelOpen(false); + const actionTypeConnectors = connectors.filter( + field => field.actionTypeId === actionTypeModel.id + ); + let freeConnectors; + if (actionTypeConnectors.length > 0) { + // Should we allow adding multiple actions to the same connector under the alert? + freeConnectors = actionTypeConnectors.filter( + (actionConnector: ActionConnector) => + !alert.actions.find((actionItem: AlertAction) => actionItem.id === actionConnector.id) + ); + if (freeConnectors.length > 0) { + alert.actions.push({ + id: '', + actionTypeId: actionTypeModel.id, + group: defaultActionGroup ?? 'Alert', + params: {}, + }); + setActionProperty('id', freeConnectors[0].id, alert.actions.length - 1); + } + } + if (actionTypeConnectors.length === 0 || !freeConnectors || freeConnectors.length === 0) { + // if no connectors exists or all connectors is already assigned an action under current alert + // set actionType as id to be able to create new connector within the alert form + alert.actions.push({ + id: '', + actionTypeId: actionTypeModel.id, + group: defaultActionGroup ?? 'Alert', + params: {}, + }); + setActionProperty('id', alert.actions.length, alert.actions.length - 1); + } + } + + const alertTypeNodes = alertTypeRegistry.list().map(function(item, index) { + return ( + <EuiKeyPadMenuItem + key={index} + data-test-subj={`${item.id}-SelectOption`} + label={item.name} + onClick={() => { + setAlertProperty('alertTypeId', item.id); + setAlertTypeModel(item); + if ( + alertTypesIndex && + alertTypesIndex[item.id] && + alertTypesIndex[item.id].actionGroups.length > 0 + ) { + setDefaultActionGroup(alertTypesIndex[item.id].actionGroups[0]); + } + }} + > + <EuiIcon size="xl" type={item.iconClass} /> + </EuiKeyPadMenuItem> + ); + }); + + const actionTypeNodes = actionTypeRegistry.list().map(function(item, index) { + return ( + <EuiKeyPadMenuItem + key={index} + data-test-subj={`${item.id}-ActionTypeSelectOption`} + label={actionTypesIndex ? actionTypesIndex[item.id].name : item.id} + onClick={() => addActionType(item)} + > + <EuiIcon size="xl" type={item.iconClass} /> + </EuiKeyPadMenuItem> + ); + }); + + const getSelectedOptions = (actionItemId: string) => { + const val = connectors.find(connector => connector.id === actionItemId); + if (!val) { + return []; + } + return [ + { + label: val.name, + value: val.name, + id: actionItemId, + }, + ]; + }; + + const getActionTypeForm = ( + actionItem: AlertAction, + actionConnector: ActionConnector, + index: number + ) => { + const optionsList = connectors + .filter( + connectorItem => + connectorItem.actionTypeId === actionItem.actionTypeId && + (connectorItem.id === actionItem.id || + !alert.actions.find( + (existingAction: AlertAction) => + existingAction.id === connectorItem.id && existingAction.group === actionItem.group + )) + ) + .map(({ name, id }) => ({ + label: name, + key: id, + id, + })); + const actionTypeRegisterd = actionTypeRegistry.get(actionConnector.actionTypeId); + if (actionTypeRegisterd === null || actionItem.group !== defaultActionGroup) return null; + const ParamsFieldsComponent = actionTypeRegisterd.actionParamsFields; + const actionParamsErrors: { errors: IErrorObject } = + Object.keys(actionsErrors).length > 0 ? actionsErrors[actionItem.id] : { errors: {} }; + + return ( + <EuiAccordion + initialIsOpen={true} + key={index} + id={index.toString()} + className="euiAccordionForm" + buttonContentClassName="euiAccordionForm__button" + data-test-subj="alertActionAccordion" + buttonContent={ + <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type={actionTypeRegisterd.iconClass} size="m" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="s"> + <h5> + <FormattedMessage + defaultMessage="Action: {actionConnectorName}" + id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle" + values={{ + actionConnectorName: actionConnector.name, + }} + /> + </h5> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + } + extraAction={ + <EuiButtonIcon + iconType="cross" + color="danger" + className="euiAccordionForm__extraAction" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel', + { + defaultMessage: 'Delete', + } + )} + onClick={() => { + const updatedActions = alert.actions.filter( + (item: AlertAction) => item.id !== actionItem.id + ); + setAlertProperty('actions', updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === 0 + ); + setActiveActionItem(undefined); + }} + /> + } + paddingSize="l" + > + <EuiFlexGroup component="div"> + <EuiFlexItem> + <EuiFormRow + fullWidth + label={ + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.actionIdLabel" + defaultMessage="{connectorInstance} instance" + values={{ + connectorInstance: actionTypesIndex + ? actionTypesIndex[actionConnector.actionTypeId].name + : actionConnector.actionTypeId, + }} + /> + } + labelAppend={ + <EuiButtonEmpty + size="xs" + onClick={() => { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} + > + <FormattedMessage + defaultMessage="Add new" + id="xpack.triggersActionsUI.sections.alertForm.addNewConnectorEmptyButton" + /> + </EuiButtonEmpty> + } + > + <EuiComboBox + fullWidth + singleSelection={{ asPlainText: true }} + options={optionsList} + selectedOptions={getSelectedOptions(actionItem.id)} + onChange={selectedOptions => { + setActionProperty('id', selectedOptions[0].id, index); + }} + isClearable={false} + /> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGroup> + <EuiSpacer size="xl" /> + {ParamsFieldsComponent ? ( + <ParamsFieldsComponent + actionParams={actionItem.params as any} + index={index} + errors={actionParamsErrors.errors} + editAction={setActionParamsProperty} + messageVariables={ + alertTypesIndex ? alertTypesIndex[alert.alertTypeId].actionVariables : undefined + } + defaultMessage={alertTypeModel?.defaultActionMessage ?? undefined} + /> + ) : null} + </EuiAccordion> + ); + }; + + const getAddConnectorsForm = (actionItem: AlertAction, index: number) => { + const actionTypeName = actionTypesIndex + ? actionTypesIndex[actionItem.actionTypeId].name + : actionItem.actionTypeId; + const actionTypeRegisterd = actionTypeRegistry.get(actionItem.actionTypeId); + if (actionTypeRegisterd === null || actionItem.group !== defaultActionGroup) return null; + return ( + <EuiAccordion + initialIsOpen={true} + key={index} + id={index.toString()} + className="euiAccordionForm" + buttonContentClassName="euiAccordionForm__button" + data-test-subj="alertActionAccordion" + buttonContent={ + <EuiFlexGroup gutterSize="s" alignItems="center"> + <EuiFlexItem grow={false}> + <EuiIcon type={actionTypeRegisterd.iconClass} size="m" /> + </EuiFlexItem> + <EuiFlexItem> + <EuiTitle size="s"> + <h5> + <FormattedMessage + defaultMessage="Action: {actionConnectorName}" + id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeEditTitle" + values={{ + actionConnectorName: actionTypeRegisterd.actionTypeTitle, + }} + /> + </h5> + </EuiTitle> + </EuiFlexItem> + </EuiFlexGroup> + } + extraAction={ + <EuiButtonIcon + iconType="cross" + color="danger" + className="euiAccordionForm__extraAction" + aria-label={i18n.translate( + 'xpack.triggersActionsUI.sections.alertForm.accordion.deleteIconAriaLabel', + { + defaultMessage: 'Delete', + } + )} + onClick={() => { + const updatedActions = alert.actions.filter( + (item: AlertAction) => item.id !== actionItem.id + ); + setAlertProperty('actions', updatedActions); + setIsAddActionPanelOpen( + updatedActions.filter((item: AlertAction) => item.id !== actionItem.id).length === 0 + ); + setActiveActionItem(undefined); + }} + /> + } + paddingSize="l" + > + <EuiEmptyPrompt + title={ + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.emptyConnectorsLabel" + defaultMessage="There are no {actionTypeName} connectors" + values={{ + actionTypeName, + }} + /> + } + actions={[ + <EuiButton + color="primary" + fill + data-test-subj="createActionConnectorButton" + onClick={() => { + setActiveActionItem({ actionTypeId: actionItem.actionTypeId, index }); + setAddModalVisibility(true); + }} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.addConnectorButtonLabel" + defaultMessage="Add {actionTypeName} connector" + values={{ + actionTypeName, + }} + /> + </EuiButton>, + ]} + /> + </EuiAccordion> + ); + }; + + const selectedGroupActions = ( + <Fragment> + {alert.actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find(field => field.id === actionItem.id); + // connectors doesn't exists + if (!actionConnector) { + return getAddConnectorsForm(actionItem, index); + } + return getActionTypeForm(actionItem, actionConnector, index); + })} + <EuiSpacer size="m" /> + {isAddActionPanelOpen === false ? ( + <EuiButton + iconType="plusInCircle" + data-test-subj="addAlertActionButton" + onClick={() => setIsAddActionPanelOpen(true)} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.addActionButtonLabel" + defaultMessage="Add action" + /> + </EuiButton> + ) : null} + </Fragment> + ); + + const alertTypeDetails = ( + <Fragment> + <EuiFlexGroup alignItems="center" gutterSize="s"> + <EuiFlexItem> + <EuiTitle size="s" data-test-subj="selectedAlertTypeTitle"> + <h5 id="selectedAlertTypeTitle"> + <FormattedMessage + defaultMessage="Trigger: {alertType}" + id="xpack.triggersActionsUI.sections.alertForm.selectedAlertTypeTitle" + values={{ alertType: alertTypeModel ? alertTypeModel.name : '' }} + /> + </h5> + </EuiTitle> + </EuiFlexItem> + {canChangeTrigger ? ( + <EuiFlexItem grow={false}> + <EuiLink + onClick={() => { + setAlertProperty('alertTypeId', null); + setAlertTypeModel(null); + }} + > + <FormattedMessage + defaultMessage="Change" + id="xpack.triggersActionsUI.sections.alertForm.changeAlertTypeLink" + /> + </EuiLink> + </EuiFlexItem> + ) : null} + </EuiFlexGroup> + {AlertParamsExpressionComponent ? ( + <AlertParamsExpressionComponent + alert={alert} + errors={errors} + setAlertParams={setAlertParams} + setAlertProperty={setAlertProperty} + /> + ) : null} + <EuiSpacer size="xl" /> + {selectedGroupActions} + {isAddActionPanelOpen ? ( + <Fragment> + <EuiTitle size="s"> + <h5 id="alertActionTypeTitle"> + <FormattedMessage + defaultMessage="Actions: Select an action type" + id="xpack.triggersActionsUI.sections.alertForm.selectAlertActionTypeTitle" + /> + </h5> + </EuiTitle> + <EuiSpacer /> + <EuiFlexGroup gutterSize="s" wrap> + {isLoadingActionTypes ? ( + <SectionLoading> + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.loadingActionTypesDescription" + defaultMessage="Loading action types…" + /> + </SectionLoading> + ) : ( + actionTypeNodes + )} + </EuiFlexGroup> + </Fragment> + ) : null} + </Fragment> + ); + + const labelForAlertChecked = ( + <> + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.checkFieldLabel" + defaultMessage="Check every" + />{' '} + <EuiIconTip + position="right" + type="questionInCircle" + content={i18n.translate('xpack.triggersActionsUI.sections.alertForm.checkWithTooltip', { + defaultMessage: 'This is some help text here for check alert.', + })} + /> + </> + ); + + const labelForAlertRenotify = ( + <> + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.renotifyFieldLabel" + defaultMessage="Re-notify every" + />{' '} + <EuiIconTip + position="right" + type="questionInCircle" + content={i18n.translate('xpack.triggersActionsUI.sections.alertForm.renotifyWithTooltip', { + defaultMessage: 'This is some help text here for re-notify alert.', + })} + /> + </> + ); + + return ( + <EuiForm isInvalid={serverError !== null} error={serverError?.body.message}> + <EuiFlexGrid columns={2}> + <EuiFlexItem> + <EuiFormRow + fullWidth + id="alertName" + label={ + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertForm.alertNameLabel" + defaultMessage="Name" + /> + } + isInvalid={errors.name.length > 0 && alert.name !== undefined} + error={errors.name} + > + <EuiFieldText + fullWidth + isInvalid={errors.name.length > 0 && alert.name !== undefined} + compressed + name="name" + data-test-subj="alertNameInput" + value={alert.name || ''} + onChange={e => { + setAlertProperty('name', e.target.value); + }} + onBlur={() => { + if (!alert.name) { + setAlertProperty('name', ''); + } + }} + /> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem> + <EuiFormRow + fullWidth + label={i18n.translate( + 'xpack.triggersActionsUI.sections.actionAdd.indexAction.indexTextFieldLabel', + { + defaultMessage: 'Tags (optional)', + } + )} + > + <EuiComboBox + noSuggestions + fullWidth + compressed + data-test-subj="tagsComboBox" + selectedOptions={tagsOptions} + onCreateOption={(searchValue: string) => { + const newOptions = [...tagsOptions, { label: searchValue }]; + setAlertProperty( + 'tags', + newOptions.map(newOption => newOption.label) + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + setAlertProperty( + 'tags', + selectedOptions.map(selectedOption => selectedOption.label) + ); + }} + onBlur={() => { + if (!alert.tags) { + setAlertProperty('tags', []); + } + }} + /> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGrid> + <EuiSpacer size="m" /> + <EuiFlexGrid columns={2}> + <EuiFlexItem> + <EuiFormRow fullWidth compressed label={labelForAlertChecked}> + <EuiFlexGroup gutterSize="s"> + <EuiFlexItem> + <EuiFieldNumber + fullWidth + min={1} + compressed + value={alertInterval || 1} + name="interval" + data-test-subj="intervalInput" + onChange={e => { + const interval = e.target.value !== '' ? parseInt(e.target.value, 10) : null; + setAlertInterval(interval); + setScheduleProperty('interval', `${e.target.value}${alertIntervalUnit}`); + }} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiSelect + fullWidth + compressed + value={alertIntervalUnit} + options={getTimeOptions((alertInterval ? alertInterval : 1).toString())} + onChange={e => { + setAlertIntervalUnit(e.target.value); + setScheduleProperty('interval', `${alertInterval}${e.target.value}`); + }} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </EuiFlexItem> + <EuiFlexItem> + <EuiFormRow fullWidth label={labelForAlertRenotify}> + <EuiFlexGroup gutterSize="s"> + <EuiFlexItem> + <EuiFieldNumber + fullWidth + min={1} + compressed + value={alertThrottle || ''} + name="throttle" + data-test-subj="throttleInput" + onChange={e => { + const throttle = e.target.value !== '' ? parseInt(e.target.value, 10) : null; + setAlertThrottle(throttle); + setAlertProperty('throttle', `${e.target.value}${alertThrottleUnit}`); + }} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiSelect + compressed + value={alertThrottleUnit} + options={getTimeOptions((alertThrottle ? alertThrottle : 1).toString())} + onChange={e => { + setAlertThrottleUnit(e.target.value); + setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); + }} + /> + </EuiFlexItem> + </EuiFlexGroup> + </EuiFormRow> + </EuiFlexItem> + </EuiFlexGrid> + <EuiSpacer size="m" /> + {alertTypeModel ? ( + <Fragment>{alertTypeDetails}</Fragment> + ) : ( + <Fragment> + <EuiTitle size="s"> + <h5 id="alertTypeTitle"> + <FormattedMessage + defaultMessage="Trigger: Select a trigger type" + id="xpack.triggersActionsUI.sections.alertForm.selectAlertTypeTitle" + /> + </h5> + </EuiTitle> + <EuiSpacer /> + <EuiFlexGroup gutterSize="s" wrap> + {alertTypeNodes} + </EuiFlexGroup> + </Fragment> + )} + {actionTypesIndex && activeActionItem ? ( + <ConnectorAddModal + key={activeActionItem.index} + actionType={actionTypesIndex[activeActionItem.actionTypeId]} + addModalVisible={addModalVisible} + setAddModalVisibility={setAddModalVisibility} + postSaveEventHandler={(savedAction: ActionConnector) => { + connectors.push(savedAction); + setActionProperty('id', savedAction.id, activeActionItem.index); + }} + /> + ) : null} + </EuiForm> + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.test.ts new file mode 100644 index 0000000000000..bd320de144024 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.test.ts @@ -0,0 +1,163 @@ +/* + * 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 { alertReducer } from './alert_reducer'; +import { Alert } from '../../../types'; + +describe('alert reducer', () => { + let initialAlert: Alert; + beforeAll(() => { + initialAlert = ({ + params: {}, + consumer: 'alerting', + alertTypeId: null, + schedule: { + interval: '1m', + }, + actions: [], + tags: [], + } as unknown) as Alert; + }); + + // setAlert + test('if modified alert was reset to initial', () => { + const alert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setProperty' }, + payload: { + key: 'name', + value: 'new name', + }, + } + ); + expect(alert.alert.name).toBe('new name'); + + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setAlert' }, + payload: { + key: 'alert', + value: initialAlert, + }, + } + ); + expect(updatedAlert.alert.name).toBeUndefined(); + }); + + test('if property name was changed', () => { + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setProperty' }, + payload: { + key: 'name', + value: 'new name', + }, + } + ); + expect(updatedAlert.alert.name).toBe('new name'); + }); + + test('if initial schedule property was updated', () => { + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setScheduleProperty' }, + payload: { + key: 'interval', + value: '10s', + }, + } + ); + expect(updatedAlert.alert.schedule.interval).toBe('10s'); + }); + + test('if alert params property was added and updated', () => { + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setAlertParams' }, + payload: { + key: 'testParam', + value: 'new test params property', + }, + } + ); + expect(updatedAlert.alert.params.testParam).toBe('new test params property'); + + const updatedAlertParamsProperty = alertReducer( + { alert: updatedAlert.alert }, + { + command: { type: 'setAlertParams' }, + payload: { + key: 'testParam', + value: 'test params property updated', + }, + } + ); + expect(updatedAlertParamsProperty.alert.params.testParam).toBe('test params property updated'); + }); + + test('if alert action params property was added and updated', () => { + initialAlert.actions.push({ + id: '', + actionTypeId: 'testId', + group: 'Alert', + params: {}, + }); + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setAlertActionParams' }, + payload: { + key: 'testActionParam', + value: 'new test action params property', + index: 0, + }, + } + ); + expect(updatedAlert.alert.actions[0].params.testActionParam).toBe( + 'new test action params property' + ); + + const updatedAlertActionParamsProperty = alertReducer( + { alert: updatedAlert.alert }, + { + command: { type: 'setAlertActionParams' }, + payload: { + key: 'testActionParam', + value: 'test action params property updated', + index: 0, + }, + } + ); + expect(updatedAlertActionParamsProperty.alert.actions[0].params.testActionParam).toBe( + 'test action params property updated' + ); + }); + + test('if alert action property was updated', () => { + initialAlert.actions.push({ + id: '', + actionTypeId: 'testId', + group: 'Alert', + params: {}, + }); + const updatedAlert = alertReducer( + { alert: initialAlert }, + { + command: { type: 'setAlertActionProperty' }, + payload: { + key: 'group', + value: 'Warning', + index: 0, + }, + } + ); + expect(updatedAlert.alert.actions[0].group).toBe('Warning'); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts index 9c2260f0178be..2e56f4b026b4a 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts @@ -9,6 +9,7 @@ interface CommandType { type: | 'setAlert' | 'setProperty' + | 'setScheduleProperty' | 'setAlertParams' | 'setAlertActionParams' | 'setAlertActionProperty'; @@ -57,6 +58,23 @@ export const alertReducer = (state: any, action: AlertReducerAction) => { }; } } + case 'setScheduleProperty': { + const { key, value } = payload; + if (isEqual(alert.schedule[key], value)) { + return state; + } else { + return { + ...state, + alert: { + ...alert, + schedule: { + ...alert.schedule, + [key]: value, + }, + }, + }; + } + } case 'setAlertParams': { const { key, value } = payload; if (isEqual(alert.params[key], value)) { diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_details/components/alert_details.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_details/components/alert_details.test.tsx index 228bceb87cad7..683aca742ac87 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_details/components/alert_details.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_details/components/alert_details.test.tsx @@ -43,6 +43,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; expect( @@ -61,6 +63,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; expect( @@ -86,6 +90,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const actionTypes: ActionType[] = [ @@ -133,6 +139,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const actionTypes: ActionType[] = [ { @@ -181,6 +189,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; expect( @@ -203,6 +213,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; expect( @@ -225,6 +237,8 @@ describe('alert_details', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; expect( @@ -252,6 +266,8 @@ describe('enable button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableButton = shallow( @@ -275,6 +291,8 @@ describe('enable button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableButton = shallow( @@ -298,6 +316,8 @@ describe('enable button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const disableAlert = jest.fn(); @@ -330,6 +350,8 @@ describe('enable button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableAlert = jest.fn(); @@ -365,6 +387,8 @@ describe('mute button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableButton = shallow( @@ -389,6 +413,8 @@ describe('mute button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableButton = shallow( @@ -413,6 +439,8 @@ describe('mute button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const muteAlert = jest.fn(); @@ -446,6 +474,8 @@ describe('mute button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const unmuteAlert = jest.fn(); @@ -479,6 +509,8 @@ describe('mute button', () => { const alertType = { id: '.noop', name: 'No Op', + actionGroups: ['default'], + actionVariables: [], }; const enableButton = shallow( diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx index f410fff44172f..60435552a5759 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -46,7 +46,6 @@ actionTypeRegistry.list.mockReturnValue([]); describe('alerts_list component empty', () => { let wrapper: ReactWrapper<any>; - beforeEach(async () => { const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); const { loadActionTypes, loadAllActions } = jest.requireMock( @@ -124,14 +123,13 @@ describe('alerts_list component empty', () => { }); it('renders empty list', () => { - expect(wrapper.find('[data-test-subj="createAlertButton"]').find('EuiButton')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="createFirstAlertEmptyPrompt"]').exists()).toBeTruthy(); }); - test('if click create button should render AlertAdd', () => { - wrapper - .find('[data-test-subj="createAlertButton"]') - .first() - .simulate('click'); + it('renders Create alert button', () => { + expect( + wrapper.find('[data-test-subj="createFirstAlertButton"]').find('EuiButton') + ).toHaveLength(1); expect(wrapper.find('AlertAdd')).toHaveLength(1); }); }); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx index 32de924f63e80..643816e728d1a 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx @@ -6,7 +6,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { Fragment, useEffect, useState } from 'react'; +import React, { useEffect, useState, Fragment } from 'react'; import { EuiBasicTable, EuiButton, @@ -15,6 +15,7 @@ import { EuiFlexItem, EuiIcon, EuiSpacer, + EuiEmptyPrompt, EuiLink, } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; @@ -245,100 +246,149 @@ export const AlertsList: React.FunctionComponent = () => { ); } - return ( - <section data-test-subj="alertsList"> - <Fragment> - <EuiSpacer size="m" /> - <AlertsContextProvider value={{ alertFlyoutVisible, setAlertFlyoutVisibility }}> - <EuiFlexGroup> - {selectedIds.length > 0 && canDelete && ( - <EuiFlexItem grow={false}> - <BulkOperationPopover> - <AlertQuickEditButtons - selectedItems={convertAlertsToTableItems( - filterAlertsById(alertsState.data, selectedIds), - alertTypesState.data - )} - onPerformingAction={() => setIsPerformingAction(true)} - onActionPerformed={() => { - loadAlertsData(); - setIsPerformingAction(false); - }} - /> - </BulkOperationPopover> - </EuiFlexItem> - )} - <EuiFlexItem> - <EuiFieldText - fullWidth - data-test-subj="alertSearchField" - prepend={<EuiIcon type="search" />} - onChange={e => setInputText(e.target.value)} - onKeyUp={e => { - if (e.keyCode === ENTER_KEY) { - setSearchText(inputText); - } - }} - placeholder={i18n.translate( - 'xpack.triggersActionsUI.sections.alertsList.searchPlaceholderTitle', - { defaultMessage: 'Search...' } + const emptyPrompt = ( + <EuiEmptyPrompt + iconType="watchesApp" + data-test-subj="createFirstAlertEmptyPrompt" + title={ + <h2> + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertsList.emptyTitle" + defaultMessage="Create your first alert" + /> + </h2> + } + body={ + <p> + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertsList.emptyDesc" + defaultMessage="Recieve an alert through email, slack or other connectors when a certain trigger is hit" + /> + </p> + } + actions={ + <EuiButton + data-test-subj="createFirstAlertButton" + key="create-action" + fill + iconType="plusInCircle" + iconSide="left" + onClick={() => setAlertFlyoutVisibility(true)} + > + <FormattedMessage + id="xpack.triggersActionsUI.sections.alertsList.emptyButton" + defaultMessage="Create alert" + /> + </EuiButton> + } + /> + ); + + const table = ( + <Fragment> + <EuiFlexGroup gutterSize="s"> + {selectedIds.length > 0 && canDelete && ( + <EuiFlexItem grow={false}> + <BulkOperationPopover> + <AlertQuickEditButtons + selectedItems={convertAlertsToTableItems( + filterAlertsById(alertsState.data, selectedIds), + alertTypesState.data )} + onPerformingAction={() => setIsPerformingAction(true)} + onActionPerformed={() => { + loadAlertsData(); + setIsPerformingAction(false); + }} /> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiFlexGroup> - {toolsRight.map((tool, index: number) => ( - <EuiFlexItem key={index} grow={false}> - {tool} - </EuiFlexItem> - ))} - </EuiFlexGroup> - </EuiFlexItem> + </BulkOperationPopover> + </EuiFlexItem> + )} + <EuiFlexItem> + <EuiFieldText + fullWidth + data-test-subj="alertSearchField" + prepend={<EuiIcon type="search" />} + onChange={e => setInputText(e.target.value)} + onKeyUp={e => { + if (e.keyCode === ENTER_KEY) { + setSearchText(inputText); + } + }} + placeholder={i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.searchPlaceholderTitle', + { defaultMessage: 'Search' } + )} + /> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <EuiFlexGroup gutterSize="s"> + {toolsRight.map((tool, index: number) => ( + <EuiFlexItem key={index} grow={false}> + {tool} + </EuiFlexItem> + ))} </EuiFlexGroup> + </EuiFlexItem> + </EuiFlexGroup> - {/* Large to remain consistent with ActionsList table spacing */} - <EuiSpacer size="l" /> + {/* Large to remain consistent with ActionsList table spacing */} + <EuiSpacer size="l" /> - <EuiBasicTable - loading={alertsState.isLoading || alertTypesState.isLoading || isPerformingAction} - /* Don't display alerts until we have the alert types initialized */ - items={ - alertTypesState.isInitialized === false - ? [] - : convertAlertsToTableItems(alertsState.data, alertTypesState.data) - } - itemId="id" - columns={alertsTableColumns} - rowProps={() => ({ - 'data-test-subj': 'alert-row', - })} - cellProps={() => ({ - 'data-test-subj': 'cell', - })} - data-test-subj="alertsList" - pagination={{ - pageIndex: page.index, - pageSize: page.size, - /* Don't display alert count until we have the alert types initialized */ - totalItemCount: - alertTypesState.isInitialized === false ? 0 : alertsState.totalItemCount, - }} - selection={ - canDelete - ? { - onSelectionChange(updatedSelectedItemsList: AlertTableItem[]) { - setSelectedIds(updatedSelectedItemsList.map(item => item.id)); - }, - } - : undefined - } - onChange={({ page: changedPage }: { page: Pagination }) => { - setPage(changedPage); - }} - /> - <AlertAdd refreshList={loadAlertsData} /> - </AlertsContextProvider> - </Fragment> + <EuiBasicTable + loading={alertsState.isLoading || alertTypesState.isLoading || isPerformingAction} + /* Don't display alerts until we have the alert types initialized */ + items={ + alertTypesState.isInitialized === false + ? [] + : convertAlertsToTableItems(alertsState.data, alertTypesState.data) + } + itemId="id" + columns={alertsTableColumns} + rowProps={() => ({ + 'data-test-subj': 'alert-row', + })} + cellProps={() => ({ + 'data-test-subj': 'cell', + })} + data-test-subj="alertsList" + pagination={{ + pageIndex: page.index, + pageSize: page.size, + /* Don't display alert count until we have the alert types initialized */ + totalItemCount: alertTypesState.isInitialized === false ? 0 : alertsState.totalItemCount, + }} + selection={ + canDelete + ? { + onSelectionChange(updatedSelectedItemsList: AlertTableItem[]) { + setSelectedIds(updatedSelectedItemsList.map(item => item.id)); + }, + } + : undefined + } + onChange={({ page: changedPage }: { page: Pagination }) => { + setPage(changedPage); + }} + /> + </Fragment> + ); + + return ( + <section data-test-subj="alertsList"> + <EuiSpacer size="m" /> + {convertAlertsToTableItems(alertsState.data, alertTypesState.data).length !== 0 && table} + {convertAlertsToTableItems(alertsState.data, alertTypesState.data).length === 0 && + emptyPrompt} + <AlertsContextProvider + value={{ + addFlyoutVisible: alertFlyoutVisible, + setAddFlyoutVisibility: setAlertFlyoutVisibility, + reloadAlerts: loadAlertsData, + }} + > + <AlertAdd /> + </AlertsContextProvider> </section> ); }; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts index 7fb7d0bf48e4d..1bed658940a6e 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts @@ -3,10 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { ActionType } from '../../../../../plugins/actions/common'; import { TypeRegistry } from './application/type_registry'; import { SanitizedAlert as Alert, AlertAction } from '../../../alerting/common'; -import { ActionType } from '../../../../../plugins/actions/common'; - export { Alert, AlertAction }; export { ActionType }; @@ -15,20 +14,20 @@ export type AlertTypeIndex = Record<string, AlertType>; export type ActionTypeRegistryContract = PublicMethodsOf<TypeRegistry<ActionTypeModel>>; export type AlertTypeRegistryContract = PublicMethodsOf<TypeRegistry<AlertTypeModel>>; -export interface ActionConnectorFieldsProps { - action: ActionConnector; +export interface ActionConnectorFieldsProps<TActionCOnnector> { + action: TActionCOnnector; editActionConfig: (property: string, value: any) => void; editActionSecrets: (property: string, value: any) => void; errors: { [key: string]: string[] }; - hasErrors?: boolean; } -export interface ActionParamsProps { - action: any; +export interface ActionParamsProps<TParams> { + actionParams: TParams; index: number; editAction: (property: string, value: any, index: number) => void; errors: { [key: string]: string[] }; - hasErrors?: boolean; + messageVariables?: string[]; + defaultMessage?: string; } export interface Pagination { @@ -40,10 +39,11 @@ export interface ActionTypeModel { id: string; iconClass: string; selectMessage: string; - validateConnector: (action: ActionConnector) => ValidationResult; + actionTypeTitle?: string; + validateConnector: (connector: any) => ValidationResult; validateParams: (actionParams: any) => ValidationResult; - actionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> | null; - actionParamsFields: React.FunctionComponent<ActionParamsProps> | null; + actionConnectorFields: React.FunctionComponent<any> | null; + actionParamsFields: any; } export interface ValidationResult { @@ -68,6 +68,8 @@ export interface ActionConnectorTableItem extends ActionConnector { export interface AlertType { id: string; name: string; + actionGroups: string[]; + actionVariables: string[]; } export type AlertWithoutId = Omit<Alert, 'id'>; @@ -83,6 +85,7 @@ export interface AlertTypeModel { iconClass: string; validate: (alert: Alert) => ValidationResult; alertParamsExpression: React.FunctionComponent<any>; + defaultActionMessage?: string; } export interface IErrorObject { diff --git a/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss b/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss index 6faad81630b2b..810176a57f9e3 100644 --- a/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss +++ b/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss @@ -3,3 +3,6 @@ // Styling within the app @import '../np_ready/public/application/sections/actions_connectors_list/components/index'; + +@import '../np_ready/public/application/sections/action_connector_form/index'; + diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts index 0276ca8109732..84081309c18d9 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -16,7 +16,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const testSubjects = getService('testSubjects'); const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); const supertest = getService('supertest'); - const retry = getService('retry'); + const find = getService('find'); async function createAlert() { const { body: createdAlert } = await supertest @@ -43,9 +43,52 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('alertsTab'); }); + it('should create an alert', async () => { + const alertName = generateUniqueKey(); + + await pageObjects.triggersActionsUI.clickCreateAlertButton(); + + const nameInput = await testSubjects.find('alertNameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(alertName); + + await testSubjects.click('threshold-SelectOption'); + + await testSubjects.click('.slack-ActionTypeSelectOption'); + await testSubjects.click('createActionConnectorButton'); + const connectorNameInput = await testSubjects.find('nameInput'); + await connectorNameInput.click(); + await connectorNameInput.clearValue(); + const connectorName = generateUniqueKey(); + await connectorNameInput.type(connectorName); + + const slackWebhookUrlInput = await testSubjects.find('slackWebhookUrlInput'); + await slackWebhookUrlInput.click(); + await slackWebhookUrlInput.clearValue(); + await slackWebhookUrlInput.type('https://test'); + + await find.clickByCssSelector('[data-test-subj="saveActionButtonModal"]:not(disabled)'); + + const loggingMessageInput = await testSubjects.find('slackMessageTextArea'); + await loggingMessageInput.click(); + await loggingMessageInput.clearValue(); + await loggingMessageInput.type('test message'); + + await testSubjects.click('slackAddVariableButton'); + const variableMenuButton = await testSubjects.find('variableMenuButton-0'); + await variableMenuButton.click(); + + await testSubjects.click('selectIndexExpression'); + + await find.clickByCssSelector('[data-test-subj="cancelSaveAlertButton"]'); + + // TODO: implement saving to the server, when threshold API will be ready + }); + it('should search for alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); @@ -61,7 +104,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should search for tags', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(`${createdAlert.name} foo`); const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); @@ -77,7 +120,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should disable single alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -95,7 +138,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should re-enable single alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -119,7 +162,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should mute single alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -137,7 +180,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should unmute single alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); @@ -161,24 +204,21 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should delete single alert', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click('collapsedItemActions'); await testSubjects.click('deleteAlert'); - - await retry.try(async () => { - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - - const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResults.length).to.eql(0); - }); + const emptyPrompt = await find.byCssSelector( + '[data-test-subj="createFirstAlertEmptyPrompt"]' + ); + expect(await emptyPrompt.elementHasClass('euiEmptyPrompt')).to.be(true); }); it('should mute all selection', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -201,7 +241,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should unmute all selection', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -226,7 +266,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should disable all selection', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -249,7 +289,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should enable all selection', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -274,7 +314,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { it('should delete all selection', async () => { const createdAlert = await createAlert(); - + await pageObjects.common.navigateToApp('triggersActions'); await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); await testSubjects.click(`checkboxSelectRow-${createdAlert.id}`); @@ -283,12 +323,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('deleteAll'); - await retry.try(async () => { - await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); - - const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); - expect(searchResults.length).to.eql(0); - }); + const emptyPrompt = await find.byCssSelector( + '[data-test-subj="createFirstAlertEmptyPrompt"]' + ); + expect(await emptyPrompt.elementHasClass('euiEmptyPrompt')).to.be(true); }); }); }; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts index d037155a29e12..9d656b08a3abd 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -35,7 +35,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.clearValue(); await nameInput.type(connectorName); - await find.clickByCssSelector('[data-test-subj="saveActionButton"]:not(disabled)'); + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Created '${connectorName}'`); @@ -65,7 +65,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.clearValue(); await nameInput.type(connectorName); - await find.clickByCssSelector('[data-test-subj="saveActionButton"]:not(disabled)'); + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); @@ -81,7 +81,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInputToUpdate.clearValue(); await nameInputToUpdate.type(updatedConnectorName); - await find.clickByCssSelector('[data-test-subj="saveActionButton"]:not(disabled)'); + await find.clickByCssSelector('[data-test-subj="saveEditedActionButton"]:not(disabled)'); const toastTitle = await pageObjects.common.closeToast(); expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`); @@ -109,7 +109,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.clearValue(); await nameInput.type(connectorName); - await find.clickByCssSelector('[data-test-subj="saveActionButton"]:not(disabled)'); + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); } const connectorName = generateUniqueKey(); @@ -147,7 +147,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await nameInput.clearValue(); await nameInput.type(connectorName); - await find.clickByCssSelector('[data-test-subj="saveActionButton"]:not(disabled)'); + await find.clickByCssSelector('[data-test-subj="saveNewActionButton"]:not(disabled)'); await pageObjects.common.closeToast(); } diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts index ae66ac0ddddfb..91c7fe1f97d12 100644 --- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -18,11 +18,21 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) async getSectionHeadingText() { return await testSubjects.getVisibleText('appTitle'); }, + async clickCreateFirstConnectorButton() { + const createBtn = await find.byCssSelector('[data-test-subj="createFirstActionButton"]'); + const createBtnIsVisible = await createBtn.isDisplayed(); + if (createBtnIsVisible) { + await createBtn.click(); + } + }, async clickCreateConnectorButton() { - const createBtn = await find.byCssSelector( - '[data-test-subj="createActionButton"],[data-test-subj="createFirstActionButton"]' - ); - await createBtn.click(); + const createBtn = await find.byCssSelector('[data-test-subj="createActionButton"]'); + const createBtnIsVisible = await createBtn.isDisplayed(); + if (createBtnIsVisible) { + await createBtn.click(); + } else { + await this.clickCreateFirstConnectorButton(); + } }, async searchConnectors(searchText: string) { const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); @@ -109,5 +119,11 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext) expect(valueAfter).not.to.eql(valueBefore); }); }, + async clickCreateAlertButton() { + const createBtn = await find.byCssSelector( + '[data-test-subj="createAlertButton"],[data-test-subj="createFirstAlertButton"]' + ); + await createBtn.click(); + }, }; } From 84fa97f89141ab01ba9f237ee6388c73ec8502df Mon Sep 17 00:00:00 2001 From: nnamdifrankie <56440728+nnamdifrankie@users.noreply.github.com> Date: Tue, 4 Feb 2020 14:22:30 -0500 Subject: [PATCH 47/60] [Endpoint] EMT-67: add kql support for endpoint list (#56328) [Endpoint] EMT-67: add kql support for endpoint list --- .../endpoint/server/routes/endpoints.test.ts | 65 ++++++++++++++++++- .../endpoint/server/routes/endpoints.ts | 28 +++++--- .../endpoint/endpoint_query_builders.test.ts | 59 +++++++++++++++++ .../endpoint/endpoint_query_builders.ts | 14 +++- .../apis/endpoint/endpoints.ts | 42 +++++++++++- 5 files changed, 194 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts index be14554f128c3..25c4225495a41 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts @@ -79,7 +79,7 @@ describe('test endpoint route', () => { expect(endpointResultList.request_page_size).toEqual(10); }); - it('test find the latest of all endpoints with params', async () => { + it('test find the latest of all endpoints with paging properties', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { paging_properties: [ @@ -112,6 +112,69 @@ describe('test endpoint route', () => { ); expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ + match_all: {}, + }); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as EndpointResultList; + expect(endpointResultList.endpoints.length).toEqual(2); + expect(endpointResultList.total).toEqual(2); + expect(endpointResultList.request_page_index).toEqual(10); + expect(endpointResultList.request_page_size).toEqual(10); + }); + + it('test find the latest of all endpoints with paging and filters properties', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + paging_properties: [ + { + page_size: 10, + }, + { + page_index: 1, + }, + ], + + filter: 'not host.ip:10.140.73.246', + }, + }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => + Promise.resolve((data as unknown) as SearchResponse<EndpointMetadata>) + ); + [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ + bool: { + must_not: { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + 'host.ip': '10.140.73.246', + }, + }, + ], + }, + }, + }, + }); expect(routeConfig.options).toEqual({ authRequired: true }); expect(mockResponse.ok).toBeCalled(); const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as EndpointResultList; diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.ts b/x-pack/plugins/endpoint/server/routes/endpoints.ts index 24ad8e3941f5e..054172a7f258a 100644 --- a/x-pack/plugins/endpoint/server/routes/endpoints.ts +++ b/x-pack/plugins/endpoint/server/routes/endpoints.ts @@ -26,16 +26,26 @@ export function registerEndpointRoutes(router: IRouter, endpointAppContext: Endp validate: { body: schema.nullable( schema.object({ - paging_properties: schema.arrayOf( - schema.oneOf([ - // the number of results to return for this request per page - schema.object({ - page_size: schema.number({ defaultValue: 10, min: 1, max: 10000 }), - }), - // the index of the page to return - schema.object({ page_index: schema.number({ defaultValue: 0, min: 0 }) }), - ]) + paging_properties: schema.nullable( + schema.arrayOf( + schema.oneOf([ + /** + * the number of results to return for this request per page + */ + schema.object({ + page_size: schema.number({ defaultValue: 10, min: 1, max: 10000 }), + }), + /** + * the zero based page index of the the total number of pages of page size + */ + schema.object({ page_index: schema.number({ defaultValue: 0, min: 0 }) }), + ]) + ) ), + /** + * filter to be applied, it could be a kql expression or discrete filter to be implemented + */ + filter: schema.nullable(schema.oneOf([schema.string()])), }) ), }, diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts index e453f777fbd50..bd9986ecf1f97 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts @@ -55,6 +55,65 @@ describe('query builder', () => { }); }); + describe('test query builder with kql filter', () => { + it('test default query params for all endpoints when no params or body is provided', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + filter: 'not host.ip:10.140.73.246', + }, + }); + const query = await kibanaRequestToEndpointListQuery(mockRequest, { + logFactory: loggingServiceMock.create(), + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + expect(query).toEqual({ + body: { + query: { + bool: { + must_not: { + bool: { + minimum_should_match: 1, + should: [ + { + match: { + 'host.ip': '10.140.73.246', + }, + }, + ], + }, + }, + }, + }, + collapse: { + field: 'host.id.keyword', + inner_hits: { + name: 'most_recent', + size: 1, + sort: [{ 'event.created': 'desc' }], + }, + }, + aggs: { + total: { + cardinality: { + field: 'host.id.keyword', + }, + }, + }, + sort: [ + { + 'event.created': { + order: 'desc', + }, + }, + ], + }, + from: 0, + size: 10, + index: 'endpoint-agent*', + } as Record<string, any>); + }); + }); + describe('EndpointFetchQuery', () => { it('searches for the correct ID', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts index b4f295a64b6ea..c143b09ec453c 100644 --- a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts @@ -6,6 +6,7 @@ import { KibanaRequest } from 'kibana/server'; import { EndpointAppConstants } from '../../../common/types'; import { EndpointAppContext } from '../../types'; +import { esKuery } from '../../../../../../src/plugins/data/server'; export const kibanaRequestToEndpointListQuery = async ( request: KibanaRequest<any, any, any>, @@ -14,9 +15,7 @@ export const kibanaRequestToEndpointListQuery = async ( const pagingProperties = await getPagingProperties(request, endpointAppContext); return { body: { - query: { - match_all: {}, - }, + query: buildQueryBody(request), collapse: { field: 'host.id.keyword', inner_hits: { @@ -66,6 +65,15 @@ async function getPagingProperties( }; } +function buildQueryBody(request: KibanaRequest<any, any, any>): Record<string, any> { + if (typeof request?.body?.filter === 'string') { + return esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(request.body.filter)); + } + return { + match_all: {}, + }; +} + export const kibanaRequestToEndpointFetchQuery = ( request: KibanaRequest<any, any, any>, endpointAppContext: EndpointAppContext diff --git a/x-pack/test/api_integration/apis/endpoint/endpoints.ts b/x-pack/test/api_integration/apis/endpoint/endpoints.ts index 1c520fe92e38e..210e9d78d7e18 100644 --- a/x-pack/test/api_integration/apis/endpoint/endpoints.ts +++ b/x-pack/test/api_integration/apis/endpoint/endpoints.ts @@ -40,7 +40,7 @@ export default function({ getService }: FtrProviderContext) { expect(body.request_page_index).to.eql(0); }); - it('endpoints api should return page based on params passed.', async () => { + it('endpoints api should return page based on paging properties passed.', async () => { const { body } = await supertest .post('/api/endpoint/endpoints') .set('kbn-xsrf', 'xxx') @@ -102,6 +102,46 @@ export default function({ getService }: FtrProviderContext) { .expect(400); expect(body.message).to.contain('Value is [0] but it must be equal to or greater than [1]'); }); + + it('endpoints api should return page based on filters passed.', async () => { + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send({ filter: 'not host.ip:10.101.149.26' }) + .expect(200); + expect(body.total).to.eql(2); + expect(body.endpoints.length).to.eql(2); + expect(body.request_page_size).to.eql(10); + expect(body.request_page_index).to.eql(0); + }); + + it('endpoints api should return page based on filters and paging passed.', async () => { + const notIncludedIp = '10.101.149.26'; + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send({ + paging_properties: [ + { + page_size: 10, + }, + { + page_index: 0, + }, + ], + filter: `not host.ip:${notIncludedIp}`, + }) + .expect(200); + expect(body.total).to.eql(2); + const resultIps: string[] = [].concat( + ...body.endpoints.map((metadata: Record<string, any>) => metadata.host.ip) + ); + expect(resultIps).to.eql(['10.192.213.130', '10.70.28.129', '10.46.229.234']); + expect(resultIps).not.include.eql(notIncludedIp); + expect(body.endpoints.length).to.eql(2); + expect(body.request_page_size).to.eql(10); + expect(body.request_page_index).to.eql(0); + }); }); }); } From 16d412f8f3022db0a93aff2c4ebf82fd97a06ce2 Mon Sep 17 00:00:00 2001 From: Matthew Kime <matt@mattki.me> Date: Tue, 4 Feb 2020 13:38:18 -0600 Subject: [PATCH 48/60] =?UTF-8?q?Advanced=20settings=20component=20registr?= =?UTF-8?q?y=20=E2=87=92=20kibana=20platform=20plugin=20(#55940)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * advanced settings component registry to new platform --- .i18nrc.json | 1 + rfcs/text/0006_management_section_service.md | 8 -- .../advanced_settings.test.tsx.snap | 12 +-- .../settings/advanced_settings.test.tsx | 18 ++++ .../sections/settings/advanced_settings.tsx | 19 ++-- .../component_registry.test.tsx.snap | 5 - .../components/component_registry.test.tsx | 82 ----------------- .../settings/components/component_registry.ts | 83 ----------------- .../default_component_registry.test.tsx | 44 --------- .../public/views/management/management.js | 9 +- src/legacy/ui/public/management/index.d.ts | 8 -- src/legacy/ui/public/management/index.js | 6 -- .../public/new_platform/__mocks__/helpers.ts | 3 + .../new_platform/new_platform.karma_mock.js | 7 ++ .../ui/public/new_platform/new_platform.ts | 6 ++ src/plugins/advanced_settings/kibana.json | 7 ++ .../component_registry.test.tsx.snap | 3 + .../component_registry.test.tsx | 90 ++++++++++++++++++ .../component_registry/component_registry.ts | 91 +++++++++++++++++++ .../public/component_registry/index.ts | 20 ++++ .../__snapshots__/page_footer.test.tsx.snap | 0 .../component_registry}/page_footer/index.ts | 0 .../page_footer/page_footer.test.tsx | 0 .../page_footer/page_footer.ts | 0 .../__snapshots__/page_subtitle.test.tsx.snap | 0 .../page_subtitle/index.ts | 0 .../page_subtitle/page_subtitle.test.tsx | 0 .../page_subtitle/page_subtitle.ts | 0 .../__snapshots__/page_title.test.tsx.snap | 2 +- .../component_registry}/page_title/index.ts | 0 .../page_title/page_title.test.tsx | 0 .../page_title/page_title.tsx | 2 +- src/plugins/advanced_settings/public/index.ts | 27 ++++++ src/plugins/advanced_settings/public/mocks.ts | 33 +++++++ .../advanced_settings/public/plugin.ts} | 28 +++--- src/plugins/advanced_settings/public/types.ts | 27 ++++++ .../advanced_settings_service.test.tsx | 21 ++--- .../advanced_settings_service.tsx | 22 +++-- x-pack/legacy/plugins/spaces/public/legacy.ts | 5 +- .../legacy/plugins/spaces/public/plugin.tsx | 11 +-- .../translations/translations/ja-JP.json | 2 +- .../translations/translations/zh-CN.json | 2 +- 42 files changed, 399 insertions(+), 305 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/settings/components/__snapshots__/component_registry.test.tsx.snap delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.test.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.ts delete mode 100644 src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.test.tsx create mode 100644 src/plugins/advanced_settings/kibana.json create mode 100644 src/plugins/advanced_settings/public/component_registry/__snapshots__/component_registry.test.tsx.snap create mode 100644 src/plugins/advanced_settings/public/component_registry/component_registry.test.tsx create mode 100644 src/plugins/advanced_settings/public/component_registry/component_registry.ts create mode 100644 src/plugins/advanced_settings/public/component_registry/index.ts rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_footer/__snapshots__/page_footer.test.tsx.snap (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_footer/index.ts (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_footer/page_footer.test.tsx (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_footer/page_footer.ts (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_subtitle/__snapshots__/page_subtitle.test.tsx.snap (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_subtitle/index.ts (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_subtitle/page_subtitle.test.tsx (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_subtitle/page_subtitle.ts (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_title/__snapshots__/page_title.test.tsx.snap (85%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_title/index.ts (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_title/page_title.test.tsx (100%) rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components => plugins/advanced_settings/public/component_registry}/page_title/page_title.tsx (91%) create mode 100644 src/plugins/advanced_settings/public/index.ts create mode 100644 src/plugins/advanced_settings/public/mocks.ts rename src/{legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.ts => plugins/advanced_settings/public/plugin.ts} (54%) create mode 100644 src/plugins/advanced_settings/public/types.ts diff --git a/.i18nrc.json b/.i18nrc.json index 7d7685b5c1ef1..e0acda70cc348 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -26,6 +26,7 @@ "src/legacy/core_plugins/management", "src/plugins/management" ], + "advancedSettings": "src/plugins/advanced_settings", "kibana_react": "src/legacy/core_plugins/kibana_react", "kibana-react": "src/plugins/kibana_react", "kibana_utils": "src/plugins/kibana_utils", diff --git a/rfcs/text/0006_management_section_service.md b/rfcs/text/0006_management_section_service.md index d9781e85cd8a9..1a52e85a4ff16 100644 --- a/rfcs/text/0006_management_section_service.md +++ b/rfcs/text/0006_management_section_service.md @@ -257,15 +257,7 @@ Current public contracts owned by the legacy service: ```js // ui/management/index interface API { - PAGE_TITLE_COMPONENT: string; // actually related to advanced settings? - PAGE_SUBTITLE_COMPONENT: string; // actually related to advanced settings? - PAGE_FOOTER_COMPONENT: string; // actually related to advanced settings? SidebarNav: React.FC<any>; - registerSettingsComponent: ( - id: string, - component: string | React.FC<any>, - allowOverride: boolean - ) => void; management: new ManagementSection(); MANAGEMENT_BREADCRUMB: { text: string; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.tsx.snap index 4814432c832e2..e76435fdb73b2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.tsx.snap @@ -6,7 +6,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = gutterSize="none" > <EuiFlexItem> - <advanced_settings_page_title /> + <foo_component /> </EuiFlexItem> <EuiFlexItem> <Search @@ -57,7 +57,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = /> </EuiFlexItem> </EuiFlexGroup> - <advanced_settings_page_subtitle /> + <foo_component /> <EuiSpacer size="m" /> @@ -139,7 +139,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = } showNoResultsMessage={true} /> - <advanced_settings_page_footer + <foo_component enableSaving={false} onQueryMatchChange={[Function]} query={ @@ -189,7 +189,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` gutterSize="none" > <EuiFlexItem> - <advanced_settings_page_title /> + <foo_component /> </EuiFlexItem> <EuiFlexItem> <Search @@ -240,7 +240,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` /> </EuiFlexItem> </EuiFlexGroup> - <advanced_settings_page_subtitle /> + <foo_component /> <EuiSpacer size="m" /> @@ -322,7 +322,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` } showNoResultsMessage={true} /> - <advanced_settings_page_footer + <foo_component enableSaving={true} onQueryMatchChange={[Function]} query={ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.tsx index 2a75c98b721bc..00b587c2e0fb5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.test.tsx @@ -27,7 +27,9 @@ import { UiSettingsType, } from '../../../../../../../core/public'; import { FieldSetting } from './types'; + import { AdvancedSettings } from './advanced_settings'; +jest.mock('ui/new_platform'); jest.mock('ui/new_platform', () => ({ npStart: mockConfig(), @@ -215,6 +217,22 @@ function mockConfig() { core: { uiSettings: config, }, + plugins: { + advancedSettings: { + component: { + register: jest.fn(), + get: () => { + const foo: React.ComponentType = () => <div>Hello</div>; + foo.displayName = 'foo_component'; + return foo; + }, + componentType: { + PAGE_TITLE_COMPONENT: 'page_title_component', + PAGE_SUBTITLE_COMPONENT: 'page_subtitle_component', + }, + }, + }, + }, }; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.tsx b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.tsx index 569ef73f2b453..c995b391d3d2d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.tsx +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/advanced_settings.tsx @@ -31,14 +31,6 @@ import { getAriaName, toEditableConfig, DEFAULT_CATEGORY } from './lib'; import { FieldSetting, IQuery } from './types'; -import { - registerDefaultComponents, - PAGE_TITLE_COMPONENT, - PAGE_SUBTITLE_COMPONENT, - PAGE_FOOTER_COMPONENT, -} from './components/default_component_registry'; -import { getSettingsComponent } from './components/component_registry'; - interface AdvancedSettingsProps { queryText: string; enableSaving: boolean; @@ -75,8 +67,6 @@ export class AdvancedSettings extends Component<AdvancedSettingsProps, AdvancedS footerQueryMatched: false, filteredSettings: this.mapSettings(Query.execute(parsedQuery, this.settings)), }; - - registerDefaultComponents(); } init(config: IUiSettingsClient) { @@ -166,10 +156,13 @@ export class AdvancedSettings extends Component<AdvancedSettingsProps, AdvancedS render() { const { filteredSettings, query, footerQueryMatched } = this.state; + const componentRegistry = npStart.plugins.advancedSettings.component; - const PageTitle = getSettingsComponent(PAGE_TITLE_COMPONENT); - const PageSubtitle = getSettingsComponent(PAGE_SUBTITLE_COMPONENT); - const PageFooter = getSettingsComponent(PAGE_FOOTER_COMPONENT); + const PageTitle = componentRegistry.get(componentRegistry.componentType.PAGE_TITLE_COMPONENT); + const PageSubtitle = componentRegistry.get( + componentRegistry.componentType.PAGE_SUBTITLE_COMPONENT + ); + const PageFooter = componentRegistry.get(componentRegistry.componentType.PAGE_FOOTER_COMPONENT); return ( <div> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/__snapshots__/component_registry.test.tsx.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/__snapshots__/component_registry.test.tsx.snap deleted file mode 100644 index 070b387057061..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/__snapshots__/component_registry.test.tsx.snap +++ /dev/null @@ -1,5 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`getSettingsComponent should throw an error when requesting a component that does not exist 1`] = `"Component not found with id does not exist"`; - -exports[`registerSettingsComponent should disallow registering a component with a duplicate id 1`] = `"Component with id test2 is already registered."`; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.test.tsx deleted file mode 100644 index 24e9e5dd3809c..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.test.tsx +++ /dev/null @@ -1,82 +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 React, { FunctionComponent } from 'react'; -import { - tryRegisterSettingsComponent, - registerSettingsComponent, - getSettingsComponent, -} from './component_registry'; - -describe('tryRegisterSettingsComponent', () => { - it('should allow a component to be registered', () => { - const component = () => <div />; - expect(tryRegisterSettingsComponent('tryTest1', component)).toEqual(true); - }); - - it('should return false if the component is already registered, and not allow an override', () => { - const component = () => <div />; - expect(tryRegisterSettingsComponent('tryTest2', component)).toEqual(true); - - const updatedComponent = () => <div />; - expect(tryRegisterSettingsComponent('tryTest2', updatedComponent)).toEqual(false); - expect(getSettingsComponent('tryTest2')).toBe(component); - }); -}); - -describe('registerSettingsComponent', () => { - it('should allow a component to be registered', () => { - const component = () => <div />; - registerSettingsComponent('test', component); - }); - - it('should disallow registering a component with a duplicate id', () => { - const component = () => <div />; - registerSettingsComponent('test2', component); - expect(() => registerSettingsComponent('test2', () => <span />)).toThrowErrorMatchingSnapshot(); - }); - - it('should allow a component to be overriden', () => { - const component = () => <div />; - registerSettingsComponent('test3', component); - - const anotherComponent = () => <span />; - registerSettingsComponent('test3', anotherComponent, true); - - expect(getSettingsComponent('test3')).toBe(anotherComponent); - }); - - it('should set a displayName for the component', () => { - const component = () => <div />; - registerSettingsComponent('display_name_component', component); - expect((component as FunctionComponent).displayName).toEqual('display_name_component'); - }); -}); - -describe('getSettingsComponent', () => { - it('should allow a component to be retrieved', () => { - const component = () => <div />; - registerSettingsComponent('test4', component); - expect(getSettingsComponent('test4')).toBe(component); - }); - - it('should throw an error when requesting a component that does not exist', () => { - expect(() => getSettingsComponent('does not exist')).toThrowErrorMatchingSnapshot(); - }); -}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.ts b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.ts deleted file mode 100644 index b58180c498edf..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/component_registry.ts +++ /dev/null @@ -1,83 +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 { ComponentType } from 'react'; - -type Id = string; -const registry: Record<Id, ComponentType<Record<string, any> | undefined>> = {}; - -/** - * Attempts to register the provided component. - * If a component with that ID is already registered, then the registration fails. - * - * @param {*} id the id of the component to register - * @param {*} component the component - */ -export function tryRegisterSettingsComponent( - id: Id, - component: ComponentType<Record<string, any> | undefined> -) { - if (id in registry) { - return false; - } - - registerSettingsComponent(id, component); - return true; -} - -/** - * Attempts to register the provided component, with the ability to optionally allow - * the component to override an existing one. - * - * If the intent is to override, then `allowOverride` must be set to true, otherwise an exception is thrown. - * - * @param {*} id the id of the component to register - * @param {*} component the component - * @param {*} allowOverride (default: false) - optional flag to allow this component to override a previously registered component - */ -export function registerSettingsComponent( - id: Id, - component: ComponentType<Record<string, any> | undefined>, - allowOverride = false -) { - if (!allowOverride && id in registry) { - throw new Error(`Component with id ${id} is already registered.`); - } - - // Setting a display name if one does not already exist. - // This enhances the snapshots, as well as the debugging experience. - if (!component.displayName) { - component.displayName = id; - } - - registry[id] = component; -} - -/** - * Retrieve a registered component by its ID. - * If the component does not exist, then an exception is thrown. - * - * @param {*} id the ID of the component to retrieve - */ -export function getSettingsComponent(id: Id): ComponentType<Record<string, any> | undefined> { - if (!(id in registry)) { - throw new Error(`Component not found with id ${id}`); - } - return registry[id]; -} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.test.tsx b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.test.tsx deleted file mode 100644 index ff3f75b79baef..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.test.tsx +++ /dev/null @@ -1,44 +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 React from 'react'; -import { registerDefaultComponents, PAGE_TITLE_COMPONENT } from './default_component_registry'; -import { getSettingsComponent, registerSettingsComponent } from './component_registry'; -import { PageTitle } from './page_title'; - -describe('default_component_registry', () => { - it('should register default components with the registry', () => { - registerDefaultComponents(); - expect(getSettingsComponent(PAGE_TITLE_COMPONENT)).toEqual(PageTitle); - }); - - it('should be able to call "registerDefaultComponents" several times without throwing', () => { - registerDefaultComponents(); - registerDefaultComponents(); - registerDefaultComponents(); - }); - - it('should not override components if they are already registered', () => { - const newComponent = () => <div />; - registerSettingsComponent(PAGE_TITLE_COMPONENT, newComponent, true); - registerDefaultComponents(); - - expect(getSettingsComponent(PAGE_TITLE_COMPONENT)).toEqual(newComponent); - }); -}); diff --git a/src/legacy/core_plugins/telemetry/public/views/management/management.js b/src/legacy/core_plugins/telemetry/public/views/management/management.js index 796caf1c8cfe6..7032775e391bb 100644 --- a/src/legacy/core_plugins/telemetry/public/views/management/management.js +++ b/src/legacy/core_plugins/telemetry/public/views/management/management.js @@ -19,7 +19,7 @@ import React from 'react'; import routes from 'ui/routes'; -import { registerSettingsComponent, PAGE_FOOTER_COMPONENT } from 'ui/management'; +import { npSetup } from 'ui/new_platform'; import { TelemetryOptInProvider } from '../../services'; import { TelemetryForm } from '../../components'; @@ -27,6 +27,7 @@ routes.defaults(/\/management/, { resolve: { telemetryManagementSection: function(Private) { const telemetryOptInProvider = Private(TelemetryOptInProvider); + const componentRegistry = npSetup.plugins.advancedSettings.component; const Component = props => ( <TelemetryForm @@ -36,7 +37,11 @@ routes.defaults(/\/management/, { /> ); - registerSettingsComponent(PAGE_FOOTER_COMPONENT, Component, true); + componentRegistry.register( + componentRegistry.componentType.PAGE_FOOTER_COMPONENT, + Component, + true + ); }, }, }); diff --git a/src/legacy/ui/public/management/index.d.ts b/src/legacy/ui/public/management/index.d.ts index 7880e1d5d0295..529efd36623a3 100644 --- a/src/legacy/ui/public/management/index.d.ts +++ b/src/legacy/ui/public/management/index.d.ts @@ -18,15 +18,7 @@ */ declare module 'ui/management' { - export const PAGE_TITLE_COMPONENT: string; - export const PAGE_SUBTITLE_COMPONENT: string; - export const PAGE_FOOTER_COMPONENT: string; export const SidebarNav: React.FC<any>; - export function registerSettingsComponent( - id: string, - component: string | React.FC<any>, - allowOverride: boolean - ): void; export const management: any; // TODO - properly provide types export const MANAGEMENT_BREADCRUMB: { text: string; diff --git a/src/legacy/ui/public/management/index.js b/src/legacy/ui/public/management/index.js index b2f1946dbc59c..25d3678c5dbba 100644 --- a/src/legacy/ui/public/management/index.js +++ b/src/legacy/ui/public/management/index.js @@ -17,12 +17,6 @@ * under the License. */ -export { - PAGE_TITLE_COMPONENT, - PAGE_SUBTITLE_COMPONENT, - PAGE_FOOTER_COMPONENT, -} from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry'; -export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry'; export { MANAGEMENT_BREADCRUMB } from './breadcrumbs'; import { npStart } from 'ui/new_platform'; export const management = npStart.plugins.management.legacy; diff --git a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts index 439ac9b5713df..cf24b0e9675fa 100644 --- a/src/legacy/ui/public/new_platform/__mocks__/helpers.ts +++ b/src/legacy/ui/public/new_platform/__mocks__/helpers.ts @@ -29,6 +29,7 @@ import { managementPluginMock } from '../../../../../plugins/management/public/m import { usageCollectionPluginMock } from '../../../../../plugins/usage_collection/public/mocks'; import { kibanaLegacyPluginMock } from '../../../../../plugins/kibana_legacy/public/mocks'; import { chartPluginMock } from '../../../../../plugins/charts/public/mocks'; +import { advancedSettingsMock } from '../../../../../plugins/advanced_settings/public/mocks'; /* eslint-enable @kbn/eslint/no-restricted-paths */ export const pluginsMock = { @@ -41,6 +42,7 @@ export const pluginsMock = { expressions: expressionsPluginMock.createSetupContract(), uiActions: uiActionsPluginMock.createSetupContract(), usageCollection: usageCollectionPluginMock.createSetupContract(), + advancedSettings: advancedSettingsMock.createSetupContract(), kibana_legacy: kibanaLegacyPluginMock.createSetupContract(), }), createStart: () => ({ @@ -52,6 +54,7 @@ export const pluginsMock = { expressions: expressionsPluginMock.createStartContract(), uiActions: uiActionsPluginMock.createStartContract(), management: managementPluginMock.createStartContract(), + advancedSettings: advancedSettingsMock.createStartContract(), kibana_legacy: kibanaLegacyPluginMock.createStartContract(), }), }; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index c2c8b5a0fae7a..7f4f67ebb06d3 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -20,6 +20,7 @@ import sinon from 'sinon'; import { getFieldFormatsRegistry } from '../../../../test_utils/public/stub_field_formats'; import { METRIC_TYPE } from '@kbn/analytics'; +import { ComponentRegistry } from '../../../../../src/plugins/advanced_settings/public/'; const mockObservable = () => { return { @@ -58,6 +59,12 @@ const mockCore = { export const npSetup = { core: mockCore, plugins: { + advancedSettings: { + component: { + register: sinon.fake(), + componentType: ComponentRegistry.componentType, + }, + }, usageCollection: { allowTrackUserAgent: sinon.fake(), reportUiStats: sinon.fake(), diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index 2ade98ec54efd..62abe2eb9b5ba 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -32,6 +32,10 @@ import { DevToolsSetup, DevToolsStart } from '../../../../plugins/dev_tools/publ import { KibanaLegacySetup, KibanaLegacyStart } from '../../../../plugins/kibana_legacy/public'; import { HomePublicPluginSetup, HomePublicPluginStart } from '../../../../plugins/home/public'; import { SharePluginSetup, SharePluginStart } from '../../../../plugins/share/public'; +import { + AdvancedSettingsSetup, + AdvancedSettingsStart, +} from '../../../../plugins/advanced_settings/public'; import { ManagementSetup, ManagementStart } from '../../../../plugins/management/public'; import { BfetchPublicSetup, BfetchPublicStart } from '../../../../plugins/bfetch/public'; import { UsageCollectionSetup } from '../../../../plugins/usage_collection/public'; @@ -54,6 +58,7 @@ export interface PluginsSetup { kibana_legacy: KibanaLegacySetup; share: SharePluginSetup; usageCollection: UsageCollectionSetup; + advancedSettings: AdvancedSettingsSetup; management: ManagementSetup; } @@ -71,6 +76,7 @@ export interface PluginsStart { kibana_legacy: KibanaLegacyStart; share: SharePluginStart; management: ManagementStart; + advancedSettings: AdvancedSettingsStart; } export const npSetup = { diff --git a/src/plugins/advanced_settings/kibana.json b/src/plugins/advanced_settings/kibana.json new file mode 100644 index 0000000000000..5fc1e916ae45f --- /dev/null +++ b/src/plugins/advanced_settings/kibana.json @@ -0,0 +1,7 @@ +{ + "id": "advancedSettings", + "version": "kibana", + "server": false, + "ui": true, + "requiredPlugins": [] +} diff --git a/src/plugins/advanced_settings/public/component_registry/__snapshots__/component_registry.test.tsx.snap b/src/plugins/advanced_settings/public/component_registry/__snapshots__/component_registry.test.tsx.snap new file mode 100644 index 0000000000000..1d6cc882cb344 --- /dev/null +++ b/src/plugins/advanced_settings/public/component_registry/__snapshots__/component_registry.test.tsx.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ComponentRegistry register should disallow registering a component with a duplicate id 1`] = `"Component with id advanced_settings_page_title is already registered."`; diff --git a/src/plugins/advanced_settings/public/component_registry/component_registry.test.tsx b/src/plugins/advanced_settings/public/component_registry/component_registry.test.tsx new file mode 100644 index 0000000000000..3b722e9517fdb --- /dev/null +++ b/src/plugins/advanced_settings/public/component_registry/component_registry.test.tsx @@ -0,0 +1,90 @@ +/* + * 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 React from 'react'; +import { ComponentRegistry } from './component_registry'; + +describe('ComponentRegistry', () => { + describe('register', () => { + it('should allow a component to be registered', () => { + const component = () => <div />; + new ComponentRegistry().setup.register( + ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, + component + ); + }); + + it('should disallow registering a component with a duplicate id', () => { + const registry = new ComponentRegistry(); + const component = () => <div />; + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component); + expect(() => + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, () => ( + <span /> + )) + ).toThrowErrorMatchingSnapshot(); + }); + + it('should allow a component to be overriden', () => { + const registry = new ComponentRegistry(); + const component = () => <div />; + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component); + + const anotherComponent = () => <span />; + registry.setup.register( + ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, + anotherComponent, + true + ); + + expect(registry.start.get(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT)).toBe( + anotherComponent + ); + }); + }); + + describe('get', () => { + it('should allow a component to be retrieved', () => { + const registry = new ComponentRegistry(); + const component = () => <div />; + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component); + expect(registry.start.get(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT)).toBe( + component + ); + }); + }); + + it('should set a displayName for the component if one does not exist', () => { + const component: React.ComponentType = () => <div />; + const registry = new ComponentRegistry(); + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component); + + expect(component.displayName).toEqual(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT); + }); + + it('should not set a displayName for the component if one already exists', () => { + const component: React.ComponentType = () => <div />; + component.displayName = '<AwesomeComponent>'; + const registry = new ComponentRegistry(); + + registry.setup.register(ComponentRegistry.componentType.PAGE_TITLE_COMPONENT, component); + + expect(component.displayName).toEqual('<AwesomeComponent>'); + }); +}); diff --git a/src/plugins/advanced_settings/public/component_registry/component_registry.ts b/src/plugins/advanced_settings/public/component_registry/component_registry.ts new file mode 100644 index 0000000000000..cc61798e84cb7 --- /dev/null +++ b/src/plugins/advanced_settings/public/component_registry/component_registry.ts @@ -0,0 +1,91 @@ +/* + * 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 { ComponentType } from 'react'; +import { PageTitle } from './page_title'; +import { PageSubtitle } from './page_subtitle'; +import { PageFooter } from './page_footer'; + +type Id = + | 'advanced_settings_page_title' + | 'advanced_settings_page_subtitle' + | 'advanced_settings_page_footer'; + +const componentType: { [key: string]: Id } = { + PAGE_TITLE_COMPONENT: 'advanced_settings_page_title' as Id, + PAGE_SUBTITLE_COMPONENT: 'advanced_settings_page_subtitle' as Id, + PAGE_FOOTER_COMPONENT: 'advanced_settings_page_footer' as Id, +}; + +type RegistryComponent = ComponentType<Record<string, any> | undefined>; + +export class ComponentRegistry { + static readonly componentType = componentType; + static readonly defaultRegistry: Record<Id, RegistryComponent> = { + advanced_settings_page_title: PageTitle, + advanced_settings_page_subtitle: PageSubtitle, + advanced_settings_page_footer: PageFooter, + }; + + registry: { [key in Id]?: RegistryComponent } = {}; + + /** + * Attempts to register the provided component, with the ability to optionally allow + * the component to override an existing one. + * + * If the intent is to override, then `allowOverride` must be set to true, otherwise an exception is thrown. + * + * @param {*} id the id of the component to register + * @param {*} component the component + * @param {*} allowOverride (default: false) - optional flag to allow this component to override a previously registered component + */ + private register(id: Id, component: RegistryComponent, allowOverride = false) { + if (!allowOverride && id in this.registry) { + throw new Error(`Component with id ${id} is already registered.`); + } + + // Setting a display name if one does not already exist. + // This enhances the snapshots, as well as the debugging experience. + if (!component.displayName) { + component.displayName = id; + } + + this.registry[id] = component; + } + + /** + * Retrieve a registered component by its ID. + * If the component does not exist, then an exception is thrown. + * + * @param {*} id the ID of the component to retrieve + */ + private get(id: Id): RegistryComponent { + return this.registry[id] || ComponentRegistry.defaultRegistry[id]; + } + + setup = { + componentType: ComponentRegistry.componentType, + register: this.register.bind(this), + }; + + start = { + componentType: ComponentRegistry.componentType, + get: this.get.bind(this), + }; +} diff --git a/src/plugins/advanced_settings/public/component_registry/index.ts b/src/plugins/advanced_settings/public/component_registry/index.ts new file mode 100644 index 0000000000000..79c9248e0c2a9 --- /dev/null +++ b/src/plugins/advanced_settings/public/component_registry/index.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export { ComponentRegistry } from './component_registry'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.tsx.snap b/src/plugins/advanced_settings/public/component_registry/page_footer/__snapshots__/page_footer.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/__snapshots__/page_footer.test.tsx.snap rename to src/plugins/advanced_settings/public/component_registry/page_footer/__snapshots__/page_footer.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.ts b/src/plugins/advanced_settings/public/component_registry/page_footer/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/index.ts rename to src/plugins/advanced_settings/public/component_registry/page_footer/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.test.tsx rename to src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.ts b/src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_footer/page_footer.ts rename to src/plugins/advanced_settings/public/component_registry/page_footer/page_footer.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/__snapshots__/page_subtitle.test.tsx.snap b/src/plugins/advanced_settings/public/component_registry/page_subtitle/__snapshots__/page_subtitle.test.tsx.snap similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/__snapshots__/page_subtitle.test.tsx.snap rename to src/plugins/advanced_settings/public/component_registry/page_subtitle/__snapshots__/page_subtitle.test.tsx.snap diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/index.ts b/src/plugins/advanced_settings/public/component_registry/page_subtitle/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/index.ts rename to src/plugins/advanced_settings/public/component_registry/page_subtitle/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/page_subtitle.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/page_subtitle.test.tsx rename to src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/page_subtitle.ts b/src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_subtitle/page_subtitle.ts rename to src/plugins/advanced_settings/public/component_registry/page_subtitle/page_subtitle.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/__snapshots__/page_title.test.tsx.snap b/src/plugins/advanced_settings/public/component_registry/page_title/__snapshots__/page_title.test.tsx.snap similarity index 85% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/__snapshots__/page_title.test.tsx.snap rename to src/plugins/advanced_settings/public/component_registry/page_title/__snapshots__/page_title.test.tsx.snap index 8dd4e501067b5..10b799a986b84 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/__snapshots__/page_title.test.tsx.snap +++ b/src/plugins/advanced_settings/public/component_registry/page_title/__snapshots__/page_title.test.tsx.snap @@ -7,7 +7,7 @@ exports[`PageTitle should render normally 1`] = ` > <FormattedMessage defaultMessage="Settings" - id="kbn.management.settings.pageTitle" + id="advancedSettings.pageTitle" values={Object {}} /> </h1> diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/index.ts b/src/plugins/advanced_settings/public/component_registry/page_title/index.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/index.ts rename to src/plugins/advanced_settings/public/component_registry/page_title/index.ts diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/page_title.test.tsx b/src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx similarity index 100% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/page_title.test.tsx rename to src/plugins/advanced_settings/public/component_registry/page_title/page_title.test.tsx diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/page_title.tsx b/src/plugins/advanced_settings/public/component_registry/page_title/page_title.tsx similarity index 91% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/page_title.tsx rename to src/plugins/advanced_settings/public/component_registry/page_title/page_title.tsx index cb807302c2380..18d9c60d331bb 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/page_title/page_title.tsx +++ b/src/plugins/advanced_settings/public/component_registry/page_title/page_title.tsx @@ -25,7 +25,7 @@ export const PageTitle = () => { return ( <EuiText> <h1 data-test-subj="managementSettingsTitle"> - <FormattedMessage id="kbn.management.settings.pageTitle" defaultMessage="Settings" /> + <FormattedMessage id="advancedSettings.pageTitle" defaultMessage="Settings" /> </h1> </EuiText> ); diff --git a/src/plugins/advanced_settings/public/index.ts b/src/plugins/advanced_settings/public/index.ts new file mode 100644 index 0000000000000..13be36e671f75 --- /dev/null +++ b/src/plugins/advanced_settings/public/index.ts @@ -0,0 +1,27 @@ +/* + * 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 { PluginInitializerContext } from 'kibana/public'; +import { AdvancedSettingsPlugin } from './plugin'; +export { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; +export { ComponentRegistry } from './component_registry'; + +export function plugin(initializerContext: PluginInitializerContext) { + return new AdvancedSettingsPlugin(); +} diff --git a/src/plugins/advanced_settings/public/mocks.ts b/src/plugins/advanced_settings/public/mocks.ts new file mode 100644 index 0000000000000..e147f57101aae --- /dev/null +++ b/src/plugins/advanced_settings/public/mocks.ts @@ -0,0 +1,33 @@ +/* + * 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 { ComponentRegistry } from './component_registry'; + +const register = jest.fn(); +const get = jest.fn(); +const componentType = ComponentRegistry.componentType; + +export const advancedSettingsMock = { + createSetupContract() { + return { register, componentType }; + }, + createStartContract() { + return { get, componentType }; + }, +}; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.ts b/src/plugins/advanced_settings/public/plugin.ts similarity index 54% rename from src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.ts rename to src/plugins/advanced_settings/public/plugin.ts index 80b2f2e79b9c7..692e515ca4e5e 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/default_component_registry.ts +++ b/src/plugins/advanced_settings/public/plugin.ts @@ -17,17 +17,23 @@ * under the License. */ -import { tryRegisterSettingsComponent } from './component_registry'; -import { PageTitle } from './page_title'; -import { PageSubtitle } from './page_subtitle'; -import { PageFooter } from './page_footer'; +import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { ComponentRegistry } from './component_registry'; +import { AdvancedSettingsSetup, AdvancedSettingsStart } from './types'; -export const PAGE_TITLE_COMPONENT = 'advanced_settings_page_title'; -export const PAGE_SUBTITLE_COMPONENT = 'advanced_settings_page_subtitle'; -export const PAGE_FOOTER_COMPONENT = 'advanced_settings_page_footer'; +const component = new ComponentRegistry(); -export function registerDefaultComponents() { - tryRegisterSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle); - tryRegisterSettingsComponent(PAGE_SUBTITLE_COMPONENT, PageSubtitle); - tryRegisterSettingsComponent(PAGE_FOOTER_COMPONENT, PageFooter); +export class AdvancedSettingsPlugin + implements Plugin<AdvancedSettingsSetup, AdvancedSettingsStart> { + public setup(core: CoreSetup) { + return { + component: component.setup, + }; + } + + public start(core: CoreStart) { + return { + component: component.start, + }; + } } diff --git a/src/plugins/advanced_settings/public/types.ts b/src/plugins/advanced_settings/public/types.ts new file mode 100644 index 0000000000000..a9b965c3c22de --- /dev/null +++ b/src/plugins/advanced_settings/public/types.ts @@ -0,0 +1,27 @@ +/* + * 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 { ComponentRegistry } from './component_registry'; + +export interface AdvancedSettingsSetup { + component: ComponentRegistry['setup']; +} +export interface AdvancedSettingsStart { + component: ComponentRegistry['start']; +} diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx index aa3c6acf26236..3c6b2332bbbdc 100644 --- a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx @@ -5,33 +5,30 @@ */ import { AdvancedSettingsService } from './advanced_settings_service'; -jest.mock('ui/management', () => { - return { - PAGE_TITLE_COMPONENT: 'page_title_component', - PAGE_SUBTITLE_COMPONENT: 'page_subtitle_component', - }; -}); +import { advancedSettingsMock } from '../../../../../../src/plugins/advanced_settings/public/mocks'; + +const componentRegistryMock = advancedSettingsMock.createSetupContract(); describe('Advanced Settings Service', () => { describe('#setup', () => { it('registers space-aware components to augment the advanced settings screen', () => { const deps = { getActiveSpace: jest.fn().mockResolvedValue({ id: 'foo', name: 'foo-space' }), - registerSettingsComponent: jest.fn(), + componentRegistry: componentRegistryMock, }; const advancedSettingsService = new AdvancedSettingsService(); advancedSettingsService.setup(deps); - expect(deps.registerSettingsComponent).toHaveBeenCalledTimes(2); - expect(deps.registerSettingsComponent).toHaveBeenCalledWith( - 'page_title_component', + expect(deps.componentRegistry.register).toHaveBeenCalledTimes(2); + expect(deps.componentRegistry.register).toHaveBeenCalledWith( + componentRegistryMock.componentType.PAGE_TITLE_COMPONENT, expect.any(Function), true ); - expect(deps.registerSettingsComponent).toHaveBeenCalledWith( - 'page_subtitle_component', + expect(deps.componentRegistry.register).toHaveBeenCalledWith( + componentRegistryMock.componentType.PAGE_SUBTITLE_COMPONENT, expect.any(Function), true ); diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx index 9c6c2fcc2cdda..a1552add18f2d 100644 --- a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx @@ -4,25 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { PAGE_TITLE_COMPONENT, PAGE_SUBTITLE_COMPONENT } from 'ui/management'; import { Space } from '../../common/model/space'; import { AdvancedSettingsTitle, AdvancedSettingsSubtitle } from './components'; +import { AdvancedSettingsSetup } from '../../../../../../src/plugins/advanced_settings/public'; interface SetupDeps { getActiveSpace: () => Promise<Space>; - registerSettingsComponent: ( - id: string, - component: string | React.FC<any>, - allowOverride: boolean - ) => void; + componentRegistry: AdvancedSettingsSetup['component']; } export class AdvancedSettingsService { - public setup({ getActiveSpace, registerSettingsComponent }: SetupDeps) { + public setup({ getActiveSpace, componentRegistry }: SetupDeps) { const PageTitle = () => <AdvancedSettingsTitle getActiveSpace={getActiveSpace} />; const SubTitle = () => <AdvancedSettingsSubtitle getActiveSpace={getActiveSpace} />; - registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); - registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); + componentRegistry.register( + componentRegistry.componentType.PAGE_TITLE_COMPONENT, + PageTitle, + true + ); + componentRegistry.register( + componentRegistry.componentType.PAGE_SUBTITLE_COMPONENT, + SubTitle, + true + ); } } diff --git a/x-pack/legacy/plugins/spaces/public/legacy.ts b/x-pack/legacy/plugins/spaces/public/legacy.ts index 1dffbd2661714..200cae5498595 100644 --- a/x-pack/legacy/plugins/spaces/public/legacy.ts +++ b/x-pack/legacy/plugins/spaces/public/legacy.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { registerSettingsComponent } from 'ui/management'; import { npSetup, npStart } from 'ui/new_platform'; import { setup as managementSetup } from '../../../../../src/legacy/core_plugins/management/public/legacy'; import { plugin } from '.'; @@ -16,9 +15,7 @@ const spacesPlugin: SpacesPlugin = plugin(); const pluginsSetup: PluginsSetup = { home: npSetup.plugins.home, management: managementSetup, - __managementLegacyCompat: { - registerSettingsComponent, - }, + advancedSettings: npSetup.plugins.advancedSettings, }; const pluginsStart: PluginsStart = { diff --git a/x-pack/legacy/plugins/spaces/public/plugin.tsx b/x-pack/legacy/plugins/spaces/public/plugin.tsx index 1ddb69a5b595c..e6271ac3a0a70 100644 --- a/x-pack/legacy/plugins/spaces/public/plugin.tsx +++ b/x-pack/legacy/plugins/spaces/public/plugin.tsx @@ -8,6 +8,7 @@ import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { HomePublicPluginSetup } from 'src/plugins/home/public'; import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; import { ManagementStart } from 'src/plugins/management/public'; +import { AdvancedSettingsSetup } from 'src/plugins/advanced_settings/public'; import { SpacesManager } from './spaces_manager'; import { initSpacesNavControl } from './nav_control'; import { createSpacesFeatureCatalogueEntry } from './create_feature_catalogue_entry'; @@ -22,13 +23,7 @@ export interface SpacesPluginStart { export interface PluginsSetup { home?: HomePublicPluginSetup; management: ManagementSetup; - __managementLegacyCompat: { - registerSettingsComponent: ( - id: string, - component: string | React.FC<any>, - allowOverride: boolean - ) => void; - }; + advancedSettings: AdvancedSettingsSetup; } export interface PluginsStart { @@ -53,7 +48,7 @@ export class SpacesPlugin implements Plugin<void, SpacesPluginStart, PluginsSetu const advancedSettingsService = new AdvancedSettingsService(); advancedSettingsService.setup({ getActiveSpace: () => this.spacesManager.getActiveSpace(), - registerSettingsComponent: plugins.__managementLegacyCompat.registerSettingsComponent, + componentRegistry: plugins.advancedSettings.component, }); if (plugins.home) { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index abf56cd2d05b6..58d97ce263bea 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1624,7 +1624,7 @@ "kbn.management.settings.form.clearSearchResultText": "(検索結果を消去)", "kbn.management.settings.form.noSearchResultText": "設定が見つかりませんでした {clearSearch}", "kbn.management.settings.form.searchResultText": "検索用語により {settingsCount} 件の設定が非表示になっています {clearSearch}", - "kbn.management.settings.pageTitle": "設定", + "advancedSettings.pageTitle": "設定", "kbn.management.settings.searchBar.unableToParseQueryErrorMessage": "クエリをパースできません", "kbn.management.settings.searchBarAriaLabel": "高度な設定を検索", "kbn.management.settings.sectionLabel": "高度な設定", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b97982b5c99da..5d62e15be2b9f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1624,7 +1624,7 @@ "kbn.management.settings.form.clearSearchResultText": "(清除搜索)", "kbn.management.settings.form.noSearchResultText": "未找到设置{clearSearch}", "kbn.management.settings.form.searchResultText": "搜索词隐藏了 {settingsCount} 个设置{clearSearch}", - "kbn.management.settings.pageTitle": "设置", + "advancedSettings.pageTitle": "设置", "kbn.management.settings.searchBar.unableToParseQueryErrorMessage": "无法解析查询", "kbn.management.settings.searchBarAriaLabel": "搜索高级设置", "kbn.management.settings.sectionLabel": "高级设置", From 74768e567ba7b66496002c401ad0a9be3b69e334 Mon Sep 17 00:00:00 2001 From: Tyler Smalley <tyler.smalley@elastic.co> Date: Tue, 4 Feb 2020 11:49:38 -0800 Subject: [PATCH 49/60] Bumps terser-webpack-plugin to 2.3.4 (#56662) We're seeing occasional "Error: Call retries were exceeded" exception with Terser, which should be resolved by https://github.com/webpack-contrib/terser-webpack-plugin/commit/abfd9506207cf392de63a0629de82145efff2361, first included in 2.3.4 Signed-off-by: Tyler Smalley <tyler.smalley@elastic.co> --- package.json | 2 +- packages/kbn-pm/dist/index.js | 21114 ++++++++++++++++++-------------- yarn.lock | 221 +- 3 files changed, 11897 insertions(+), 9440 deletions(-) diff --git a/package.json b/package.json index f9a3bfd99b109..c9376e974492a 100644 --- a/package.json +++ b/package.json @@ -262,7 +262,7 @@ "style-loader": "0.23.1", "symbol-observable": "^1.2.0", "tar": "4.4.13", - "terser-webpack-plugin": "^2.1.2", + "terser-webpack-plugin": "^2.3.4", "thread-loader": "^2.1.3", "tinygradient": "0.4.3", "tinymath": "1.2.1", diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 8bded9d403c21..8d17d285dce21 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(703); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(705); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); @@ -152,7 +152,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(689); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(690); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -2507,8 +2507,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(18); /* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(587); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(686); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(687); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(687); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(688); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -46624,9 +46624,11 @@ function range(a, b, str) { try { var util = __webpack_require__(29); + /* istanbul ignore next */ if (typeof util.inherits !== 'function') throw ''; module.exports = util.inherits; } catch (e) { + /* istanbul ignore next */ module.exports = __webpack_require__(510); } @@ -46638,24 +46640,28 @@ try { if (typeof Object.create === 'function') { // implementation from standard node.js 'util' module module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } }; } else { // old school shim for old browsers module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } } } @@ -68970,7 +68976,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(588); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(675); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(676); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); @@ -69078,13 +69084,13 @@ const CleanCommand = { const {promisify} = __webpack_require__(29); const path = __webpack_require__(16); const globby = __webpack_require__(589); -const isGlob = __webpack_require__(601); -const slash = __webpack_require__(662); -const gracefulFs = __webpack_require__(664); -const isPathCwd = __webpack_require__(668); -const isPathInside = __webpack_require__(669); -const rimraf = __webpack_require__(670); -const pMap = __webpack_require__(671); +const isGlob = __webpack_require__(606); +const slash = __webpack_require__(667); +const gracefulFs = __webpack_require__(22); +const isPathCwd = __webpack_require__(669); +const isPathInside = __webpack_require__(670); +const rimraf = __webpack_require__(671); +const pMap = __webpack_require__(672); const rimrafP = promisify(rimraf); @@ -69206,11 +69212,11 @@ module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options const fs = __webpack_require__(23); const arrayUnion = __webpack_require__(590); const merge2 = __webpack_require__(591); -const glob = __webpack_require__(502); -const fastGlob = __webpack_require__(592); -const dirGlob = __webpack_require__(658); -const gitignore = __webpack_require__(660); -const {FilterStream, UniqueStream} = __webpack_require__(663); +const glob = __webpack_require__(592); +const fastGlob = __webpack_require__(597); +const dirGlob = __webpack_require__(663); +const gitignore = __webpack_require__(665); +const {FilterStream, UniqueStream} = __webpack_require__(668); const DEFAULT_FILTER = () => false; @@ -69512,5704 +69518,7286 @@ function pauseStreams (streams, options) { /* 592 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const taskManager = __webpack_require__(593); -const async_1 = __webpack_require__(621); -const stream_1 = __webpack_require__(654); -const sync_1 = __webpack_require__(655); -const settings_1 = __webpack_require__(657); -const utils = __webpack_require__(594); -function FastGlob(source, options) { - try { - assertPatternsInput(source); - } - catch (error) { - return Promise.reject(error); - } - const works = getWorks(source, async_1.default, options); - return Promise.all(works).then(utils.array.flatten); -} -(function (FastGlob) { - function sync(source, options) { - assertPatternsInput(source); - const works = getWorks(source, sync_1.default, options); - return utils.array.flatten(works); - } - FastGlob.sync = sync; - function stream(source, options) { - assertPatternsInput(source); - const works = getWorks(source, stream_1.default, options); - /** - * The stream returned by the provider cannot work with an asynchronous iterator. - * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. - * This affects performance (+25%). I don't see best solution right now. - */ - return utils.stream.merge(works); - } - FastGlob.stream = stream; - function generateTasks(source, options) { - assertPatternsInput(source); - const patterns = [].concat(source); - const settings = new settings_1.default(options); - return taskManager.generate(patterns, settings); - } - FastGlob.generateTasks = generateTasks; -})(FastGlob || (FastGlob = {})); -function getWorks(source, _Provider, options) { - const patterns = [].concat(source); - const settings = new settings_1.default(options); - const tasks = taskManager.generate(patterns, settings); - const provider = new _Provider(settings); - return tasks.map(provider.read, provider); -} -function assertPatternsInput(source) { - if ([].concat(source).every(isString)) { - return; - } - throw new TypeError('Patterns must be a string or an array of strings'); -} -function isString(source) { - /* tslint:disable-next-line strict-type-predicates */ - return typeof source === 'string'; -} -module.exports = FastGlob; +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. +module.exports = glob -/***/ }), -/* 593 */ -/***/ (function(module, exports, __webpack_require__) { +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var inherits = __webpack_require__(593) +var EE = __webpack_require__(379).EventEmitter +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var globSync = __webpack_require__(595) +var common = __webpack_require__(596) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = __webpack_require__(514) +var util = __webpack_require__(29) +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(594); -function generate(patterns, settings) { - const positivePatterns = getPositivePatterns(patterns); - const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); - /** - * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check - * filepath directly (without read directory). - */ - const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); - const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); - const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); - const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); - return staticTasks.concat(dynamicTasks); -} -exports.generate = generate; -function convertPatternsToTasks(positive, negative, dynamic) { - const positivePatternsGroup = groupPatternsByBaseDirectory(positive); - // When we have a global group – there is no reason to divide the patterns into independent tasks. - // In this case, the global task covers the rest. - if ('.' in positivePatternsGroup) { - const task = convertPatternGroupToTask('.', positive, negative, dynamic); - return [task]; - } - return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); -} -exports.convertPatternsToTasks = convertPatternsToTasks; -function getPositivePatterns(patterns) { - return utils.pattern.getPositivePatterns(patterns); -} -exports.getPositivePatterns = getPositivePatterns; -function getNegativePatternsAsPositive(patterns, ignore) { - const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); - const positive = negative.map(utils.pattern.convertToPositivePattern); - return positive; -} -exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; -function groupPatternsByBaseDirectory(patterns) { - return patterns.reduce((collection, pattern) => { - const base = utils.pattern.getBaseDirectory(pattern); - if (base in collection) { - collection[base].push(pattern); - } - else { - collection[base] = [pattern]; - } - return collection; - }, {}); -} -exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; -function convertPatternGroupsToTasks(positive, negative, dynamic) { - return Object.keys(positive).map((base) => { - return convertPatternGroupToTask(base, positive[base], negative, dynamic); - }); -} -exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; -function convertPatternGroupToTask(base, positive, negative, dynamic) { - return { - dynamic, - positive, - negative, - base, - patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) - }; -} -exports.convertPatternGroupToTask = convertPatternGroupToTask; +var once = __webpack_require__(385) +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} -/***/ }), -/* 594 */ -/***/ (function(module, exports, __webpack_require__) { + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const array = __webpack_require__(595); -exports.array = array; -const errno = __webpack_require__(596); -exports.errno = errno; -const fs = __webpack_require__(597); -exports.fs = fs; -const path = __webpack_require__(598); -exports.path = path; -const pattern = __webpack_require__(599); -exports.pattern = pattern; -const stream = __webpack_require__(620); -exports.stream = stream; + return new Glob(pattern, options, cb) +} +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync -/***/ }), -/* 595 */ -/***/ (function(module, exports, __webpack_require__) { +// old api surface +glob.glob = glob -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function flatten(items) { - return items.reduce((collection, item) => [].concat(collection, item), []); -} -exports.flatten = flatten; +function extend (origin, add) { + if (add === null || typeof add !== 'object') { + return origin + } + var keys = Object.keys(add) + var i = keys.length + while (i--) { + origin[keys[i]] = add[keys[i]] + } + return origin +} -/***/ }), -/* 596 */ -/***/ (function(module, exports, __webpack_require__) { +glob.hasMagic = function (pattern, options_) { + var options = extend({}, options_) + options.noprocess = true -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function isEnoentCodeError(error) { - return error.code === 'ENOENT'; -} -exports.isEnoentCodeError = isEnoentCodeError; + var g = new Glob(pattern, options) + var set = g.minimatch.set + if (!pattern) + return false -/***/ }), -/* 597 */ -/***/ (function(module, exports, __webpack_require__) { + if (set.length > 1) + return true -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -class DirentFromStats { - constructor(name, stats) { - this.name = name; - this.isBlockDevice = stats.isBlockDevice.bind(stats); - this.isCharacterDevice = stats.isCharacterDevice.bind(stats); - this.isDirectory = stats.isDirectory.bind(stats); - this.isFIFO = stats.isFIFO.bind(stats); - this.isFile = stats.isFile.bind(stats); - this.isSocket = stats.isSocket.bind(stats); - this.isSymbolicLink = stats.isSymbolicLink.bind(stats); - } -} -function createDirentFromStats(name, stats) { - return new DirentFromStats(name, stats); -} -exports.createDirentFromStats = createDirentFromStats; + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + return false +} -/***/ }), -/* 598 */ -/***/ (function(module, exports, __webpack_require__) { +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -/** - * Designed to work only with simple paths: `dir\\file`. - */ -function unixify(filepath) { - return filepath.replace(/\\/g, '/'); -} -exports.unixify = unixify; -function makeAbsolute(cwd, filepath) { - return path.resolve(cwd, filepath); -} -exports.makeAbsolute = makeAbsolute; + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) -/***/ }), -/* 599 */ -/***/ (function(module, exports, __webpack_require__) { + setopts(this, pattern, options) + this._didRealPath = false -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const globParent = __webpack_require__(600); -const isGlob = __webpack_require__(601); -const micromatch = __webpack_require__(603); -const GLOBSTAR = '**'; -function isStaticPattern(pattern) { - return !isDynamicPattern(pattern); -} -exports.isStaticPattern = isStaticPattern; -function isDynamicPattern(pattern) { - return isGlob(pattern, { strict: false }); -} -exports.isDynamicPattern = isDynamicPattern; -function convertToPositivePattern(pattern) { - return isNegativePattern(pattern) ? pattern.slice(1) : pattern; -} -exports.convertToPositivePattern = convertToPositivePattern; -function convertToNegativePattern(pattern) { - return '!' + pattern; -} -exports.convertToNegativePattern = convertToNegativePattern; -function isNegativePattern(pattern) { - return pattern.startsWith('!') && pattern[1] !== '('; -} -exports.isNegativePattern = isNegativePattern; -function isPositivePattern(pattern) { - return !isNegativePattern(pattern); -} -exports.isPositivePattern = isPositivePattern; -function getNegativePatterns(patterns) { - return patterns.filter(isNegativePattern); -} -exports.getNegativePatterns = getNegativePatterns; -function getPositivePatterns(patterns) { - return patterns.filter(isPositivePattern); -} -exports.getPositivePatterns = getPositivePatterns; -function getBaseDirectory(pattern) { - return globParent(pattern); -} -exports.getBaseDirectory = getBaseDirectory; -function hasGlobStar(pattern) { - return pattern.indexOf(GLOBSTAR) !== -1; -} -exports.hasGlobStar = hasGlobStar; -function endsWithSlashGlobStar(pattern) { - return pattern.endsWith('/' + GLOBSTAR); -} -exports.endsWithSlashGlobStar = endsWithSlashGlobStar; -function isAffectDepthOfReadingPattern(pattern) { - const basename = path.basename(pattern); - return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); -} -exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; -function getNaiveDepth(pattern) { - const base = getBaseDirectory(pattern); - const patternDepth = pattern.split('/').length; - const patternBaseDepth = base.split('/').length; - /** - * This is a hack for pattern that has no base directory. - * - * This is related to the `*\something\*` pattern. - */ - if (base === '.') { - return patternDepth - patternBaseDepth; - } - return patternDepth - patternBaseDepth - 1; -} -exports.getNaiveDepth = getNaiveDepth; -function getMaxNaivePatternsDepth(patterns) { - return patterns.reduce((max, pattern) => { - const depth = getNaiveDepth(pattern); - return depth > max ? depth : max; - }, 0); -} -exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; -function makeRe(pattern, options) { - return micromatch.makeRe(pattern, options); -} -exports.makeRe = makeRe; -function convertPatternsToRe(patterns, options) { - return patterns.map((pattern) => makeRe(pattern, options)); -} -exports.convertPatternsToRe = convertPatternsToRe; -function matchAny(entry, patternsRe) { - const filepath = entry.replace(/^\.[\\\/]/, ''); - return patternsRe.some((patternRe) => patternRe.test(filepath)); -} -exports.matchAny = matchAny; + // process each pattern in the minimatch set + var n = this.minimatch.set.length + // The matches are stored as {<filename>: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) -/***/ }), -/* 600 */ -/***/ (function(module, exports, __webpack_require__) { + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } -"use strict"; + var self = this + this._processing = 0 + this._emitQueue = [] + this._processQueue = [] + this.paused = false -var isGlob = __webpack_require__(601); -var pathPosixDirname = __webpack_require__(16).posix.dirname; -var isWin32 = __webpack_require__(11).platform() === 'win32'; + if (this.noprocess) + return this -var slash = '/'; -var backslash = /\\/g; -var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; -var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; -var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; + if (n === 0) + return done() -module.exports = function globParent(str) { - // flip windows path separators - if (isWin32 && str.indexOf(slash) < 0) { - str = str.replace(backslash, slash); + var sync = true + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) } + sync = false - // special case for strings ending in enclosure containing path separator - if (enclosure.test(str)) { - str += slash; + function done () { + --self._processing + if (self._processing <= 0) { + if (sync) { + process.nextTick(function () { + self._finish() + }) + } else { + self._finish() + } + } } +} - // preserves full path in case of trailing path separator - str += 'a'; - - // remove path parts that are globby - do { - str = pathPosixDirname(str); - } while (isGlob(str) || globby.test(str)); +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return - // remove escape chars and return result - return str.replace(escaped, '$1'); -}; + if (this.realpath && !this._didRealpath) + return this._realpath() + common.finish(this) + this.emit('end', this.found) +} -/***/ }), -/* 601 */ -/***/ (function(module, exports, __webpack_require__) { +Glob.prototype._realpath = function () { + if (this._didRealpath) + return -/*! - * is-glob <https://github.com/jonschlinkert/is-glob> - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ + this._didRealpath = true -var isExtglob = __webpack_require__(602); -var chars = { '{': '}', '(': ')', '[': ']'}; -var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; -var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; + var n = this.matches.length + if (n === 0) + return this._finish() -module.exports = function isGlob(str, options) { - if (typeof str !== 'string' || str === '') { - return false; - } + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) - if (isExtglob(str)) { - return true; + function next () { + if (--n === 0) + self._finish() } +} - var regex = strictRegex; - var match; +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() - // optionally relax regex - if (options && options.strict === false) { - regex = relaxedRegex; - } + var found = Object.keys(matchset) + var self = this + var n = found.length - while ((match = regex.exec(str))) { - if (match[2]) return true; - var idx = match.index + match[0].length; + if (n === 0) + return cb() - // if an open bracket/brace/paren is escaped, - // set the index to the next closing character - var open = match[1]; - var close = open ? chars[open] : null; - if (open && close) { - var n = str.indexOf(close, idx); - if (n !== -1) { - idx = n + 1; + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + rp.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() } - } + }) + }) +} - str = str.slice(idx); - } - return false; -}; +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} -/***/ }), -/* 602 */ -/***/ (function(module, exports) { +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} -/*! - * is-extglob <https://github.com/jonschlinkert/is-extglob> - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License. - */ +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} -module.exports = function isExtglob(str) { - if (typeof str !== 'string' || str === '') { - return false; +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } } +} - var match; - while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { - if (match[2]) return true; - str = str.slice(match.index + match[0].length); +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return } - return false; -}; + //console.error('PROCESS %d', this._processing, pattern) + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. -/***/ }), -/* 603 */ -/***/ (function(module, exports, __webpack_require__) { + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return -"use strict"; + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } -const util = __webpack_require__(29); -const braces = __webpack_require__(604); -const picomatch = __webpack_require__(614); -const utils = __webpack_require__(617); -const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); + var remain = pattern.slice(n) -/** - * Returns an array of strings that match one or more glob patterns. - * - * ```js - * const mm = require('micromatch'); - * // mm(list, patterns[, options]); - * - * console.log(mm(['a.js', 'a.txt'], ['*.js'])); - * //=> [ 'a.js' ] - * ``` - * @param {String|Array<string>} list List of strings to match. - * @param {String|Array<string>} patterns One or more glob patterns to use for matching. - * @param {Object} options See available [options](#options) - * @return {Array} Returns an array of matches - * @summary false - * @api public - */ + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix -const micromatch = (list, patterns, options) => { - patterns = [].concat(patterns); - list = [].concat(list); + var abs = this._makeAbs(read) - let omit = new Set(); - let keep = new Set(); - let items = new Set(); - let negatives = 0; + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() - let onResult = state => { - items.add(state.output); - if (options && options.onResult) { - options.onResult(state); - } - }; + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} - for (let i = 0; i < patterns.length; i++) { - let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); - let negated = isMatch.state.negated || isMatch.state.negatedExtglob; - if (negated) negatives++; +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} - for (let item of list) { - let matched = isMatch(item, true); +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { - let match = negated ? !matched.isMatch : matched.isMatch; - if (!match) continue; + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() - if (negated) { - omit.add(matched.output); + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) } else { - omit.delete(matched.output); - keep.add(matched.output); + m = e.match(pn) } + if (m) + matchedEntries.push(e) } } - let result = negatives === patterns.length ? [...items] : [...keep]; - let matches = result.filter(item => !omit.has(item)); + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) - if (options && matches.length === 0) { - if (options.failglob === true) { - throw new Error(`No matches found for "${patterns.join(', ')}"`); + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) } + // This was the last one, and no stats were needed + return cb() + } - if (options.nonull === true || options.nullglob === true) { - return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e } + this._process([e].concat(remain), index, inGlobStar, cb) } + cb() +} - return matches; -}; +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return -/** - * Backwards compatibility - */ + if (isIgnored(this, e)) + return -micromatch.match = micromatch; + if (this.paused) { + this._emitQueue.push([index, e]) + return + } -/** - * Returns a matcher function from the given glob `pattern` and `options`. - * The returned function takes a string to match as its only argument and returns - * true if the string is a match. - * - * ```js - * const mm = require('micromatch'); - * // mm.matcher(pattern[, options]); - * - * const isMatch = mm.matcher('*.!(*a)'); - * console.log(isMatch('a.a')); //=> false - * console.log(isMatch('a.b')); //=> true - * ``` - * @param {String} `pattern` Glob pattern - * @param {Object} `options` - * @return {Function} Returns a matcher function. - * @api public - */ + var abs = isAbsolute(e) ? e : this._makeAbs(e) -micromatch.matcher = (pattern, options) => picomatch(pattern, options); + if (this.mark) + e = this._mark(e) -/** - * Returns true if **any** of the given glob `patterns` match the specified `string`. - * - * ```js - * const mm = require('micromatch'); - * // mm.isMatch(string, patterns[, options]); - * - * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true - * console.log(mm.isMatch('a.a', 'b.*')); //=> false - * ``` - * @param {String} str The string to test. - * @param {String|Array} patterns One or more glob patterns to use for matching. - * @param {Object} [options] See available [options](#options). - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + if (this.absolute) + e = abs -micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + if (this.matches[index][e]) + return -/** - * Backwards compatibility - */ + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } -micromatch.any = micromatch.isMatch; + this.matches[index][e] = true -/** - * Returns a list of strings that _**do not match any**_ of the given `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.not(list, patterns[, options]); - * - * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); - * //=> ['b.b', 'c.c'] - * ``` - * @param {Array} `list` Array of strings to match. - * @param {String|Array} `patterns` One or more glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of strings that **do not match** the given patterns. - * @api public - */ + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) -micromatch.not = (list, patterns, options = {}) => { - patterns = [].concat(patterns).map(String); - let result = new Set(); - let items = []; + this.emit('match', e) +} - let onResult = state => { - if (options.onResult) options.onResult(state); - items.push(state.output); - }; +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return - let matches = micromatch(list, patterns, { ...options, onResult }); + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) - for (let item of items) { - if (!matches.includes(item)) { - result.add(item); - } - } - return [...result]; -}; + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) -/** - * Returns true if the given `string` contains the given pattern. Similar - * to [.isMatch](#isMatch) but the pattern can match any part of the string. - * - * ```js - * var mm = require('micromatch'); - * // mm.contains(string, pattern[, options]); - * - * console.log(mm.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(mm.contains('aa/bb/cc', '*d')); - * //=> false - * ``` - * @param {String} `str` The string to match. - * @param {String|Array} `patterns` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the patter matches any part of `str`. - * @api public - */ + if (lstatcb) + fs.lstat(abs, lstatcb) -micromatch.contains = (str, pattern, options) => { - if (typeof str !== 'string') { - throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + function lstatcb_ (er, lstat) { + if (er && er.code === 'ENOENT') + return cb() + + var isSym = lstat && lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) } +} - if (Array.isArray(pattern)) { - return pattern.some(p => micromatch.contains(str, p, options)); +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) } - if (typeof pattern === 'string') { - if (isEmptyString(str) || isEmptyString(pattern)) { - return false; - } + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} - if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { - return true; - } +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) } +} - return micromatch.isMatch(str, pattern, { ...options, contains: true }); -}; +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return -/** - * Filter the keys of the given object with the given `glob` pattern - * and `options`. Does not attempt to match nested keys. If you need this feature, - * use [glob-object][] instead. - * - * ```js - * const mm = require('micromatch'); - * // mm.matchKeys(object, patterns[, options]); - * - * const obj = { aa: 'a', ab: 'b', ac: 'c' }; - * console.log(mm.matchKeys(obj, '*b')); - * //=> { ab: 'b' } - * ``` - * @param {Object} `object` The object with keys to filter. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Object} Returns an object with only keys that match the given patterns. - * @api public - */ - -micromatch.matchKeys = (obj, patterns, options) => { - if (!utils.isObject(obj)) { - throw new TypeError('Expected the first argument to be an object'); + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } } - let keys = micromatch(Object.keys(obj), patterns, options); - let res = {}; - for (let key of keys) res[key] = obj[key]; - return res; -}; - -/** - * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.some(list, patterns[, options]); - * - * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // true - * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ -micromatch.some = (list, patterns, options) => { - let items = [].concat(list); + this.cache[abs] = entries + return cb(null, entries) +} - for (let pattern of [].concat(patterns)) { - let isMatch = picomatch(String(pattern), options); - if (items.some(item => isMatch(item))) { - return true; - } - } - return false; -}; +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return -/** - * Returns true if every string in the given `list` matches - * any of the given glob `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.every(list, patterns[, options]); - * - * console.log(mm.every('foo.js', ['foo.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // false - * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + this.emit('error', error) + this.abort() + } + break -micromatch.every = (list, patterns, options) => { - let items = [].concat(list); + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break - for (let pattern of [].concat(patterns)) { - let isMatch = picomatch(String(pattern), options); - if (!items.every(item => isMatch(item))) { - return false; - } + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break } - return true; -}; -/** - * Returns true if **all** of the given `patterns` match - * the specified string. - * - * ```js - * const mm = require('micromatch'); - * // mm.all(string, patterns[, options]); - * - * console.log(mm.all('foo.js', ['foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); - * // false - * - * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); - * // true - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + return cb() +} -micromatch.all = (str, patterns, options) => { - if (typeof str !== 'string') { - throw new TypeError(`Expected a string: "${util.inspect(str)}"`); - } +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} - return [].concat(patterns).every(p => picomatch(p, options)(str)); -}; -/** - * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. - * - * ```js - * const mm = require('micromatch'); - * // mm.capture(pattern, string[, options]); - * - * console.log(mm.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(mm.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `glob` Glob pattern to use for matching. - * @param {String} `input` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. - * @api public - */ +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) -micromatch.capture = (glob, input, options) => { - let posix = utils.isWindows(options); - let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); - let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() - if (match) { - return match.slice(1).map(v => v === void 0 ? '' : v); - } -}; + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) -/** - * Create a regular expression from the given glob `pattern`. - * - * ```js - * const mm = require('micromatch'); - * // mm.makeRe(pattern[, options]); - * - * console.log(mm.makeRe('*.js')); - * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ - * ``` - * @param {String} `pattern` A glob pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} Returns a regex created from the given pattern. - * @api public - */ + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) -micromatch.makeRe = (...args) => picomatch.makeRe(...args); + var isSym = this.symlinks[abs] + var len = entries.length -/** - * Scan a glob pattern to separate the pattern into segments. Used - * by the [split](#split) method. - * - * ```js - * const mm = require('micromatch'); - * const state = mm.scan(pattern[, options]); - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} Returns an object with - * @api public - */ + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() -micromatch.scan = (...args) => picomatch.scan(...args); + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue -/** - * Parse a glob pattern to create the source string for a regular - * expression. - * - * ```js - * const mm = require('micromatch'); - * const state = mm(pattern[, options]); - * ``` - * @param {String} `glob` - * @param {Object} `options` - * @return {Object} Returns an object with useful properties and output to be used as regex source string. - * @api public - */ + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) -micromatch.parse = (patterns, options) => { - let res = []; - for (let pattern of [].concat(patterns || [])) { - for (let str of braces(String(pattern), options)) { - res.push(picomatch.parse(str, options)); - } + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) } - return res; -}; -/** - * Process the given brace `pattern`. - * - * ```js - * const { braces } = require('micromatch'); - * console.log(braces('foo/{a,b,c}/bar')); - * //=> [ 'foo/(a|b|c)/bar' ] - * - * console.log(braces('foo/{a,b,c}/bar', { expand: true })); - * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] - * ``` - * @param {String} `pattern` String with brace pattern to process. - * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. - * @return {Array} - * @api public - */ + cb() +} -micromatch.braces = (pattern, options) => { - if (typeof pattern !== 'string') throw new TypeError('Expected a string'); - if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { - return [pattern]; +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } } - return braces(pattern, options); -}; -/** - * Expand braces - */ + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') -micromatch.braceExpand = (pattern, options) => { - if (typeof pattern !== 'string') throw new TypeError('Expected a string'); - return micromatch.braces(pattern, { ...options, expand: true }); -}; + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} -/** - * Expose micromatch - */ +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' -module.exports = micromatch; + if (f.length > this.maxLength) + return cb() + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] -/***/ }), -/* 604 */ -/***/ (function(module, exports, __webpack_require__) { + if (Array.isArray(c)) + c = 'DIR' -"use strict"; + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + if (needDir && c === 'FILE') + return cb() -const stringify = __webpack_require__(605); -const compile = __webpack_require__(607); -const expand = __webpack_require__(611); -const parse = __webpack_require__(612); + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } -/** - * Expand the given pattern or create a regex-compatible string. - * - * ```js - * const braces = require('braces'); - * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] - * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {String} - * @api public - */ + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } -const braces = (input, options = {}) => { - let output = []; + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) - if (Array.isArray(input)) { - for (let pattern of input) { - let result = braces.create(pattern, options); - if (Array.isArray(result)) { - output.push(...result); - } else { - output.push(result); - } + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) } - } else { - output = [].concat(braces.create(input, options)); } +} - if (options && options.expand === true && options.nodupes === true) { - output = [...new Set(output)]; +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return cb() } - return output; -}; -/** - * Parse the given `str` with the given `options`. - * - * ```js - * // braces.parse(pattern, [, options]); - * const ast = braces.parse('a/{b,c}/d'); - * console.log(ast); - * ``` - * @param {String} pattern Brace pattern to parse - * @param {Object} options - * @return {Object} Returns an AST - * @api public - */ + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat -braces.parse = (input, options = {}) => parse(input, options); + if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) + return cb(null, false, stat) -/** - * Creates a braces string from an AST, or an AST node. - * - * ```js - * const braces = require('braces'); - * let ast = braces.parse('foo/{a,b}/bar'); - * console.log(stringify(ast.nodes[2])); //=> '{a,b}' - * ``` - * @param {String} `input` Brace pattern or AST. - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c -braces.stringify = (input, options = {}) => { - if (typeof input === 'string') { - return stringify(braces.parse(input, options), options); - } - return stringify(input, options); -}; + if (needDir && c === 'FILE') + return cb() -/** - * Compiles a brace pattern into a regex-compatible, optimized string. - * This method is called by the main [braces](#braces) function by default. - * - * ```js - * const braces = require('braces'); - * console.log(braces.compile('a/{b,c}/d')); - * //=> ['a/(b|c)/d'] - * ``` - * @param {String} `input` Brace pattern or AST. - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ + return cb(null, c, stat) +} -braces.compile = (input, options = {}) => { - if (typeof input === 'string') { - input = braces.parse(input, options); - } - return compile(input, options); -}; -/** - * Expands a brace pattern into an array. This method is called by the - * main [braces](#braces) function when `options.expand` is true. Before - * using this method it's recommended that you read the [performance notes](#performance)) - * and advantages of using [.compile](#compile) instead. - * - * ```js - * const braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/b/d', 'a/c/d']; - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ +/***/ }), +/* 593 */ +/***/ (function(module, exports, __webpack_require__) { -braces.expand = (input, options = {}) => { - if (typeof input === 'string') { - input = braces.parse(input, options); - } +try { + var util = __webpack_require__(29); + /* istanbul ignore next */ + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + /* istanbul ignore next */ + module.exports = __webpack_require__(594); +} - let result = expand(input, options); - // filter out empty strings if specified - if (options.noempty === true) { - result = result.filter(Boolean); - } +/***/ }), +/* 594 */ +/***/ (function(module, exports) { - // filter out duplicates if specified - if (options.nodupes === true) { - result = [...new Set(result)]; +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } } +} - return result; -}; -/** - * Processes a brace pattern and returns either an expanded array - * (if `options.expand` is true), a highly optimized regex-compatible string. - * This method is called by the main [braces](#braces) function. - * - * ```js - * const braces = require('braces'); - * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) - * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ +/***/ }), +/* 595 */ +/***/ (function(module, exports, __webpack_require__) { -braces.create = (input, options = {}) => { - if (input === '' || input.length < 3) { - return [input]; - } +module.exports = globSync +globSync.GlobSync = GlobSync - return options.expand !== true - ? braces.compile(input, options) - : braces.expand(input, options); -}; +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var Glob = __webpack_require__(592).Glob +var util = __webpack_require__(29) +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var common = __webpack_require__(596) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored -/** - * Expose "braces" - */ +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -module.exports = braces; + return new GlobSync(pattern, options).found +} +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') -/***/ }), -/* 605 */ -/***/ (function(module, exports, __webpack_require__) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -"use strict"; + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + setopts(this, pattern, options) -const utils = __webpack_require__(606); + if (this.noprocess) + return this -module.exports = (ast, options = {}) => { - let stringify = (node, parent = {}) => { - let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; - let output = ''; + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} - if (node.value) { - if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { - return '\\' + node.value; +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = rp.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } } - return node.value; - } + }) + } + common.finish(this) +} - if (node.value) { - return node.value; - } - if (node.nodes) { - for (let child of node.nodes) { - output += stringify(child); - } - } - return output; - }; +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) - return stringify(ast); -}; + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break -/***/ }), -/* 606 */ -/***/ (function(module, exports, __webpack_require__) { + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } -"use strict"; + var remain = pattern.slice(n) + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix -exports.isInteger = num => { - if (typeof num === 'number') { - return Number.isInteger(num); - } - if (typeof num === 'string' && num.trim() !== '') { - return Number.isInteger(Number(num)); - } - return false; -}; + var abs = this._makeAbs(read) -/** - * Find a node of the given type - */ + //if ignored, skip processing + if (childrenIgnored(this, read)) + return -exports.find = (node, type) => node.nodes.find(node => node.type === type); + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} -/** - * Find a node of the given type - */ -exports.exceedsLimit = (min, max, step = 1, limit) => { - if (limit === false) return false; - if (!exports.isInteger(min) || !exports.isInteger(max)) return false; - return ((Number(max) - Number(min)) / Number(step)) >= limit; -}; +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) -/** - * Escape the given node with '\\' before node.value - */ + // if the abs isn't a dir, then nothing can match! + if (!entries) + return -exports.escapeNode = (block, n = 0, type) => { - let node = block.nodes[n]; - if (!node) return; + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' - if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { - if (node.escaped !== true) { - node.value = '\\' + node.value; - node.escaped = true; + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) } } -}; -/** - * Returns true if the given brace node should be enclosed in literal braces - */ + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return -exports.encloseBrace = node => { - if (node.type !== 'brace') return false; - if ((node.commas >> 0 + node.ranges >> 0) === 0) { - node.invalid = true; - return true; - } - return false; -}; + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. -/** - * Returns true if a brace node is invalid. - */ + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) -exports.isInvalidBrace = block => { - if (block.type !== 'brace') return false; - if (block.invalid === true || block.dollar) return true; - if ((block.commas >> 0 + block.ranges >> 0) === 0) { - block.invalid = true; - return true; - } - if (block.open !== true || block.close !== true) { - block.invalid = true; - return true; - } - return false; -}; + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } -/** - * Returns true if a node is an open or close node - */ + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return + } -exports.isOpenOrClose = node => { - if (node.type === 'open' || node.type === 'close') { - return true; + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) } - return node.open === true || node.close === true; -}; +} -/** - * Reduce an array of text nodes. - */ -exports.reduce = nodes => nodes.reduce((acc, node) => { - if (node.type === 'text') acc.push(node.value); - if (node.type === 'range') node.type = 'text'; - return acc; -}, []); +GlobSync.prototype._emitMatch = function (index, e) { + if (isIgnored(this, e)) + return -/** - * Flatten an array - */ + var abs = this._makeAbs(e) -exports.flatten = (...args) => { - const result = []; - const flat = arr => { - for (let i = 0; i < arr.length; i++) { - let ele = arr[i]; - Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); - } - return result; - }; - flat(args); - return result; -}; + if (this.mark) + e = this._mark(e) + if (this.absolute) { + e = abs + } -/***/ }), -/* 607 */ -/***/ (function(module, exports, __webpack_require__) { + if (this.matches[index][e]) + return -"use strict"; + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + this.matches[index][e] = true -const fill = __webpack_require__(608); -const utils = __webpack_require__(606); + if (this.stat) + this._stat(e) +} -const compile = (ast, options = {}) => { - let walk = (node, parent = {}) => { - let invalidBlock = utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; - let invalid = invalidBlock === true || invalidNode === true; - let prefix = options.escapeInvalid === true ? '\\' : ''; - let output = ''; - if (node.isOpen === true) { - return prefix + node.value; - } - if (node.isClose === true) { - return prefix + node.value; - } +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) - if (node.type === 'open') { - return invalid ? (prefix + node.value) : '('; + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er.code === 'ENOENT') { + // lstat failed, doesn't exist + return null } + } - if (node.type === 'close') { - return invalid ? (prefix + node.value) : ')'; - } + var isSym = lstat && lstat.isSymbolicLink() + this.symlinks[abs] = isSym - if (node.type === 'comma') { - return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); - } + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) - if (node.value) { - return node.value; - } + return entries +} - if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); - let range = fill(...args, { ...options, wrap: false, toRegex: true }); +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries - if (range.length !== 0) { - return args.length > 1 && range.length > 1 ? `(${range})` : range; - } - } + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) - if (node.nodes) { - for (let child of node.nodes) { - output += walk(child, node); - } - } - return output; - }; + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null - return walk(ast); -}; + if (Array.isArray(c)) + return c + } -module.exports = compile; + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } -/***/ }), -/* 608 */ -/***/ (function(module, exports, __webpack_require__) { + this.cache[abs] = entries -"use strict"; -/*! - * fill-range <https://github.com/jonschlinkert/fill-range> - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ + // mark and cache dir-ness + return entries +} +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + throw error + } + break + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break -const util = __webpack_require__(29); -const toRegexRange = __webpack_require__(609); - -const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); - -const transform = toNumber => { - return value => toNumber === true ? Number(value) : String(value); -}; - -const isValidValue = value => { - return typeof value === 'number' || (typeof value === 'string' && value !== ''); -}; - -const isNumber = num => Number.isInteger(+num); - -const zeros = input => { - let value = `${input}`; - let index = -1; - if (value[0] === '-') value = value.slice(1); - if (value === '0') return false; - while (value[++index] === '0'); - return index > 0; -}; - -const stringify = (start, end, options) => { - if (typeof start === 'string' || typeof end === 'string') { - return true; + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break } - return options.stringify === true; -}; +} -const pad = (input, maxLength, toNumber) => { - if (maxLength > 0) { - let dash = input[0] === '-' ? '-' : ''; - if (dash) input = input.slice(1); - input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); - } - if (toNumber === false) { - return String(input); - } - return input; -}; +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { -const toMaxLen = (input, maxLength) => { - let negative = input[0] === '-' ? '-' : ''; - if (negative) { - input = input.slice(1); - maxLength--; - } - while (input.length < maxLength) input = '0' + input; - return negative ? ('-' + input) : input; -}; + var entries = this._readdir(abs, inGlobStar) -const toSequence = (parts, options) => { - parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); - parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return - let prefix = options.capture ? '' : '?:'; - let positives = ''; - let negatives = ''; - let result; + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) - if (parts.positives.length) { - positives = parts.positives.join('|'); - } + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) - if (parts.negatives.length) { - negatives = `-(${prefix}${parts.negatives.join('|')})`; - } + var len = entries.length + var isSym = this.symlinks[abs] - if (positives && negatives) { - result = `${positives}|${negatives}`; - } else { - result = positives || negatives; - } + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return - if (options.wrap) { - return `(${prefix}${result})`; - } + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue - return result; -}; + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) -const toRange = (a, b, isNumbers, options) => { - if (isNumbers) { - return toRegexRange(a, b, { wrap: false, ...options }); + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) } +} - let start = String.fromCharCode(a); - if (a === b) return start; +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) - let stop = String.fromCharCode(b); - return `[${start}-${stop}]`; -}; + if (!this.matches[index]) + this.matches[index] = Object.create(null) -const toRegex = (start, end, options) => { - if (Array.isArray(start)) { - let wrap = options.wrap === true; - let prefix = options.capture ? '' : '?:'; - return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } } - return toRegexRange(start, end, options); -}; -const rangeError = (...args) => { - return new RangeError('Invalid range arguments: ' + util.inspect(...args)); -}; + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') -const invalidRange = (start, end, options) => { - if (options.strictRanges === true) throw rangeError([start, end]); - return []; -}; + // Mark this as a match + this._emitMatch(index, prefix) +} -const invalidStep = (step, options) => { - if (options.strictRanges === true) { - throw new TypeError(`Expected step "${step}" to be a number`); - } - return []; -}; +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' -const fillNumbers = (start, end, step = 1, options = {}) => { - let a = Number(start); - let b = Number(end); + if (f.length > this.maxLength) + return false - if (!Number.isInteger(a) || !Number.isInteger(b)) { - if (options.strictRanges === true) throw rangeError([start, end]); - return []; - } + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] - // fix negative zero - if (a === 0) a = 0; - if (b === 0) b = 0; + if (Array.isArray(c)) + c = 'DIR' - let descending = a > b; - let startString = String(start); - let endString = String(end); - let stepString = String(step); - step = Math.max(Math.abs(step), 1); + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c - let padded = zeros(startString) || zeros(endString) || zeros(stepString); - let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; - let toNumber = padded === false && stringify(start, end, options) === false; - let format = options.transform || transform(toNumber); + if (needDir && c === 'FILE') + return false - if (options.toRegex && step === 1) { - return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. } - let parts = { negatives: [], positives: [] }; - let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); - let range = []; - let index = 0; + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return false + } + } - while (descending ? a >= b : a <= b) { - if (options.toRegex === true && step > 1) { - push(a); + if (lstat && lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } } else { - range.push(pad(format(a, index), maxLen, toNumber)); + stat = lstat } - a = descending ? a - step : a + step; - index++; } - if (options.toRegex === true) { - return step > 1 - ? toSequence(parts, options) - : toRegex(range, null, { wrap: false, ...options }); - } + this.statCache[abs] = stat - return range; -}; + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' -const fillLetters = (start, end, step = 1, options = {}) => { - if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { - return invalidRange(start, end, options); - } + this.cache[abs] = this.cache[abs] || c + if (needDir && c === 'FILE') + return false - let format = options.transform || (val => String.fromCharCode(val)); - let a = `${start}`.charCodeAt(0); - let b = `${end}`.charCodeAt(0); + return c +} - let descending = a > b; - let min = Math.min(a, b); - let max = Math.max(a, b); +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} - if (options.toRegex && step === 1) { - return toRange(min, max, false, options); - } +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} - let range = []; - let index = 0; - while (descending ? a >= b : a <= b) { - range.push(format(a, index)); - a = descending ? a - step : a + step; - index++; - } +/***/ }), +/* 596 */ +/***/ (function(module, exports, __webpack_require__) { - if (options.toRegex === true) { - return toRegex(range, null, { wrap: false, options }); - } +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored - return range; -}; +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} -const fill = (start, end, step, options = {}) => { - if (end == null && isValidValue(start)) { - return [start]; - } +var path = __webpack_require__(16) +var minimatch = __webpack_require__(505) +var isAbsolute = __webpack_require__(511) +var Minimatch = minimatch.Minimatch - if (!isValidValue(start) || !isValidValue(end)) { - return invalidRange(start, end, options); +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} + +function alphasort (a, b) { + return a.localeCompare(b) +} + +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] + + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) } +} - if (typeof step === 'function') { - return fill(start, end, 1, { transform: step }); +// ignore patterns are always in dot:true mode. +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern, { dot: true }) } - if (isObject(step)) { - return fill(start, end, 0, step); + return { + matcher: new Minimatch(pattern, { dot: true }), + gmatcher: gmatcher } +} - let opts = { ...options }; - if (opts.capture === true) opts.wrap = true; - step = step || opts.step || 1; +function setopts (self, pattern, options) { + if (!options) + options = {} - if (!isNumber(step)) { - if (step != null && !isObject(step)) return invalidStep(step, opts); - return fill(start, end, 1, step); + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern } - if (isNumber(start) && isNumber(end)) { - return fillNumbers(start, end, step, opts); - } + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + self.absolute = !!options.absolute - return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); -}; + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) -module.exports = fill; + setupIgnores(self, options) + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = path.resolve(options.cwd) + self.changedCwd = self.cwd !== cwd + } -/***/ }), -/* 609 */ -/***/ (function(module, exports, __webpack_require__) { + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") -"use strict"; -/*! - * to-regex-range <https://github.com/micromatch/to-regex-range> - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ + // TODO: is an absolute `cwd` supposed to be resolved against `root`? + // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') + self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd) + if (process.platform === "win32") + self.cwdAbs = self.cwdAbs.replace(/\\/g, "/") + self.nomount = !!options.nomount + // disable comments and negation in Minimatch. + // Note that they are not supported in Glob itself anyway. + options.nonegate = true + options.nocomment = true + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} -const isNumber = __webpack_require__(610); +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) -const toRegexRange = (min, max, options) => { - if (isNumber(min) === false) { - throw new TypeError('toRegexRange: expected the first argument to be a number'); + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } } - if (max === void 0 || min === max) { - return String(min); - } + if (!nou) + all = Object.keys(all) - if (isNumber(max) === false) { - throw new TypeError('toRegexRange: expected the second argument to be a number.'); - } + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) - let opts = { relaxZeros: true, ...options }; - if (typeof opts.strictZeros === 'boolean') { - opts.relaxZeros = opts.strictZeros === false; + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + var notDir = !(/\/$/.test(e)) + var c = self.cache[e] || self.cache[makeAbs(self, e)] + if (notDir && c) + notDir = c !== 'DIR' && !Array.isArray(c) + return notDir + }) + } } - let relax = String(opts.relaxZeros); - let shorthand = String(opts.shorthand); - let capture = String(opts.capture); - let wrap = String(opts.wrap); - let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) - if (toRegexRange.cache.hasOwnProperty(cacheKey)) { - return toRegexRange.cache[cacheKey].result; - } + self.found = all +} - let a = Math.min(min, max); - let b = Math.max(min, max); +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' - if (Math.abs(a - b) === 1) { - let result = min + '|' + max; - if (opts.capture) { - return `(${result})`; - } - if (opts.wrap === false) { - return result; + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) + + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] } - return `(?:${result})`; } - let isPadded = hasPadding(min) || hasPadding(max); - let state = { min, max, a, b }; - let positives = []; - let negatives = []; - - if (isPadded) { - state.isPadded = isPadded; - state.maxLen = String(state.max).length; - } + return m +} - if (a < 0) { - let newMin = b < 0 ? Math.abs(b) : 1; - negatives = splitToPatterns(newMin, Math.abs(a), state, opts); - a = state.a = 0; +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) } - if (b >= 0) { - positives = splitToPatterns(a, b, state, opts); - } + if (process.platform === 'win32') + abs = abs.replace(/\\/g, '/') - state.negatives = negatives; - state.positives = positives; - state.result = collatePatterns(negatives, positives, opts); + return abs +} - if (opts.capture === true) { - state.result = `(${state.result})`; - } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { - state.result = `(?:${state.result})`; - } - toRegexRange.cache[cacheKey] = state; - return state.result; -}; +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false -function collatePatterns(neg, pos, options) { - let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; - let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; - let intersected = filterPatterns(neg, pos, '-?', true, options) || []; - let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); - return subpatterns.join('|'); + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) } -function splitToRanges(min, max) { - let nines = 1; - let zeros = 1; +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false - let stop = countNines(min, nines); - let stops = new Set([max]); + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} - while (min <= stop && stop <= max) { - stops.add(stop); - nines += 1; - stop = countNines(min, nines); - } - stop = countZeros(max + 1, zeros) - 1; +/***/ }), +/* 597 */ +/***/ (function(module, exports, __webpack_require__) { - while (min < stop && stop <= max) { - stops.add(stop); - zeros += 1; - stop = countZeros(max + 1, zeros) - 1; - } +"use strict"; + +const taskManager = __webpack_require__(598); +const async_1 = __webpack_require__(626); +const stream_1 = __webpack_require__(659); +const sync_1 = __webpack_require__(660); +const settings_1 = __webpack_require__(662); +const utils = __webpack_require__(599); +function FastGlob(source, options) { + try { + assertPatternsInput(source); + } + catch (error) { + return Promise.reject(error); + } + const works = getWorks(source, async_1.default, options); + return Promise.all(works).then(utils.array.flatten); +} +(function (FastGlob) { + function sync(source, options) { + assertPatternsInput(source); + const works = getWorks(source, sync_1.default, options); + return utils.array.flatten(works); + } + FastGlob.sync = sync; + function stream(source, options) { + assertPatternsInput(source); + const works = getWorks(source, stream_1.default, options); + /** + * The stream returned by the provider cannot work with an asynchronous iterator. + * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. + * This affects performance (+25%). I don't see best solution right now. + */ + return utils.stream.merge(works); + } + FastGlob.stream = stream; + function generateTasks(source, options) { + assertPatternsInput(source); + const patterns = [].concat(source); + const settings = new settings_1.default(options); + return taskManager.generate(patterns, settings); + } + FastGlob.generateTasks = generateTasks; +})(FastGlob || (FastGlob = {})); +function getWorks(source, _Provider, options) { + const patterns = [].concat(source); + const settings = new settings_1.default(options); + const tasks = taskManager.generate(patterns, settings); + const provider = new _Provider(settings); + return tasks.map(provider.read, provider); +} +function assertPatternsInput(source) { + if ([].concat(source).every(isString)) { + return; + } + throw new TypeError('Patterns must be a string or an array of strings'); +} +function isString(source) { + /* tslint:disable-next-line strict-type-predicates */ + return typeof source === 'string'; +} +module.exports = FastGlob; - stops = [...stops]; - stops.sort(compare); - return stops; -} -/** - * Convert a range to a regex pattern - * @param {Number} `start` - * @param {Number} `stop` - * @return {String} - */ +/***/ }), +/* 598 */ +/***/ (function(module, exports, __webpack_require__) { -function rangeToPattern(start, stop, options) { - if (start === stop) { - return { pattern: start, count: [], digits: 0 }; - } +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(599); +function generate(patterns, settings) { + const positivePatterns = getPositivePatterns(patterns); + const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); + /** + * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check + * filepath directly (without read directory). + */ + const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); + const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); + const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); + const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); + return staticTasks.concat(dynamicTasks); +} +exports.generate = generate; +function convertPatternsToTasks(positive, negative, dynamic) { + const positivePatternsGroup = groupPatternsByBaseDirectory(positive); + // When we have a global group – there is no reason to divide the patterns into independent tasks. + // In this case, the global task covers the rest. + if ('.' in positivePatternsGroup) { + const task = convertPatternGroupToTask('.', positive, negative, dynamic); + return [task]; + } + return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); +} +exports.convertPatternsToTasks = convertPatternsToTasks; +function getPositivePatterns(patterns) { + return utils.pattern.getPositivePatterns(patterns); +} +exports.getPositivePatterns = getPositivePatterns; +function getNegativePatternsAsPositive(patterns, ignore) { + const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); + const positive = negative.map(utils.pattern.convertToPositivePattern); + return positive; +} +exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; +function groupPatternsByBaseDirectory(patterns) { + return patterns.reduce((collection, pattern) => { + const base = utils.pattern.getBaseDirectory(pattern); + if (base in collection) { + collection[base].push(pattern); + } + else { + collection[base] = [pattern]; + } + return collection; + }, {}); +} +exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; +function convertPatternGroupsToTasks(positive, negative, dynamic) { + return Object.keys(positive).map((base) => { + return convertPatternGroupToTask(base, positive[base], negative, dynamic); + }); +} +exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; +function convertPatternGroupToTask(base, positive, negative, dynamic) { + return { + dynamic, + positive, + negative, + base, + patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) + }; +} +exports.convertPatternGroupToTask = convertPatternGroupToTask; - let zipped = zip(start, stop); - let digits = zipped.length; - let pattern = ''; - let count = 0; - for (let i = 0; i < digits; i++) { - let [startDigit, stopDigit] = zipped[i]; +/***/ }), +/* 599 */ +/***/ (function(module, exports, __webpack_require__) { - if (startDigit === stopDigit) { - pattern += startDigit; +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const array = __webpack_require__(600); +exports.array = array; +const errno = __webpack_require__(601); +exports.errno = errno; +const fs = __webpack_require__(602); +exports.fs = fs; +const path = __webpack_require__(603); +exports.path = path; +const pattern = __webpack_require__(604); +exports.pattern = pattern; +const stream = __webpack_require__(625); +exports.stream = stream; - } else if (startDigit !== '0' || stopDigit !== '9') { - pattern += toCharacterClass(startDigit, stopDigit, options); - } else { - count++; - } - } +/***/ }), +/* 600 */ +/***/ (function(module, exports, __webpack_require__) { - if (count) { - pattern += options.shorthand === true ? '\\d' : '[0-9]'; - } +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function flatten(items) { + return items.reduce((collection, item) => [].concat(collection, item), []); +} +exports.flatten = flatten; - return { pattern, count: [count], digits }; -} -function splitToPatterns(min, max, tok, options) { - let ranges = splitToRanges(min, max); - let tokens = []; - let start = min; - let prev; +/***/ }), +/* 601 */ +/***/ (function(module, exports, __webpack_require__) { - for (let i = 0; i < ranges.length; i++) { - let max = ranges[i]; - let obj = rangeToPattern(String(start), String(max), options); - let zeros = ''; +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isEnoentCodeError(error) { + return error.code === 'ENOENT'; +} +exports.isEnoentCodeError = isEnoentCodeError; - if (!tok.isPadded && prev && prev.pattern === obj.pattern) { - if (prev.count.length > 1) { - prev.count.pop(); - } - prev.count.push(obj.count[0]); - prev.string = prev.pattern + toQuantifier(prev.count); - start = max + 1; - continue; - } +/***/ }), +/* 602 */ +/***/ (function(module, exports, __webpack_require__) { - if (tok.isPadded) { - zeros = padZeros(max, tok, options); - } +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; - obj.string = zeros + obj.pattern + toQuantifier(obj.count); - tokens.push(obj); - start = max + 1; - prev = obj; - } - return tokens; -} +/***/ }), +/* 603 */ +/***/ (function(module, exports, __webpack_require__) { -function filterPatterns(arr, comparison, prefix, intersection, options) { - let result = []; +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +/** + * Designed to work only with simple paths: `dir\\file`. + */ +function unixify(filepath) { + return filepath.replace(/\\/g, '/'); +} +exports.unixify = unixify; +function makeAbsolute(cwd, filepath) { + return path.resolve(cwd, filepath); +} +exports.makeAbsolute = makeAbsolute; - for (let ele of arr) { - let { string } = ele; - // only push if _both_ are negative... - if (!intersection && !contains(comparison, 'string', string)) { - result.push(prefix + string); - } +/***/ }), +/* 604 */ +/***/ (function(module, exports, __webpack_require__) { - // or _both_ are positive - if (intersection && contains(comparison, 'string', string)) { - result.push(prefix + string); - } - } - return result; -} +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const globParent = __webpack_require__(605); +const isGlob = __webpack_require__(606); +const micromatch = __webpack_require__(608); +const GLOBSTAR = '**'; +function isStaticPattern(pattern) { + return !isDynamicPattern(pattern); +} +exports.isStaticPattern = isStaticPattern; +function isDynamicPattern(pattern) { + return isGlob(pattern, { strict: false }); +} +exports.isDynamicPattern = isDynamicPattern; +function convertToPositivePattern(pattern) { + return isNegativePattern(pattern) ? pattern.slice(1) : pattern; +} +exports.convertToPositivePattern = convertToPositivePattern; +function convertToNegativePattern(pattern) { + return '!' + pattern; +} +exports.convertToNegativePattern = convertToNegativePattern; +function isNegativePattern(pattern) { + return pattern.startsWith('!') && pattern[1] !== '('; +} +exports.isNegativePattern = isNegativePattern; +function isPositivePattern(pattern) { + return !isNegativePattern(pattern); +} +exports.isPositivePattern = isPositivePattern; +function getNegativePatterns(patterns) { + return patterns.filter(isNegativePattern); +} +exports.getNegativePatterns = getNegativePatterns; +function getPositivePatterns(patterns) { + return patterns.filter(isPositivePattern); +} +exports.getPositivePatterns = getPositivePatterns; +function getBaseDirectory(pattern) { + return globParent(pattern); +} +exports.getBaseDirectory = getBaseDirectory; +function hasGlobStar(pattern) { + return pattern.indexOf(GLOBSTAR) !== -1; +} +exports.hasGlobStar = hasGlobStar; +function endsWithSlashGlobStar(pattern) { + return pattern.endsWith('/' + GLOBSTAR); +} +exports.endsWithSlashGlobStar = endsWithSlashGlobStar; +function isAffectDepthOfReadingPattern(pattern) { + const basename = path.basename(pattern); + return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); +} +exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; +function getNaiveDepth(pattern) { + const base = getBaseDirectory(pattern); + const patternDepth = pattern.split('/').length; + const patternBaseDepth = base.split('/').length; + /** + * This is a hack for pattern that has no base directory. + * + * This is related to the `*\something\*` pattern. + */ + if (base === '.') { + return patternDepth - patternBaseDepth; + } + return patternDepth - patternBaseDepth - 1; +} +exports.getNaiveDepth = getNaiveDepth; +function getMaxNaivePatternsDepth(patterns) { + return patterns.reduce((max, pattern) => { + const depth = getNaiveDepth(pattern); + return depth > max ? depth : max; + }, 0); +} +exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; +function makeRe(pattern, options) { + return micromatch.makeRe(pattern, options); +} +exports.makeRe = makeRe; +function convertPatternsToRe(patterns, options) { + return patterns.map((pattern) => makeRe(pattern, options)); +} +exports.convertPatternsToRe = convertPatternsToRe; +function matchAny(entry, patternsRe) { + const filepath = entry.replace(/^\.[\\\/]/, ''); + return patternsRe.some((patternRe) => patternRe.test(filepath)); +} +exports.matchAny = matchAny; -/** - * Zip strings - */ -function zip(a, b) { - let arr = []; - for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); - return arr; -} +/***/ }), +/* 605 */ +/***/ (function(module, exports, __webpack_require__) { -function compare(a, b) { - return a > b ? 1 : b > a ? -1 : 0; -} +"use strict"; -function contains(arr, key, val) { - return arr.some(ele => ele[key] === val); -} -function countNines(min, len) { - return Number(String(min).slice(0, -len) + '9'.repeat(len)); -} +var isGlob = __webpack_require__(606); +var pathPosixDirname = __webpack_require__(16).posix.dirname; +var isWin32 = __webpack_require__(11).platform() === 'win32'; -function countZeros(integer, zeros) { - return integer - (integer % Math.pow(10, zeros)); -} +var slash = '/'; +var backslash = /\\/g; +var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; +var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; +var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; -function toQuantifier(digits) { - let [start = 0, stop = ''] = digits; - if (stop || start > 1) { - return `{${start + (stop ? ',' + stop : '')}}`; +module.exports = function globParent(str) { + // flip windows path separators + if (isWin32 && str.indexOf(slash) < 0) { + str = str.replace(backslash, slash); } - return ''; -} - -function toCharacterClass(a, b, options) { - return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; -} - -function hasPadding(str) { - return /^-?(0+)\d/.test(str); -} -function padZeros(value, tok, options) { - if (!tok.isPadded) { - return value; + // special case for strings ending in enclosure containing path separator + if (enclosure.test(str)) { + str += slash; } - let diff = Math.abs(tok.maxLen - String(value).length); - let relax = options.relaxZeros !== false; + // preserves full path in case of trailing path separator + str += 'a'; - switch (diff) { - case 0: - return ''; - case 1: - return relax ? '0?' : '0'; - case 2: - return relax ? '0{0,2}' : '00'; - default: { - return relax ? `0{0,${diff}}` : `0{${diff}}`; - } - } -} - -/** - * Cache - */ - -toRegexRange.cache = {}; -toRegexRange.clearCache = () => (toRegexRange.cache = {}); - -/** - * Expose `toRegexRange` - */ + // remove path parts that are globby + do { + str = pathPosixDirname(str); + } while (isGlob(str) || globby.test(str)); -module.exports = toRegexRange; + // remove escape chars and return result + return str.replace(escaped, '$1'); +}; /***/ }), -/* 610 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; /*! - * is-number <https://github.com/jonschlinkert/is-number> + * is-glob <https://github.com/jonschlinkert/is-glob> * - * Copyright (c) 2014-present, Jon Schlinkert. + * Copyright (c) 2014-2017, Jon Schlinkert. * Released under the MIT License. */ +var isExtglob = __webpack_require__(607); +var chars = { '{': '}', '(': ')', '[': ']'}; +var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; +var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; - -module.exports = function(num) { - if (typeof num === 'number') { - return num - num === 0; +module.exports = function isGlob(str, options) { + if (typeof str !== 'string' || str === '') { + return false; } - if (typeof num === 'string' && num.trim() !== '') { - return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + + if (isExtglob(str)) { + return true; } - return false; -}; + var regex = strictRegex; + var match; -/***/ }), -/* 611 */ -/***/ (function(module, exports, __webpack_require__) { + // optionally relax regex + if (options && options.strict === false) { + regex = relaxedRegex; + } -"use strict"; + while ((match = regex.exec(str))) { + if (match[2]) return true; + var idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + var open = match[1]; + var close = open ? chars[open] : null; + if (open && close) { + var n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + str = str.slice(idx); + } + return false; +}; -const fill = __webpack_require__(608); -const stringify = __webpack_require__(605); -const utils = __webpack_require__(606); -const append = (queue = '', stash = '', enclose = false) => { - let result = []; +/***/ }), +/* 607 */ +/***/ (function(module, exports) { - queue = [].concat(queue); - stash = [].concat(stash); +/*! + * is-extglob <https://github.com/jonschlinkert/is-extglob> + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */ - if (!stash.length) return queue; - if (!queue.length) { - return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; +module.exports = function isExtglob(str) { + if (typeof str !== 'string' || str === '') { + return false; } - for (let item of queue) { - if (Array.isArray(item)) { - for (let value of item) { - result.push(append(value, stash, enclose)); - } - } else { - for (let ele of stash) { - if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; - result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); - } - } + var match; + while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { + if (match[2]) return true; + str = str.slice(match.index + match[0].length); } - return utils.flatten(result); -}; -const expand = (ast, options = {}) => { - let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; + return false; +}; - let walk = (node, parent = {}) => { - node.queue = []; - let p = parent; - let q = parent.queue; +/***/ }), +/* 608 */ +/***/ (function(module, exports, __webpack_require__) { - while (p.type !== 'brace' && p.type !== 'root' && p.parent) { - p = p.parent; - q = p.queue; - } +"use strict"; - if (node.invalid || node.dollar) { - q.push(append(q.pop(), stringify(node, options))); - return; - } - if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { - q.push(append(q.pop(), ['{}'])); - return; - } +const util = __webpack_require__(29); +const braces = __webpack_require__(609); +const picomatch = __webpack_require__(619); +const utils = __webpack_require__(622); +const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); - if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); +/** + * Returns an array of strings that match one or more glob patterns. + * + * ```js + * const mm = require('micromatch'); + * // mm(list, patterns[, options]); + * + * console.log(mm(['a.js', 'a.txt'], ['*.js'])); + * //=> [ 'a.js' ] + * ``` + * @param {String|Array<string>} list List of strings to match. + * @param {String|Array<string>} patterns One or more glob patterns to use for matching. + * @param {Object} options See available [options](#options) + * @return {Array} Returns an array of matches + * @summary false + * @api public + */ - if (utils.exceedsLimit(...args, options.step, rangeLimit)) { - throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); - } +const micromatch = (list, patterns, options) => { + patterns = [].concat(patterns); + list = [].concat(list); - let range = fill(...args, options); - if (range.length === 0) { - range = stringify(node, options); - } + let omit = new Set(); + let keep = new Set(); + let items = new Set(); + let negatives = 0; - q.push(append(q.pop(), range)); - node.nodes = []; - return; + let onResult = state => { + items.add(state.output); + if (options && options.onResult) { + options.onResult(state); } + }; - let enclose = utils.encloseBrace(node); - let queue = node.queue; - let block = node; - - while (block.type !== 'brace' && block.type !== 'root' && block.parent) { - block = block.parent; - queue = block.queue; - } + for (let i = 0; i < patterns.length; i++) { + let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); + let negated = isMatch.state.negated || isMatch.state.negatedExtglob; + if (negated) negatives++; - for (let i = 0; i < node.nodes.length; i++) { - let child = node.nodes[i]; + for (let item of list) { + let matched = isMatch(item, true); - if (child.type === 'comma' && node.type === 'brace') { - if (i === 1) queue.push(''); - queue.push(''); - continue; - } + let match = negated ? !matched.isMatch : matched.isMatch; + if (!match) continue; - if (child.type === 'close') { - q.push(append(q.pop(), queue, enclose)); - continue; + if (negated) { + omit.add(matched.output); + } else { + omit.delete(matched.output); + keep.add(matched.output); } + } + } - if (child.value && child.type !== 'open') { - queue.push(append(queue.pop(), child.value)); - continue; - } + let result = negatives === patterns.length ? [...items] : [...keep]; + let matches = result.filter(item => !omit.has(item)); - if (child.nodes) { - walk(child, node); - } + if (options && matches.length === 0) { + if (options.failglob === true) { + throw new Error(`No matches found for "${patterns.join(', ')}"`); } - return queue; - }; + if (options.nonull === true || options.nullglob === true) { + return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; + } + } - return utils.flatten(walk(ast)); + return matches; }; -module.exports = expand; - - -/***/ }), -/* 612 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - +/** + * Backwards compatibility + */ -const stringify = __webpack_require__(605); +micromatch.match = micromatch; /** - * Constants + * Returns a matcher function from the given glob `pattern` and `options`. + * The returned function takes a string to match as its only argument and returns + * true if the string is a match. + * + * ```js + * const mm = require('micromatch'); + * // mm.matcher(pattern[, options]); + * + * const isMatch = mm.matcher('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @param {String} `pattern` Glob pattern + * @param {Object} `options` + * @return {Function} Returns a matcher function. + * @api public */ -const { - MAX_LENGTH, - CHAR_BACKSLASH, /* \ */ - CHAR_BACKTICK, /* ` */ - CHAR_COMMA, /* , */ - CHAR_DOT, /* . */ - CHAR_LEFT_PARENTHESES, /* ( */ - CHAR_RIGHT_PARENTHESES, /* ) */ - CHAR_LEFT_CURLY_BRACE, /* { */ - CHAR_RIGHT_CURLY_BRACE, /* } */ - CHAR_LEFT_SQUARE_BRACKET, /* [ */ - CHAR_RIGHT_SQUARE_BRACKET, /* ] */ - CHAR_DOUBLE_QUOTE, /* " */ - CHAR_SINGLE_QUOTE, /* ' */ - CHAR_NO_BREAK_SPACE, - CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(613); +micromatch.matcher = (pattern, options) => picomatch(pattern, options); /** - * parse + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const mm = require('micromatch'); + * // mm.isMatch(string, patterns[, options]); + * + * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(mm.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public */ -const parse = (input, options = {}) => { - if (typeof input !== 'string') { - throw new TypeError('Expected a string'); - } - - let opts = options || {}; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - if (input.length > max) { - throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); - } +micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); - let ast = { type: 'root', input, nodes: [] }; - let stack = [ast]; - let block = ast; - let prev = ast; - let brackets = 0; - let length = input.length; - let index = 0; - let depth = 0; - let value; - let memo = {}; +/** + * Backwards compatibility + */ - /** - * Helpers - */ +micromatch.any = micromatch.isMatch; - const advance = () => input[index++]; - const push = node => { - if (node.type === 'text' && prev.type === 'dot') { - prev.type = 'text'; - } +/** + * Returns a list of strings that _**do not match any**_ of the given `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.not(list, patterns[, options]); + * + * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); + * //=> ['b.b', 'c.c'] + * ``` + * @param {Array} `list` Array of strings to match. + * @param {String|Array} `patterns` One or more glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Array} Returns an array of strings that **do not match** the given patterns. + * @api public + */ - if (prev && prev.type === 'text' && node.type === 'text') { - prev.value += node.value; - return; - } +micromatch.not = (list, patterns, options = {}) => { + patterns = [].concat(patterns).map(String); + let result = new Set(); + let items = []; - block.nodes.push(node); - node.parent = block; - node.prev = prev; - prev = node; - return node; + let onResult = state => { + if (options.onResult) options.onResult(state); + items.push(state.output); }; - push({ type: 'bos' }); - - while (index < length) { - block = stack[stack.length - 1]; - value = advance(); - - /** - * Invalid chars - */ + let matches = micromatch(list, patterns, { ...options, onResult }); - if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { - continue; + for (let item of items) { + if (!matches.includes(item)) { + result.add(item); } + } + return [...result]; +}; - /** - * Escaped chars - */ +/** + * Returns true if the given `string` contains the given pattern. Similar + * to [.isMatch](#isMatch) but the pattern can match any part of the string. + * + * ```js + * var mm = require('micromatch'); + * // mm.contains(string, pattern[, options]); + * + * console.log(mm.contains('aa/bb/cc', '*b')); + * //=> true + * console.log(mm.contains('aa/bb/cc', '*d')); + * //=> false + * ``` + * @param {String} `str` The string to match. + * @param {String|Array} `patterns` Glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if the patter matches any part of `str`. + * @api public + */ - if (value === CHAR_BACKSLASH) { - push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); - continue; - } +micromatch.contains = (str, pattern, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } - /** - * Right square bracket (literal): ']' - */ + if (Array.isArray(pattern)) { + return pattern.some(p => micromatch.contains(str, p, options)); + } - if (value === CHAR_RIGHT_SQUARE_BRACKET) { - push({ type: 'text', value: '\\' + value }); - continue; + if (typeof pattern === 'string') { + if (isEmptyString(str) || isEmptyString(pattern)) { + return false; } - /** - * Left square bracket: '[' - */ - - if (value === CHAR_LEFT_SQUARE_BRACKET) { - brackets++; - - let closed = true; - let next; + if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { + return true; + } + } - while (index < length && (next = advance())) { - value += next; + return micromatch.isMatch(str, pattern, { ...options, contains: true }); +}; - if (next === CHAR_LEFT_SQUARE_BRACKET) { - brackets++; - continue; - } +/** + * Filter the keys of the given object with the given `glob` pattern + * and `options`. Does not attempt to match nested keys. If you need this feature, + * use [glob-object][] instead. + * + * ```js + * const mm = require('micromatch'); + * // mm.matchKeys(object, patterns[, options]); + * + * const obj = { aa: 'a', ab: 'b', ac: 'c' }; + * console.log(mm.matchKeys(obj, '*b')); + * //=> { ab: 'b' } + * ``` + * @param {Object} `object` The object with keys to filter. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Object} Returns an object with only keys that match the given patterns. + * @api public + */ - if (next === CHAR_BACKSLASH) { - value += advance(); - continue; - } +micromatch.matchKeys = (obj, patterns, options) => { + if (!utils.isObject(obj)) { + throw new TypeError('Expected the first argument to be an object'); + } + let keys = micromatch(Object.keys(obj), patterns, options); + let res = {}; + for (let key of keys) res[key] = obj[key]; + return res; +}; - if (next === CHAR_RIGHT_SQUARE_BRACKET) { - brackets--; +/** + * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.some(list, patterns[, options]); + * + * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // true + * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ - if (brackets === 0) { - break; - } - } - } +micromatch.some = (list, patterns, options) => { + let items = [].concat(list); - push({ type: 'text', value }); - continue; + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (items.some(item => isMatch(item))) { + return true; } + } + return false; +}; - /** - * Parentheses - */ - - if (value === CHAR_LEFT_PARENTHESES) { - block = push({ type: 'paren', nodes: [] }); - stack.push(block); - push({ type: 'text', value }); - continue; - } - - if (value === CHAR_RIGHT_PARENTHESES) { - if (block.type !== 'paren') { - push({ type: 'text', value }); - continue; - } - block = stack.pop(); - push({ type: 'text', value }); - block = stack[stack.length - 1]; - continue; - } - - /** - * Quotes: '|"|` - */ - - if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { - let open = value; - let next; - - if (options.keepQuotes !== true) { - value = ''; - } - - while (index < length && (next = advance())) { - if (next === CHAR_BACKSLASH) { - value += next + advance(); - continue; - } - - if (next === open) { - if (options.keepQuotes === true) value += next; - break; - } - - value += next; - } - - push({ type: 'text', value }); - continue; - } - - /** - * Left curly brace: '{' - */ - - if (value === CHAR_LEFT_CURLY_BRACE) { - depth++; - - let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; - let brace = { - type: 'brace', - open: true, - close: false, - dollar, - depth, - commas: 0, - ranges: 0, - nodes: [] - }; - - block = push(brace); - stack.push(block); - push({ type: 'open', value }); - continue; - } - - /** - * Right curly brace: '}' - */ - - if (value === CHAR_RIGHT_CURLY_BRACE) { - if (block.type !== 'brace') { - push({ type: 'text', value }); - continue; - } - - let type = 'close'; - block = stack.pop(); - block.close = true; - - push({ type, value }); - depth--; - - block = stack[stack.length - 1]; - continue; - } - - /** - * Comma: ',' - */ - - if (value === CHAR_COMMA && depth > 0) { - if (block.ranges > 0) { - block.ranges = 0; - let open = block.nodes.shift(); - block.nodes = [open, { type: 'text', value: stringify(block) }]; - } - - push({ type: 'comma', value }); - block.commas++; - continue; - } - - /** - * Dot: '.' - */ - - if (value === CHAR_DOT && depth > 0 && block.commas === 0) { - let siblings = block.nodes; - - if (depth === 0 || siblings.length === 0) { - push({ type: 'text', value }); - continue; - } - - if (prev.type === 'dot') { - block.range = []; - prev.value += value; - prev.type = 'range'; - - if (block.nodes.length !== 3 && block.nodes.length !== 5) { - block.invalid = true; - block.ranges = 0; - prev.type = 'text'; - continue; - } - - block.ranges++; - block.args = []; - continue; - } - - if (prev.type === 'range') { - siblings.pop(); +/** + * Returns true if every string in the given `list` matches + * any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.every(list, patterns[, options]); + * + * console.log(mm.every('foo.js', ['foo.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // false + * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ - let before = siblings[siblings.length - 1]; - before.value += prev.value + value; - prev = before; - block.ranges--; - continue; - } +micromatch.every = (list, patterns, options) => { + let items = [].concat(list); - push({ type: 'dot', value }); - continue; + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (!items.every(item => isMatch(item))) { + return false; } - - /** - * Text - */ - - push({ type: 'text', value }); } - - // Mark imbalanced braces and brackets as invalid - do { - block = stack.pop(); - - if (block.type !== 'root') { - block.nodes.forEach(node => { - if (!node.nodes) { - if (node.type === 'open') node.isOpen = true; - if (node.type === 'close') node.isClose = true; - if (!node.nodes) node.type = 'text'; - node.invalid = true; - } - }); - - // get the location of the block on parent.nodes (block's siblings) - let parent = stack[stack.length - 1]; - let index = parent.nodes.indexOf(block); - // replace the (invalid) block with it's nodes - parent.nodes.splice(index, 1, ...block.nodes); - } - } while (stack.length > 0); - - push({ type: 'eos' }); - return ast; -}; - -module.exports = parse; - - -/***/ }), -/* 613 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = { - MAX_LENGTH: 1024 * 64, - - // Digits - CHAR_0: '0', /* 0 */ - CHAR_9: '9', /* 9 */ - - // Alphabet chars. - CHAR_UPPERCASE_A: 'A', /* A */ - CHAR_LOWERCASE_A: 'a', /* a */ - CHAR_UPPERCASE_Z: 'Z', /* Z */ - CHAR_LOWERCASE_Z: 'z', /* z */ - - CHAR_LEFT_PARENTHESES: '(', /* ( */ - CHAR_RIGHT_PARENTHESES: ')', /* ) */ - - CHAR_ASTERISK: '*', /* * */ - - // Non-alphabetic chars. - CHAR_AMPERSAND: '&', /* & */ - CHAR_AT: '@', /* @ */ - CHAR_BACKSLASH: '\\', /* \ */ - CHAR_BACKTICK: '`', /* ` */ - CHAR_CARRIAGE_RETURN: '\r', /* \r */ - CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ - CHAR_COLON: ':', /* : */ - CHAR_COMMA: ',', /* , */ - CHAR_DOLLAR: '$', /* . */ - CHAR_DOT: '.', /* . */ - CHAR_DOUBLE_QUOTE: '"', /* " */ - CHAR_EQUAL: '=', /* = */ - CHAR_EXCLAMATION_MARK: '!', /* ! */ - CHAR_FORM_FEED: '\f', /* \f */ - CHAR_FORWARD_SLASH: '/', /* / */ - CHAR_HASH: '#', /* # */ - CHAR_HYPHEN_MINUS: '-', /* - */ - CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ - CHAR_LEFT_CURLY_BRACE: '{', /* { */ - CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ - CHAR_LINE_FEED: '\n', /* \n */ - CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ - CHAR_PERCENT: '%', /* % */ - CHAR_PLUS: '+', /* + */ - CHAR_QUESTION_MARK: '?', /* ? */ - CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ - CHAR_RIGHT_CURLY_BRACE: '}', /* } */ - CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ - CHAR_SEMICOLON: ';', /* ; */ - CHAR_SINGLE_QUOTE: '\'', /* ' */ - CHAR_SPACE: ' ', /* */ - CHAR_TAB: '\t', /* \t */ - CHAR_UNDERSCORE: '_', /* _ */ - CHAR_VERTICAL_LINE: '|', /* | */ - CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ + return true; }; - -/***/ }), -/* 614 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = __webpack_require__(615); - - -/***/ }), -/* 615 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const path = __webpack_require__(16); -const scan = __webpack_require__(616); -const parse = __webpack_require__(619); -const utils = __webpack_require__(617); - /** - * Creates a matcher function from one or more glob patterns. The - * returned function takes a string to match as its first argument, - * and returns true if the string is a match. The returned matcher - * function also takes a boolean as the second argument that, when true, - * returns an object with additional information. + * Returns true if **all** of the given `patterns` match + * the specified string. * * ```js - * const picomatch = require('picomatch'); - * // picomatch(glob[, options]); + * const mm = require('micromatch'); + * // mm.all(string, patterns[, options]); * - * const isMatch = picomatch('*.!(*a)'); - * console.log(isMatch('a.a')); //=> false - * console.log(isMatch('a.b')); //=> true + * console.log(mm.all('foo.js', ['foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); + * // false + * + * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); + * // true * ``` - * @name picomatch - * @param {String|Array} `globs` One or more glob patterns. - * @param {Object=} `options` - * @return {Function=} Returns a matcher function. + * @param {String|Array} `str` The string to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` * @api public */ -const picomatch = (glob, options, returnState = false) => { - if (Array.isArray(glob)) { - let fns = glob.map(input => picomatch(input, options, returnState)); - return str => { - for (let isMatch of fns) { - let state = isMatch(str); - if (state) return state; - } - return false; - }; - } - - if (typeof glob !== 'string' || glob === '') { - throw new TypeError('Expected pattern to be a non-empty string'); - } - - let opts = options || {}; - let posix = utils.isWindows(options); - let regex = picomatch.makeRe(glob, options, false, true); - let state = regex.state; - delete regex.state; - - let isIgnored = () => false; - if (opts.ignore) { - let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; - isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); - } - - const matcher = (input, returnObject = false) => { - let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); - let result = { glob, state, regex, posix, input, output, match, isMatch }; - - if (typeof opts.onResult === 'function') { - opts.onResult(result); - } - - if (isMatch === false) { - result.isMatch = false; - return returnObject ? result : false; - } - - if (isIgnored(input)) { - if (typeof opts.onIgnore === 'function') { - opts.onIgnore(result); - } - result.isMatch = false; - return returnObject ? result : false; - } - - if (typeof opts.onMatch === 'function') { - opts.onMatch(result); - } - return returnObject ? result : true; - }; - - if (returnState) { - matcher.state = state; +micromatch.all = (str, patterns, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); } - return matcher; + return [].concat(patterns).every(p => picomatch(p, options)(str)); }; /** - * Test `input` with the given `regex`. This is used by the main - * `picomatch()` function to test the input string. + * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. * * ```js - * const picomatch = require('picomatch'); - * // picomatch.test(input, regex[, options]); + * const mm = require('micromatch'); + * // mm.capture(pattern, string[, options]); * - * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); - * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } + * console.log(mm.capture('test/*.js', 'test/foo.js')); + * //=> ['foo'] + * console.log(mm.capture('test/*.js', 'foo/bar.css')); + * //=> null * ``` - * @param {String} `input` String to test. - * @param {RegExp} `regex` - * @return {Object} Returns an object with matching info. + * @param {String} `glob` Glob pattern to use for matching. + * @param {String} `input` String to match + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. * @api public */ -picomatch.test = (input, regex, options, { glob, posix } = {}) => { - if (typeof input !== 'string') { - throw new TypeError('Expected input to be a string'); - } - - if (input === '') { - return { isMatch: false, output: '' }; - } - - let opts = options || {}; - let format = opts.format || (posix ? utils.toPosixSlashes : null); - let match = input === glob; - let output = (match && format) ? format(input) : input; - - if (match === false) { - output = format ? format(input) : input; - match = output === glob; - } +micromatch.capture = (glob, input, options) => { + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); + let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); - if (match === false || opts.capture === true) { - if (opts.matchBase === true || opts.basename === true) { - match = picomatch.matchBase(input, regex, options, posix); - } else { - match = regex.exec(output); - } + if (match) { + return match.slice(1).map(v => v === void 0 ? '' : v); } - - return { isMatch: !!match, match, output }; }; /** - * Match the basename of a filepath. + * Create a regular expression from the given glob `pattern`. * * ```js - * const picomatch = require('picomatch'); - * // picomatch.matchBase(input, glob[, options]); - * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true + * const mm = require('micromatch'); + * // mm.makeRe(pattern[, options]); + * + * console.log(mm.makeRe('*.js')); + * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ * ``` - * @param {String} `input` String to test. - * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). - * @return {Boolean} + * @param {String} `pattern` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. * @api public */ -picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { - let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); - return regex.test(path.basename(input)); -}; +micromatch.makeRe = (...args) => picomatch.makeRe(...args); /** - * Returns true if **any** of the given glob `patterns` match the specified `string`. + * Scan a glob pattern to separate the pattern into segments. Used + * by the [split](#split) method. * * ```js - * const picomatch = require('picomatch'); - * // picomatch.isMatch(string, patterns[, options]); - * - * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true - * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false + * const mm = require('micromatch'); + * const state = mm.scan(pattern[, options]); * ``` - * @param {String|Array} str The string to test. - * @param {String|Array} patterns One or more glob patterns to use for matching. - * @param {Object} [options] See available [options](#options). - * @return {Boolean} Returns true if any patterns match `str` + * @param {String} `pattern` + * @param {Object} `options` + * @return {Object} Returns an object with * @api public */ -picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); +micromatch.scan = (...args) => picomatch.scan(...args); /** * Parse a glob pattern to create the source string for a regular * expression. * * ```js - * const picomatch = require('picomatch'); - * const result = picomatch.parse(glob[, options]); + * const mm = require('micromatch'); + * const state = mm(pattern[, options]); * ``` * @param {String} `glob` * @param {Object} `options` - * @return {Object} Returns an object with useful properties and output to be used as a regex source string. + * @return {Object} Returns an object with useful properties and output to be used as regex source string. * @api public */ -picomatch.parse = (glob, options) => parse(glob, options); +micromatch.parse = (patterns, options) => { + let res = []; + for (let pattern of [].concat(patterns || [])) { + for (let str of braces(String(pattern), options)) { + res.push(picomatch.parse(str, options)); + } + } + return res; +}; /** - * Scan a glob pattern to separate the pattern into segments. + * Process the given brace `pattern`. * * ```js - * const picomatch = require('picomatch'); - * // picomatch.scan(input[, options]); + * const { braces } = require('micromatch'); + * console.log(braces('foo/{a,b,c}/bar')); + * //=> [ 'foo/(a|b|c)/bar' ] * - * const result = picomatch.scan('!./foo/*.js'); - * console.log(result); - * // { prefix: '!./', - * // input: '!./foo/*.js', - * // base: 'foo', - * // glob: '*.js', - * // negated: true, - * // isGlob: true } + * console.log(braces('foo/{a,b,c}/bar', { expand: true })); + * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] * ``` - * @param {String} `input` Glob pattern to scan. - * @param {Object} `options` - * @return {Object} Returns an object with + * @param {String} `pattern` String with brace pattern to process. + * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. + * @return {Array} * @api public */ -picomatch.scan = (input, options) => scan(input, options); +micromatch.braces = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { + return [pattern]; + } + return braces(pattern, options); +}; /** - * Create a regular expression from a glob pattern. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.makeRe(input[, options]); - * - * console.log(picomatch.makeRe('*.js')); - * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ - * ``` - * @param {String} `input` A glob pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} Returns a regex created from the given pattern. - * @api public + * Expand braces */ -picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { - if (!input || typeof input !== 'string') { - throw new TypeError('Expected a non-empty string'); - } +micromatch.braceExpand = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + return micromatch.braces(pattern, { ...options, expand: true }); +}; - let opts = options || {}; - let prepend = opts.contains ? '' : '^'; - let append = opts.contains ? '' : '$'; - let state = { negated: false, fastpaths: true }; - let prefix = ''; - let output; +/** + * Expose micromatch + */ - if (input.startsWith('./')) { - input = input.slice(2); - prefix = state.prefix = './'; - } +module.exports = micromatch; - if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { - output = parse.fastpaths(input, options); - } - if (output === void 0) { - state = picomatch.parse(input, options); - state.prefix = prefix + (state.prefix || ''); - output = state.output; - } +/***/ }), +/* 609 */ +/***/ (function(module, exports, __webpack_require__) { - if (returnOutput === true) { - return output; - } +"use strict"; - let source = `${prepend}(?:${output})${append}`; - if (state && state.negated === true) { - source = `^(?!${source}).*$`; - } - let regex = picomatch.toRegex(source, options); - if (returnState === true) { - regex.state = state; +const stringify = __webpack_require__(610); +const compile = __webpack_require__(612); +const expand = __webpack_require__(616); +const parse = __webpack_require__(617); + +/** + * Expand the given pattern or create a regex-compatible string. + * + * ```js + * const braces = require('braces'); + * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] + * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {String} + * @api public + */ + +const braces = (input, options = {}) => { + let output = []; + + if (Array.isArray(input)) { + for (let pattern of input) { + let result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } + } + } else { + output = [].concat(braces.create(input, options)); } - return regex; + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; + } + return output; }; /** - * Create a regular expression from the given regex source string. + * Parse the given `str` with the given `options`. * * ```js - * const picomatch = require('picomatch'); - * // picomatch.toRegex(source[, options]); + * // braces.parse(pattern, [, options]); + * const ast = braces.parse('a/{b,c}/d'); + * console.log(ast); + * ``` + * @param {String} pattern Brace pattern to parse + * @param {Object} options + * @return {Object} Returns an AST + * @api public + */ + +braces.parse = (input, options = {}) => parse(input, options); + +/** + * Creates a braces string from an AST, or an AST node. * - * const { output } = picomatch.parse('*.js'); - * console.log(picomatch.toRegex(output)); - * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ```js + * const braces = require('braces'); + * let ast = braces.parse('foo/{a,b}/bar'); + * console.log(stringify(ast.nodes[2])); //=> '{a,b}' * ``` - * @param {String} `source` Regular expression source string. + * @param {String} `input` Brace pattern or AST. * @param {Object} `options` - * @return {RegExp} + * @return {Array} Returns an array of expanded values. * @api public */ -picomatch.toRegex = (source, options) => { - try { - let opts = options || {}; - return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); - } catch (err) { - if (options && options.debug === true) throw err; - return /$^/; +braces.stringify = (input, options = {}) => { + if (typeof input === 'string') { + return stringify(braces.parse(input, options), options); } + return stringify(input, options); }; /** - * Picomatch constants. - * @return {Object} + * Compiles a brace pattern into a regex-compatible, optimized string. + * This method is called by the main [braces](#braces) function by default. + * + * ```js + * const braces = require('braces'); + * console.log(braces.compile('a/{b,c}/d')); + * //=> ['a/(b|c)/d'] + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public */ -picomatch.constants = __webpack_require__(618); +braces.compile = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + return compile(input, options); +}; /** - * Expose "picomatch" + * Expands a brace pattern into an array. This method is called by the + * main [braces](#braces) function when `options.expand` is true. Before + * using this method it's recommended that you read the [performance notes](#performance)) + * and advantages of using [.compile](#compile) instead. + * + * ```js + * const braces = require('braces'); + * console.log(braces.expand('a/{b,c}/d')); + * //=> ['a/b/d', 'a/c/d']; + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public */ -module.exports = picomatch; - - -/***/ }), -/* 616 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; +braces.expand = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + let result = expand(input, options); -const utils = __webpack_require__(617); + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); + } -const { - CHAR_ASTERISK, /* * */ - CHAR_AT, /* @ */ - CHAR_BACKWARD_SLASH, /* \ */ - CHAR_COMMA, /* , */ - CHAR_DOT, /* . */ - CHAR_EXCLAMATION_MARK, /* ! */ - CHAR_FORWARD_SLASH, /* / */ - CHAR_LEFT_CURLY_BRACE, /* { */ - CHAR_LEFT_PARENTHESES, /* ( */ - CHAR_LEFT_SQUARE_BRACKET, /* [ */ - CHAR_PLUS, /* + */ - CHAR_QUESTION_MARK, /* ? */ - CHAR_RIGHT_CURLY_BRACE, /* } */ - CHAR_RIGHT_PARENTHESES, /* ) */ - CHAR_RIGHT_SQUARE_BRACKET /* ] */ -} = __webpack_require__(618); + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; + } -const isPathSeparator = code => { - return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; + return result; }; /** - * Quickly scans a glob pattern and returns an object with a handful of - * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), - * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). + * Processes a brace pattern and returns either an expanded array + * (if `options.expand` is true), a highly optimized regex-compatible string. + * This method is called by the main [braces](#braces) function. * * ```js - * const pm = require('picomatch'); - * console.log(pm.scan('foo/bar/*.js')); - * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } + * const braces = require('braces'); + * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) + * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' * ``` - * @param {String} `str` + * @param {String} `pattern` Brace pattern * @param {Object} `options` - * @return {Object} Returns an object with tokens and regex source string. + * @return {Array} Returns an array of expanded values. * @api public */ -module.exports = (input, options) => { - let opts = options || {}; - let length = input.length - 1; - let index = -1; - let start = 0; - let lastIndex = 0; - let isGlob = false; - let backslashes = false; - let negated = false; - let braces = 0; - let prev; - let code; - - let braceEscaped = false; - - let eos = () => index >= length; - let advance = () => { - prev = code; - return input.charCodeAt(++index); - }; +braces.create = (input, options = {}) => { + if (input === '' || input.length < 3) { + return [input]; + } - while (index < length) { - code = advance(); - let next; + return options.expand !== true + ? braces.compile(input, options) + : braces.expand(input, options); +}; - if (code === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); +/** + * Expose "braces" + */ - if (next === CHAR_LEFT_CURLY_BRACE) { - braceEscaped = true; - } - continue; - } +module.exports = braces; - if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { - braces++; - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; - } +/***/ }), +/* 610 */ +/***/ (function(module, exports, __webpack_require__) { - if (next === CHAR_LEFT_CURLY_BRACE) { - braces++; - continue; - } +"use strict"; - if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { - isGlob = true; - break; - } - if (!braceEscaped && next === CHAR_COMMA) { - isGlob = true; - break; - } +const utils = __webpack_require__(611); - if (next === CHAR_RIGHT_CURLY_BRACE) { - braces--; - if (braces === 0) { - braceEscaped = false; - break; - } - } - } - } +module.exports = (ast, options = {}) => { + let stringify = (node, parent = {}) => { + let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ''; - if (code === CHAR_FORWARD_SLASH) { - if (prev === CHAR_DOT && index === (start + 1)) { - start += 2; - continue; + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return '\\' + node.value; } - - lastIndex = index + 1; - continue; - } - - if (code === CHAR_ASTERISK) { - isGlob = true; - break; + return node.value; } - if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { - isGlob = true; - break; + if (node.value) { + return node.value; } - if (code === CHAR_LEFT_SQUARE_BRACKET) { - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; - } - - if (next === CHAR_RIGHT_SQUARE_BRACKET) { - isGlob = true; - break; - } + if (node.nodes) { + for (let child of node.nodes) { + output += stringify(child); } } + return output; + }; - let isExtglobChar = code === CHAR_PLUS - || code === CHAR_AT - || code === CHAR_EXCLAMATION_MARK; - - if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { - isGlob = true; - break; - } + return stringify(ast); +}; - if (code === CHAR_EXCLAMATION_MARK && index === start) { - negated = true; - start++; - continue; - } - if (code === CHAR_LEFT_PARENTHESES) { - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; - } - if (next === CHAR_RIGHT_PARENTHESES) { - isGlob = true; - break; - } - } - } +/***/ }), +/* 611 */ +/***/ (function(module, exports, __webpack_require__) { - if (isGlob) { - break; - } - } +"use strict"; - let prefix = ''; - let orig = input; - let base = input; - let glob = ''; - if (start > 0) { - prefix = input.slice(0, start); - input = input.slice(start); - lastIndex -= start; +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); } - - if (base && isGlob === true && lastIndex > 0) { - base = input.slice(0, lastIndex); - glob = input.slice(lastIndex); - } else if (isGlob === true) { - base = ''; - glob = input; - } else { - base = input; + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); } + return false; +}; - if (base && base !== '' && base !== '/' && base !== input) { - if (isPathSeparator(base.charCodeAt(base.length - 1))) { - base = base.slice(0, -1); - } - } +/** + * Find a node of the given type + */ - if (opts.unescape === true) { - if (glob) glob = utils.removeBackslashes(glob); +exports.find = (node, type) => node.nodes.find(node => node.type === type); - if (base && backslashes === true) { - base = utils.removeBackslashes(base); - } - } +/** + * Find a node of the given type + */ - return { prefix, input: orig, base, glob, negated, isGlob }; +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; }; +/** + * Escape the given node with '\\' before node.value + */ -/***/ }), -/* 617 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const path = __webpack_require__(16); -const win32 = process.platform === 'win32'; -const { - REGEX_SPECIAL_CHARS, - REGEX_SPECIAL_CHARS_GLOBAL, - REGEX_REMOVE_BACKSLASH -} = __webpack_require__(618); +exports.escapeNode = (block, n = 0, type) => { + let node = block.nodes[n]; + if (!node) return; -exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); -exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); -exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); -exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); -exports.toPosixSlashes = str => str.replace(/\\/g, '/'); + if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { + if (node.escaped !== true) { + node.value = '\\' + node.value; + node.escaped = true; + } + } +}; -exports.removeBackslashes = str => { - return str.replace(REGEX_REMOVE_BACKSLASH, match => { - return match === '\\' ? '' : match; - }); -} +/** + * Returns true if the given brace node should be enclosed in literal braces + */ -exports.supportsLookbehinds = () => { - let segs = process.version.slice(1).split('.'); - if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { +exports.encloseBrace = node => { + if (node.type !== 'brace') return false; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; return true; } return false; }; -exports.isWindows = options => { - if (options && typeof options.windows === 'boolean') { - return options.windows; +/** + * Returns true if a brace node is invalid. + */ + +exports.isInvalidBrace = block => { + if (block.type !== 'brace') return false; + if (block.invalid === true || block.dollar) return true; + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; } - return win32 === true || path.sep === '\\'; + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; }; -exports.escapeLast = (input, char, lastIdx) => { - let idx = input.lastIndexOf(char, lastIdx); - if (idx === -1) return input; - if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); - return input.slice(0, idx) + '\\' + input.slice(idx); -}; - - -/***/ }), -/* 618 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const path = __webpack_require__(16); -const WIN_SLASH = '\\\\/'; -const WIN_NO_SLASH = `[^${WIN_SLASH}]`; - /** - * Posix glob regex + * Returns true if a node is an open or close node */ -const DOT_LITERAL = '\\.'; -const PLUS_LITERAL = '\\+'; -const QMARK_LITERAL = '\\?'; -const SLASH_LITERAL = '\\/'; -const ONE_CHAR = '(?=.)'; -const QMARK = '[^/]'; -const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; -const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; -const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; -const NO_DOT = `(?!${DOT_LITERAL})`; -const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; -const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; -const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; -const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; -const STAR = `${QMARK}*?`; - -const POSIX_CHARS = { - DOT_LITERAL, - PLUS_LITERAL, - QMARK_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - QMARK, - END_ANCHOR, - DOTS_SLASH, - NO_DOT, - NO_DOTS, - NO_DOT_SLASH, - NO_DOTS_SLASH, - QMARK_NO_DOT, - STAR, - START_ANCHOR +exports.isOpenOrClose = node => { + if (node.type === 'open' || node.type === 'close') { + return true; + } + return node.open === true || node.close === true; }; /** - * Windows glob regex + * Reduce an array of text nodes. */ -const WINDOWS_CHARS = { - ...POSIX_CHARS, - - SLASH_LITERAL: `[${WIN_SLASH}]`, - QMARK: WIN_NO_SLASH, - STAR: `${WIN_NO_SLASH}*?`, - DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, - NO_DOT: `(?!${DOT_LITERAL})`, - NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, - NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, - NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, - QMARK_NO_DOT: `[^.${WIN_SLASH}]`, - START_ANCHOR: `(?:^|[${WIN_SLASH}])`, - END_ANCHOR: `(?:[${WIN_SLASH}]|$)` -}; +exports.reduce = nodes => nodes.reduce((acc, node) => { + if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; + return acc; +}, []); /** - * POSIX Bracket Regex + * Flatten an array */ -const POSIX_REGEX_SOURCE = { - alnum: 'a-zA-Z0-9', - alpha: 'a-zA-Z', - ascii: '\\x00-\\x7F', - blank: ' \\t', - cntrl: '\\x00-\\x1F\\x7F', - digit: '0-9', - graph: '\\x21-\\x7E', - lower: 'a-z', - print: '\\x20-\\x7E ', - punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', - space: ' \\t\\r\\n\\v\\f', - upper: 'A-Z', - word: 'A-Za-z0-9_', - xdigit: 'A-Fa-f0-9' +exports.flatten = (...args) => { + const result = []; + const flat = arr => { + for (let i = 0; i < arr.length; i++) { + let ele = arr[i]; + Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); + } + return result; + }; + flat(args); + return result; }; -module.exports = { - MAX_LENGTH: 1024 * 64, - POSIX_REGEX_SOURCE, - // regular expressions - REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, - REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, - REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, - REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, - REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, - REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, +/***/ }), +/* 612 */ +/***/ (function(module, exports, __webpack_require__) { - // Replace globs with equivalent patterns to reduce parsing time. - REPLACEMENTS: { - '***': '*', - '**/**': '**', - '**/**/**': '**' - }, +"use strict"; - // Digits - CHAR_0: 48, /* 0 */ - CHAR_9: 57, /* 9 */ - // Alphabet chars. - CHAR_UPPERCASE_A: 65, /* A */ - CHAR_LOWERCASE_A: 97, /* a */ - CHAR_UPPERCASE_Z: 90, /* Z */ - CHAR_LOWERCASE_Z: 122, /* z */ +const fill = __webpack_require__(613); +const utils = __webpack_require__(611); - CHAR_LEFT_PARENTHESES: 40, /* ( */ - CHAR_RIGHT_PARENTHESES: 41, /* ) */ +const compile = (ast, options = {}) => { + let walk = (node, parent = {}) => { + let invalidBlock = utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let invalid = invalidBlock === true || invalidNode === true; + let prefix = options.escapeInvalid === true ? '\\' : ''; + let output = ''; - CHAR_ASTERISK: 42, /* * */ + if (node.isOpen === true) { + return prefix + node.value; + } + if (node.isClose === true) { + return prefix + node.value; + } - // Non-alphabetic chars. - CHAR_AMPERSAND: 38, /* & */ - CHAR_AT: 64, /* @ */ - CHAR_BACKWARD_SLASH: 92, /* \ */ - CHAR_CARRIAGE_RETURN: 13, /* \r */ - CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ - CHAR_COLON: 58, /* : */ - CHAR_COMMA: 44, /* , */ - CHAR_DOT: 46, /* . */ - CHAR_DOUBLE_QUOTE: 34, /* " */ - CHAR_EQUAL: 61, /* = */ - CHAR_EXCLAMATION_MARK: 33, /* ! */ - CHAR_FORM_FEED: 12, /* \f */ - CHAR_FORWARD_SLASH: 47, /* / */ - CHAR_GRAVE_ACCENT: 96, /* ` */ - CHAR_HASH: 35, /* # */ - CHAR_HYPHEN_MINUS: 45, /* - */ - CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ - CHAR_LEFT_CURLY_BRACE: 123, /* { */ - CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ - CHAR_LINE_FEED: 10, /* \n */ - CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ - CHAR_PERCENT: 37, /* % */ - CHAR_PLUS: 43, /* + */ - CHAR_QUESTION_MARK: 63, /* ? */ - CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ - CHAR_RIGHT_CURLY_BRACE: 125, /* } */ - CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ - CHAR_SEMICOLON: 59, /* ; */ - CHAR_SINGLE_QUOTE: 39, /* ' */ - CHAR_SPACE: 32, /* */ - CHAR_TAB: 9, /* \t */ - CHAR_UNDERSCORE: 95, /* _ */ - CHAR_VERTICAL_LINE: 124, /* | */ - CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ + if (node.type === 'open') { + return invalid ? (prefix + node.value) : '('; + } - SEP: path.sep, + if (node.type === 'close') { + return invalid ? (prefix + node.value) : ')'; + } - /** - * Create EXTGLOB_CHARS - */ + if (node.type === 'comma') { + return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); + } - extglobChars(chars) { - return { - '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, - '?': { type: 'qmark', open: '(?:', close: ')?' }, - '+': { type: 'plus', open: '(?:', close: ')+' }, - '*': { type: 'star', open: '(?:', close: ')*' }, - '@': { type: 'at', open: '(?:', close: ')' } - }; - }, + if (node.value) { + return node.value; + } - /** - * Create GLOB_CHARS - */ + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + let range = fill(...args, { ...options, wrap: false, toRegex: true }); - globChars(win32) { - return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; - } + if (range.length !== 0) { + return args.length > 1 && range.length > 1 ? `(${range})` : range; + } + } + + if (node.nodes) { + for (let child of node.nodes) { + output += walk(child, node); + } + } + return output; + }; + + return walk(ast); }; +module.exports = compile; + /***/ }), -/* 619 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -const utils = __webpack_require__(617); -const constants = __webpack_require__(618); - -/** - * Constants +/*! + * fill-range <https://github.com/jonschlinkert/fill-range> + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. */ -const { - MAX_LENGTH, - POSIX_REGEX_SOURCE, - REGEX_NON_SPECIAL_CHAR, - REGEX_SPECIAL_CHARS_BACKREF, - REPLACEMENTS -} = constants; -/** - * Helpers - */ -const expandRange = (args, options) => { - if (typeof options.expandRange === 'function') { - return options.expandRange(...args, options); - } +const util = __webpack_require__(29); +const toRegexRange = __webpack_require__(614); - args.sort(); - let value = `[${args.join('-')}]`; +const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); - try { - /* eslint-disable no-new */ - new RegExp(value); - } catch (ex) { - return args.map(v => utils.escapeRegex(v)).join('..'); - } +const transform = toNumber => { + return value => toNumber === true ? Number(value) : String(value); +}; - return value; +const isValidValue = value => { + return typeof value === 'number' || (typeof value === 'string' && value !== ''); }; -const negate = state => { - let count = 1; +const isNumber = num => Number.isInteger(+num); - while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { - state.advance(); - state.start++; - count++; +const zeros = input => { + let value = `${input}`; + let index = -1; + if (value[0] === '-') value = value.slice(1); + if (value === '0') return false; + while (value[++index] === '0'); + return index > 0; +}; + +const stringify = (start, end, options) => { + if (typeof start === 'string' || typeof end === 'string') { + return true; } + return options.stringify === true; +}; - if (count % 2 === 0) { - return false; +const pad = (input, maxLength, toNumber) => { + if (maxLength > 0) { + let dash = input[0] === '-' ? '-' : ''; + if (dash) input = input.slice(1); + input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); + } + if (toNumber === false) { + return String(input); } + return input; +}; - state.negated = true; - state.start++; - return true; +const toMaxLen = (input, maxLength) => { + let negative = input[0] === '-' ? '-' : ''; + if (negative) { + input = input.slice(1); + maxLength--; + } + while (input.length < maxLength) input = '0' + input; + return negative ? ('-' + input) : input; }; -/** - * Create the message for a syntax error - */ +const toSequence = (parts, options) => { + parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); -const syntaxError = (type, char) => { - return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; -}; + let prefix = options.capture ? '' : '?:'; + let positives = ''; + let negatives = ''; + let result; -/** - * Parse the given input string. - * @param {String} input - * @param {Object} options - * @return {Object} - */ + if (parts.positives.length) { + positives = parts.positives.join('|'); + } -const parse = (input, options) => { - if (typeof input !== 'string') { - throw new TypeError('Expected a string'); + if (parts.negatives.length) { + negatives = `-(${prefix}${parts.negatives.join('|')})`; } - input = REPLACEMENTS[input] || input; + if (positives && negatives) { + result = `${positives}|${negatives}`; + } else { + result = positives || negatives; + } - let opts = { ...options }; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - let len = input.length; - if (len > max) { - throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + if (options.wrap) { + return `(${prefix}${result})`; } - let bos = { type: 'bos', value: '', output: opts.prepend || '' }; - let tokens = [bos]; + return result; +}; - let capture = opts.capture ? '' : '?:'; - let win32 = utils.isWindows(options); +const toRange = (a, b, isNumbers, options) => { + if (isNumbers) { + return toRegexRange(a, b, { wrap: false, ...options }); + } - // create constants based on platform, for windows or posix - const PLATFORM_CHARS = constants.globChars(win32); - const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + let start = String.fromCharCode(a); + if (a === b) return start; - const { - DOT_LITERAL, - PLUS_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - DOTS_SLASH, - NO_DOT, - NO_DOT_SLASH, - NO_DOTS_SLASH, - QMARK, - QMARK_NO_DOT, - STAR, - START_ANCHOR - } = PLATFORM_CHARS; + let stop = String.fromCharCode(b); + return `[${start}-${stop}]`; +}; - const globstar = (opts) => { - return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; - }; +const toRegex = (start, end, options) => { + if (Array.isArray(start)) { + let wrap = options.wrap === true; + let prefix = options.capture ? '' : '?:'; + return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); + } + return toRegexRange(start, end, options); +}; - let nodot = opts.dot ? '' : NO_DOT; - let star = opts.bash === true ? globstar(opts) : STAR; - let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; +const rangeError = (...args) => { + return new RangeError('Invalid range arguments: ' + util.inspect(...args)); +}; - if (opts.capture) { - star = `(${star})`; - } +const invalidRange = (start, end, options) => { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; +}; - // minimatch options support - if (typeof opts.noext === 'boolean') { - opts.noextglob = opts.noext; +const invalidStep = (step, options) => { + if (options.strictRanges === true) { + throw new TypeError(`Expected step "${step}" to be a number`); } + return []; +}; - let state = { - index: -1, - start: 0, - consumed: '', - output: '', - backtrack: false, - brackets: 0, - braces: 0, - parens: 0, - quotes: 0, - tokens - }; +const fillNumbers = (start, end, step = 1, options = {}) => { + let a = Number(start); + let b = Number(end); - let extglobs = []; - let stack = []; - let prev = bos; - let value; + if (!Number.isInteger(a) || !Number.isInteger(b)) { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; + } - /** - * Tokenizing helpers - */ + // fix negative zero + if (a === 0) a = 0; + if (b === 0) b = 0; - const eos = () => state.index === len - 1; - const peek = state.peek = (n = 1) => input[state.index + n]; - const advance = state.advance = () => input[++state.index]; - const append = token => { - state.output += token.output != null ? token.output : token.value; - state.consumed += token.value || ''; - }; + let descending = a > b; + let startString = String(start); + let endString = String(end); + let stepString = String(step); + step = Math.max(Math.abs(step), 1); - const increment = type => { - state[type]++; - stack.push(type); - }; + let padded = zeros(startString) || zeros(endString) || zeros(stepString); + let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; + let toNumber = padded === false && stringify(start, end, options) === false; + let format = options.transform || transform(toNumber); - const decrement = type => { - state[type]--; - stack.pop(); - }; + if (options.toRegex && step === 1) { + return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); + } - /** - * Push tokens onto the tokens array. This helper speeds up - * tokenizing by 1) helping us avoid backtracking as much as possible, - * and 2) helping us avoid creating extra tokens when consecutive - * characters are plain text. This improves performance and simplifies - * lookbehinds. - */ + let parts = { negatives: [], positives: [] }; + let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); + let range = []; + let index = 0; - const push = tok => { - if (prev.type === 'globstar') { - let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); - let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); - if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { - state.output = state.output.slice(0, -prev.output.length); - prev.type = 'star'; - prev.value = '*'; - prev.output = star; - state.output += prev.output; - } + while (descending ? a >= b : a <= b) { + if (options.toRegex === true && step > 1) { + push(a); + } else { + range.push(pad(format(a, index), maxLen, toNumber)); } + a = descending ? a - step : a + step; + index++; + } - if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { - extglobs[extglobs.length - 1].inner += tok.value; - } - - if (tok.value || tok.output) append(tok); - if (prev && prev.type === 'text' && tok.type === 'text') { - prev.value += tok.value; - return; - } + if (options.toRegex === true) { + return step > 1 + ? toSequence(parts, options) + : toRegex(range, null, { wrap: false, ...options }); + } - tok.prev = prev; - tokens.push(tok); - prev = tok; - }; + return range; +}; - const extglobOpen = (type, value) => { - let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; +const fillLetters = (start, end, step = 1, options = {}) => { + if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { + return invalidRange(start, end, options); + } - token.prev = prev; - token.parens = state.parens; - token.output = state.output; - let output = (opts.capture ? '(' : '') + token.open; - push({ type, value, output: state.output ? '' : ONE_CHAR }); - push({ type: 'paren', extglob: true, value: advance(), output }); - increment('parens'); - extglobs.push(token); - }; + let format = options.transform || (val => String.fromCharCode(val)); + let a = `${start}`.charCodeAt(0); + let b = `${end}`.charCodeAt(0); - const extglobClose = token => { - let output = token.close + (opts.capture ? ')' : ''); + let descending = a > b; + let min = Math.min(a, b); + let max = Math.max(a, b); - if (token.type === 'negate') { - let extglobStar = star; + if (options.toRegex && step === 1) { + return toRange(min, max, false, options); + } - if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { - extglobStar = globstar(opts); - } + let range = []; + let index = 0; - if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { - output = token.close = ')$))' + extglobStar; - } + while (descending ? a >= b : a <= b) { + range.push(format(a, index)); + a = descending ? a - step : a + step; + index++; + } - if (token.prev.type === 'bos' && eos()) { - state.negatedExtglob = true; - } - } + if (options.toRegex === true) { + return toRegex(range, null, { wrap: false, options }); + } - push({ type: 'paren', extglob: true, value, output }); - decrement('parens'); - }; + return range; +}; - if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { - let backslashes = false; +const fill = (start, end, step, options = {}) => { + if (end == null && isValidValue(start)) { + return [start]; + } - let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { - if (first === '\\') { - backslashes = true; - return m; - } + if (!isValidValue(start) || !isValidValue(end)) { + return invalidRange(start, end, options); + } - if (first === '?') { - if (esc) { - return esc + first + (rest ? QMARK.repeat(rest.length) : ''); - } - if (index === 0) { - return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); - } - return QMARK.repeat(chars.length); - } + if (typeof step === 'function') { + return fill(start, end, 1, { transform: step }); + } - if (first === '.') { - return DOT_LITERAL.repeat(chars.length); - } + if (isObject(step)) { + return fill(start, end, 0, step); + } - if (first === '*') { - if (esc) { - return esc + first + (rest ? star : ''); - } - return star; - } - return esc ? m : '\\' + m; - }); + let opts = { ...options }; + if (opts.capture === true) opts.wrap = true; + step = step || opts.step || 1; - if (backslashes === true) { - if (opts.unescape === true) { - output = output.replace(/\\/g, ''); - } else { - output = output.replace(/\\+/g, m => { - return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); - }); - } - } + if (!isNumber(step)) { + if (step != null && !isObject(step)) return invalidStep(step, opts); + return fill(start, end, 1, step); + } - state.output = output; - return state; + if (isNumber(start) && isNumber(end)) { + return fillNumbers(start, end, step, opts); } - /** - * Tokenize input until we reach end-of-string - */ + return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); +}; - while (!eos()) { - value = advance(); +module.exports = fill; - if (value === '\u0000') { - continue; - } - /** - * Escaped characters - */ +/***/ }), +/* 614 */ +/***/ (function(module, exports, __webpack_require__) { - if (value === '\\') { - let next = peek(); +"use strict"; +/*! + * to-regex-range <https://github.com/micromatch/to-regex-range> + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ - if (next === '/' && opts.bash !== true) { - continue; - } - if (next === '.' || next === ';') { - continue; - } - if (!next) { - value += '\\'; - push({ type: 'text', value }); - continue; - } +const isNumber = __webpack_require__(615); - // collapse slashes to reduce potential for exploits - let match = /^\\+/.exec(input.slice(state.index + 1)); - let slashes = 0; +const toRegexRange = (min, max, options) => { + if (isNumber(min) === false) { + throw new TypeError('toRegexRange: expected the first argument to be a number'); + } - if (match && match[0].length > 2) { - slashes = match[0].length; - state.index += slashes; - if (slashes % 2 !== 0) { - value += '\\'; - } - } + if (max === void 0 || min === max) { + return String(min); + } - if (opts.unescape === true) { - value = advance() || ''; - } else { - value += advance() || ''; - } + if (isNumber(max) === false) { + throw new TypeError('toRegexRange: expected the second argument to be a number.'); + } - if (state.brackets === 0) { - push({ type: 'text', value }); - continue; - } - } + let opts = { relaxZeros: true, ...options }; + if (typeof opts.strictZeros === 'boolean') { + opts.relaxZeros = opts.strictZeros === false; + } - /** - * If we're inside a regex character class, continue - * until we reach the closing bracket. - */ + let relax = String(opts.relaxZeros); + let shorthand = String(opts.shorthand); + let capture = String(opts.capture); + let wrap = String(opts.wrap); + let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; - if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { - if (opts.posix !== false && value === ':') { - let inner = prev.value.slice(1); - if (inner.includes('[')) { - prev.posix = true; + if (toRegexRange.cache.hasOwnProperty(cacheKey)) { + return toRegexRange.cache[cacheKey].result; + } - if (inner.includes(':')) { - let idx = prev.value.lastIndexOf('['); - let pre = prev.value.slice(0, idx); - let rest = prev.value.slice(idx + 2); - let posix = POSIX_REGEX_SOURCE[rest]; - if (posix) { - prev.value = pre + posix; - state.backtrack = true; - advance(); + let a = Math.min(min, max); + let b = Math.max(min, max); - if (!bos.output && tokens.indexOf(prev) === 1) { - bos.output = ONE_CHAR; - } - continue; - } - } - } - } + if (Math.abs(a - b) === 1) { + let result = min + '|' + max; + if (opts.capture) { + return `(${result})`; + } + if (opts.wrap === false) { + return result; + } + return `(?:${result})`; + } - if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { - value = '\\' + value; - } + let isPadded = hasPadding(min) || hasPadding(max); + let state = { min, max, a, b }; + let positives = []; + let negatives = []; - if (value === ']' && (prev.value === '[' || prev.value === '[^')) { - value = '\\' + value; - } + if (isPadded) { + state.isPadded = isPadded; + state.maxLen = String(state.max).length; + } - if (opts.posix === true && value === '!' && prev.value === '[') { - value = '^'; - } + if (a < 0) { + let newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } - prev.value += value; - append({ value }); - continue; - } + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); + } - /** - * If we're inside a quoted string, continue - * until we reach the closing double quote. - */ + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives, opts); - if (state.quotes === 1 && value !== '"') { - value = utils.escapeRegex(value); - prev.value += value; - append({ value }); - continue; - } + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { + state.result = `(?:${state.result})`; + } - /** - * Double quotes - */ + toRegexRange.cache[cacheKey] = state; + return state.result; +}; - if (value === '"') { - state.quotes = state.quotes === 1 ? 0 : 1; - if (opts.keepQuotes === true) { - push({ type: 'text', value }); - } - continue; - } +function collatePatterns(neg, pos, options) { + let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; + let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; + let intersected = filterPatterns(neg, pos, '-?', true, options) || []; + let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); + return subpatterns.join('|'); +} - /** - * Parentheses - */ +function splitToRanges(min, max) { + let nines = 1; + let zeros = 1; - if (value === '(') { - push({ type: 'paren', value }); - increment('parens'); - continue; - } + let stop = countNines(min, nines); + let stops = new Set([max]); - if (value === ')') { - if (state.parens === 0 && opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('opening', '(')); - } + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); + } - let extglob = extglobs[extglobs.length - 1]; - if (extglob && state.parens === extglob.parens + 1) { - extglobClose(extglobs.pop()); - continue; - } + stop = countZeros(max + 1, zeros) - 1; - push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); - decrement('parens'); - continue; - } + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; + } - /** - * Brackets - */ + stops = [...stops]; + stops.sort(compare); + return stops; +} - if (value === '[') { - if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { - if (opts.nobracket !== true && opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('closing', ']')); - } +/** + * Convert a range to a regex pattern + * @param {Number} `start` + * @param {Number} `stop` + * @return {String} + */ - value = '\\' + value; - } else { - increment('brackets'); - } +function rangeToPattern(start, stop, options) { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; + } - push({ type: 'bracket', value }); - continue; - } + let zipped = zip(start, stop); + let digits = zipped.length; + let pattern = ''; + let count = 0; - if (value === ']') { - if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { - push({ type: 'text', value, output: '\\' + value }); - continue; - } + for (let i = 0; i < digits; i++) { + let [startDigit, stopDigit] = zipped[i]; - if (state.brackets === 0) { - if (opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('opening', '[')); - } + if (startDigit === stopDigit) { + pattern += startDigit; - push({ type: 'text', value, output: '\\' + value }); - continue; - } + } else if (startDigit !== '0' || stopDigit !== '9') { + pattern += toCharacterClass(startDigit, stopDigit, options); - decrement('brackets'); + } else { + count++; + } + } - let prevValue = prev.value.slice(1); - if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { - value = '/' + value; - } + if (count) { + pattern += options.shorthand === true ? '\\d' : '[0-9]'; + } - prev.value += value; - append({ value }); + return { pattern, count: [count], digits }; +} - // when literal brackets are explicitly disabled - // assume we should match with a regex character class - if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { - continue; - } +function splitToPatterns(min, max, tok, options) { + let ranges = splitToRanges(min, max); + let tokens = []; + let start = min; + let prev; - let escaped = utils.escapeRegex(prev.value); - state.output = state.output.slice(0, -prev.value.length); + for (let i = 0; i < ranges.length; i++) { + let max = ranges[i]; + let obj = rangeToPattern(String(start), String(max), options); + let zeros = ''; - // when literal brackets are explicitly enabled - // assume we should escape the brackets to match literal characters - if (opts.literalBrackets === true) { - state.output += escaped; - prev.value = escaped; - continue; + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); } - // when the user specifies nothing, try to match both - prev.value = `(${capture}${escaped}|${prev.value})`; - state.output += prev.value; + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = max + 1; continue; } - /** - * Braces - */ - - if (value === '{' && opts.nobrace !== true) { - push({ type: 'brace', value, output: '(' }); - increment('braces'); - continue; + if (tok.isPadded) { + zeros = padZeros(max, tok, options); } - if (value === '}') { - if (opts.nobrace === true || state.braces === 0) { - push({ type: 'text', value, output: '\\' + value }); - continue; - } + obj.string = zeros + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = max + 1; + prev = obj; + } - let output = ')'; + return tokens; +} - if (state.dots === true) { - let arr = tokens.slice(); - let range = []; +function filterPatterns(arr, comparison, prefix, intersection, options) { + let result = []; - for (let i = arr.length - 1; i >= 0; i--) { - tokens.pop(); - if (arr[i].type === 'brace') { - break; - } - if (arr[i].type !== 'dots') { - range.unshift(arr[i].value); - } - } + for (let ele of arr) { + let { string } = ele; - output = expandRange(range, opts); - state.backtrack = true; - } + // only push if _both_ are negative... + if (!intersection && !contains(comparison, 'string', string)) { + result.push(prefix + string); + } - push({ type: 'brace', value, output }); - decrement('braces'); - continue; + // or _both_ are positive + if (intersection && contains(comparison, 'string', string)) { + result.push(prefix + string); } + } + return result; +} - /** - * Pipes - */ +/** + * Zip strings + */ - if (value === '|') { - if (extglobs.length > 0) { - extglobs[extglobs.length - 1].conditions++; - } - push({ type: 'text', value }); - continue; - } +function zip(a, b) { + let arr = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; +} - /** - * Commas - */ +function compare(a, b) { + return a > b ? 1 : b > a ? -1 : 0; +} - if (value === ',') { - let output = value; +function contains(arr, key, val) { + return arr.some(ele => ele[key] === val); +} - if (state.braces > 0 && stack[stack.length - 1] === 'braces') { - output = '|'; - } +function countNines(min, len) { + return Number(String(min).slice(0, -len) + '9'.repeat(len)); +} - push({ type: 'comma', value, output }); - continue; - } +function countZeros(integer, zeros) { + return integer - (integer % Math.pow(10, zeros)); +} - /** - * Slashes - */ +function toQuantifier(digits) { + let [start = 0, stop = ''] = digits; + if (stop || start > 1) { + return `{${start + (stop ? ',' + stop : '')}}`; + } + return ''; +} - if (value === '/') { - // if the beginning of the glob is "./", advance the start - // to the current index, and don't add the "./" characters - // to the state. This greatly simplifies lookbehinds when - // checking for BOS characters like "!" and "." (not "./") - if (prev.type === 'dot' && state.index === 1) { - state.start = state.index + 1; - state.consumed = ''; - state.output = ''; - tokens.pop(); - prev = bos; // reset "prev" to the first token - continue; - } +function toCharacterClass(a, b, options) { + return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; +} - push({ type: 'slash', value, output: SLASH_LITERAL }); - continue; - } +function hasPadding(str) { + return /^-?(0+)\d/.test(str); +} - /** - * Dots - */ +function padZeros(value, tok, options) { + if (!tok.isPadded) { + return value; + } - if (value === '.') { - if (state.braces > 0 && prev.type === 'dot') { - if (prev.value === '.') prev.output = DOT_LITERAL; - prev.type = 'dots'; - prev.output += value; - prev.value += value; - state.dots = true; - continue; - } + let diff = Math.abs(tok.maxLen - String(value).length); + let relax = options.relaxZeros !== false; - push({ type: 'dot', value, output: DOT_LITERAL }); - continue; + switch (diff) { + case 0: + return ''; + case 1: + return relax ? '0?' : '0'; + case 2: + return relax ? '0{0,2}' : '00'; + default: { + return relax ? `0{0,${diff}}` : `0{${diff}}`; } + } +} - /** - * Question marks - */ +/** + * Cache + */ - if (value === '?') { - if (prev && prev.type === 'paren') { - let next = peek(); - let output = value; +toRegexRange.cache = {}; +toRegexRange.clearCache = () => (toRegexRange.cache = {}); - if (next === '<' && !utils.supportsLookbehinds()) { - throw new Error('Node.js v10 or higher is required for regex lookbehinds'); - } +/** + * Expose `toRegexRange` + */ - if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { - output = '\\' + value; - } +module.exports = toRegexRange; - push({ type: 'text', value, output }); - continue; - } - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('qmark', value); - continue; - } +/***/ }), +/* 615 */ +/***/ (function(module, exports, __webpack_require__) { - if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { - push({ type: 'qmark', value, output: QMARK_NO_DOT }); - continue; - } +"use strict"; +/*! + * is-number <https://github.com/jonschlinkert/is-number> + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ - push({ type: 'qmark', value, output: QMARK }); - continue; - } - /** - * Exclamation - */ - if (value === '!') { - if (opts.noextglob !== true && peek() === '(') { - if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { - extglobOpen('negate', value); - continue; - } - } +module.exports = function(num) { + if (typeof num === 'number') { + return num - num === 0; + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + } + return false; +}; - if (opts.nonegate !== true && state.index === 0) { - negate(state); - continue; - } - } - /** - * Plus - */ +/***/ }), +/* 616 */ +/***/ (function(module, exports, __webpack_require__) { - if (value === '+') { - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('plus', value); - continue; - } +"use strict"; - if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { - let output = prev.extglob === true ? '\\' + value : value; - push({ type: 'plus', value, output }); - continue; - } - // use regex behavior inside parens - if (state.parens > 0 && opts.regex !== false) { - push({ type: 'plus', value }); - continue; - } +const fill = __webpack_require__(613); +const stringify = __webpack_require__(610); +const utils = __webpack_require__(611); - push({ type: 'plus', value: PLUS_LITERAL }); - continue; - } +const append = (queue = '', stash = '', enclose = false) => { + let result = []; - /** - * Plain text - */ + queue = [].concat(queue); + stash = [].concat(stash); - if (value === '@') { - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - push({ type: 'at', value, output: '' }); - continue; - } + if (!stash.length) return queue; + if (!queue.length) { + return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + } - push({ type: 'text', value }); - continue; + for (let item of queue) { + if (Array.isArray(item)) { + for (let value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); + } } + } + return utils.flatten(result); +}; - /** - * Plain text - */ +const expand = (ast, options = {}) => { + let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; - if (value !== '*') { - if (value === '$' || value === '^') { - value = '\\' + value; - } + let walk = (node, parent = {}) => { + node.queue = []; - let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); - if (match) { - value += match[0]; - state.index += match[0].length; - } + let p = parent; + let q = parent.queue; - push({ type: 'text', value }); - continue; + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; } - /** - * Stars - */ - - if (prev && (prev.type === 'globstar' || prev.star === true)) { - prev.type = 'star'; - prev.star = true; - prev.value += value; - prev.output = star; - state.backtrack = true; - state.consumed += value; - continue; + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; } - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('star', value); - continue; + if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { + q.push(append(q.pop(), ['{}'])); + return; } - if (prev.type === 'star') { - if (opts.noglobstar === true) { - state.consumed += value; - continue; - } - - let prior = prev.prev; - let before = prior.prev; - let isStart = prior.type === 'slash' || prior.type === 'bos'; - let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); - if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { - push({ type: 'star', value, output: '' }); - continue; + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); } - let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); - let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); - if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { - push({ type: 'star', value, output: '' }); - continue; + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); } - // strip consecutive `/**/` - while (input.slice(state.index + 1, state.index + 4) === '/**') { - let after = input[state.index + 4]; - if (after && after !== '/') { - break; - } - state.consumed += '/**'; - state.index += 3; - } + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } - if (prior.type === 'bos' && eos()) { - prev.type = 'globstar'; - prev.value += value; - prev.output = globstar(opts); - state.output = prev.output; - state.consumed += value; - continue; - } + let enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; - if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { - state.output = state.output.slice(0, -(prior.output + prev.output).length); - prior.output = '(?:' + prior.output; + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; + } - prev.type = 'globstar'; - prev.output = globstar(opts) + '|$)'; - prev.value += value; + for (let i = 0; i < node.nodes.length; i++) { + let child = node.nodes[i]; - state.output += prior.output + prev.output; - state.consumed += value; + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); continue; } - let next = peek(); - if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { - let end = peek(2) !== void 0 ? '|$' : ''; - - state.output = state.output.slice(0, -(prior.output + prev.output).length); - prior.output = '(?:' + prior.output; - - prev.type = 'globstar'; - prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; - prev.value += value; - - state.output += prior.output + prev.output; - state.consumed += value + advance(); - - push({ type: 'slash', value, output: '' }); + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); continue; } - if (prior.type === 'bos' && next === '/') { - prev.type = 'globstar'; - prev.value += value; - prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; - state.output = prev.output; - state.consumed += value + advance(); - push({ type: 'slash', value, output: '' }); + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); continue; } - // remove single star from output - state.output = state.output.slice(0, -prev.output.length); + if (child.nodes) { + walk(child, node); + } + } - // reset previous token to globstar - prev.type = 'globstar'; - prev.output = globstar(opts); - prev.value += value; + return queue; + }; - // reset output with globstar - state.output += prev.output; - state.consumed += value; - continue; - } + return utils.flatten(walk(ast)); +}; - let token = { type: 'star', value, output: star }; +module.exports = expand; - if (opts.bash === true) { - token.output = '.*?'; - if (prev.type === 'bos' || prev.type === 'slash') { - token.output = nodot + token.output; - } - push(token); - continue; - } - if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { - token.output = value; - push(token); - continue; - } +/***/ }), +/* 617 */ +/***/ (function(module, exports, __webpack_require__) { - if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { - if (prev.type === 'dot') { - state.output += NO_DOT_SLASH; - prev.output += NO_DOT_SLASH; +"use strict"; - } else if (opts.dot === true) { - state.output += NO_DOTS_SLASH; - prev.output += NO_DOTS_SLASH; - } else { - state.output += nodot; - prev.output += nodot; - } +const stringify = __webpack_require__(610); - if (peek() !== '*') { - state.output += ONE_CHAR; - prev.output += ONE_CHAR; - } - } +/** + * Constants + */ - push(token); - } +const { + MAX_LENGTH, + CHAR_BACKSLASH, /* \ */ + CHAR_BACKTICK, /* ` */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_RIGHT_SQUARE_BRACKET, /* ] */ + CHAR_DOUBLE_QUOTE, /* " */ + CHAR_SINGLE_QUOTE, /* ' */ + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE +} = __webpack_require__(618); - while (state.brackets > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); - state.output = utils.escapeLast(state.output, '['); - decrement('brackets'); - } +/** + * parse + */ - while (state.parens > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); - state.output = utils.escapeLast(state.output, '('); - decrement('parens'); +const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); } - while (state.braces > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); - state.output = utils.escapeLast(state.output, '{'); - decrement('braces'); + let opts = options || {}; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); } - if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { - push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); - } + let ast = { type: 'root', input, nodes: [] }; + let stack = [ast]; + let block = ast; + let prev = ast; + let brackets = 0; + let length = input.length; + let index = 0; + let depth = 0; + let value; + let memo = {}; - // rebuild the output if we had to backtrack at any point - if (state.backtrack === true) { - state.output = ''; + /** + * Helpers + */ - for (let token of state.tokens) { - state.output += token.output != null ? token.output : token.value; + const advance = () => input[index++]; + const push = node => { + if (node.type === 'text' && prev.type === 'dot') { + prev.type = 'text'; + } - if (token.suffix) { - state.output += token.suffix; - } + if (prev && prev.type === 'text' && node.type === 'text') { + prev.value += node.value; + return; } - } - return state; -}; + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; -/** - * Fast paths for creating regular expressions for common glob patterns. - * This can significantly speed up processing and has very little downside - * impact when none of the fast paths match. - */ + push({ type: 'bos' }); -parse.fastpaths = (input, options) => { - let opts = { ...options }; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - let len = input.length; - if (len > max) { - throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); - } + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); - input = REPLACEMENTS[input] || input; - let win32 = utils.isWindows(options); + /** + * Invalid chars + */ - // create constants based on platform, for windows or posix - const { - DOT_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - DOTS_SLASH, - NO_DOT, - NO_DOTS, - NO_DOTS_SLASH, - STAR, - START_ANCHOR - } = constants.globChars(win32); + if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { + continue; + } - let capture = opts.capture ? '' : '?:'; - let star = opts.bash === true ? '.*?' : STAR; - let nodot = opts.dot ? NO_DOTS : NO_DOT; - let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + /** + * Escaped chars + */ - if (opts.capture) { - star = `(${star})`; - } + if (value === CHAR_BACKSLASH) { + push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); + continue; + } - const globstar = (opts) => { - return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; - }; + /** + * Right square bracket (literal): ']' + */ - const create = str => { - switch (str) { - case '*': - return `${nodot}${ONE_CHAR}${star}`; + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } - case '.*': - return `${DOT_LITERAL}${ONE_CHAR}${star}`; + /** + * Left square bracket: '[' + */ - case '*.*': - return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; - case '*/*': - return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + let closed = true; + let next; - case '**': - return nodot + globstar(opts); + while (index < length && (next = advance())) { + value += next; - case '**/*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; + } - case '**/*.*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; + } - case '**/.*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; - default: { - let match = /^(.*?)\.(\w+)$/.exec(str); - if (!match) return; + if (brackets === 0) { + break; + } + } + } - let source = create(match[1], options); - if (!source) return; + push({ type: 'text', value }); + continue; + } - return source + DOT_LITERAL + match[2]; + /** + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; + } + + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; } - }; - let output = create(input); - if (output && opts.strictSlashes !== true) { - output += `${SLASH_LITERAL}?`; - } + /** + * Quotes: '|"|` + */ - return output; -}; + if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { + let open = value; + let next; -module.exports = parse; + if (options.keepQuotes !== true) { + value = ''; + } + while (index < length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } -/***/ }), -/* 620 */ -/***/ (function(module, exports, __webpack_require__) { + if (next === open) { + if (options.keepQuotes === true) value += next; + break; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const merge2 = __webpack_require__(591); -function merge(streams) { - const mergedStream = merge2(streams); - streams.forEach((stream) => { - stream.once('error', (err) => mergedStream.emit('error', err)); - }); - return mergedStream; -} -exports.merge = merge; + value += next; + } + push({ type: 'text', value }); + continue; + } -/***/ }), -/* 621 */ -/***/ (function(module, exports, __webpack_require__) { + /** + * Left curly brace: '{' + */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(622); -const provider_1 = __webpack_require__(649); -class ProviderAsync extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new stream_1.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const entries = []; - return new Promise((resolve, reject) => { - const stream = this.api(root, task, options); - stream.once('error', reject); - stream.on('data', (entry) => entries.push(options.transform(entry))); - stream.once('end', () => resolve(entries)); - }); - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderAsync; + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; + let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + let brace = { + type: 'brace', + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [] + }; -/***/ }), -/* 622 */ -/***/ (function(module, exports, __webpack_require__) { + block = push(brace); + stack.push(block); + push({ type: 'open', value }); + continue; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(27); -const fsStat = __webpack_require__(623); -const fsWalk = __webpack_require__(628); -const reader_1 = __webpack_require__(648); -class ReaderStream extends reader_1.default { - constructor() { - super(...arguments); - this._walkStream = fsWalk.walkStream; - this._stat = fsStat.stat; - } - dynamic(root, options) { - return this._walkStream(root, options); - } - static(patterns, options) { - const filepaths = patterns.map(this._getFullEntryPath, this); - const stream = new stream_1.PassThrough({ objectMode: true }); - stream._write = (index, _enc, done) => { - return this._getEntry(filepaths[index], patterns[index], options) - .then((entry) => { - if (entry !== null && options.entryFilter(entry)) { - stream.push(entry); - } - if (index === filepaths.length - 1) { - stream.end(); - } - done(); - }) - .catch(done); - }; - for (let i = 0; i < filepaths.length; i++) { - stream.write(i); - } - return stream; - } - _getEntry(filepath, pattern, options) { - return this._getStat(filepath) - .then((stats) => this._makeEntry(stats, pattern)) - .catch((error) => { - if (options.errorFilter(error)) { - return null; - } - throw error; - }); - } - _getStat(filepath) { - return new Promise((resolve, reject) => { - this._stat(filepath, this._fsStatSettings, (error, stats) => { - error ? reject(error) : resolve(stats); - }); - }); - } -} -exports.default = ReaderStream; + /** + * Right curly brace: '}' + */ + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== 'brace') { + push({ type: 'text', value }); + continue; + } -/***/ }), -/* 623 */ -/***/ (function(module, exports, __webpack_require__) { + let type = 'close'; + block = stack.pop(); + block.close = true; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(624); -const sync = __webpack_require__(625); -const settings_1 = __webpack_require__(626); -exports.Settings = settings_1.default; -function stat(path, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return async.read(path, getSettings(), optionsOrSettingsOrCallback); - } - async.read(path, getSettings(optionsOrSettingsOrCallback), callback); -} -exports.stat = stat; -function statSync(path, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - return sync.read(path, settings); -} -exports.statSync = statSync; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} + push({ type, value }); + depth--; + block = stack[stack.length - 1]; + continue; + } -/***/ }), -/* 624 */ -/***/ (function(module, exports, __webpack_require__) { + /** + * Comma: ',' + */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function read(path, settings, callback) { - settings.fs.lstat(path, (lstatError, lstat) => { - if (lstatError) { - return callFailureCallback(callback, lstatError); - } - if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { - return callSuccessCallback(callback, lstat); - } - settings.fs.stat(path, (statError, stat) => { - if (statError) { - if (settings.throwErrorOnBrokenSymbolicLink) { - return callFailureCallback(callback, statError); - } - return callSuccessCallback(callback, lstat); - } - if (settings.markSymbolicLink) { - stat.isSymbolicLink = () => true; - } - callSuccessCallback(callback, stat); - }); - }); -} -exports.read = read; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, result) { - callback(null, result); -} + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + let open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } + push({ type: 'comma', value }); + block.commas++; + continue; + } -/***/ }), -/* 625 */ -/***/ (function(module, exports, __webpack_require__) { + /** + * Dot: '.' + */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function read(path, settings) { - const lstat = settings.fs.lstatSync(path); - if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { - return lstat; - } - try { - const stat = settings.fs.statSync(path); - if (settings.markSymbolicLink) { - stat.isSymbolicLink = () => true; - } - return stat; - } - catch (error) { - if (!settings.throwErrorOnBrokenSymbolicLink) { - return lstat; - } - throw error; - } -} -exports.read = read; + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + let siblings = block.nodes; + if (depth === 0 || siblings.length === 0) { + push({ type: 'text', value }); + continue; + } -/***/ }), -/* 626 */ -/***/ (function(module, exports, __webpack_require__) { + if (prev.type === 'dot') { + block.range = []; + prev.value += value; + prev.type = 'range'; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(627); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); - this.fs = fs.createFileSystemAdapter(this._options.fs); - this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = 'text'; + continue; + } + block.ranges++; + block.args = []; + continue; + } -/***/ }), -/* 627 */ -/***/ (function(module, exports, __webpack_require__) { + if (prev.type === 'range') { + siblings.pop(); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -exports.FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - stat: fs.stat, - lstatSync: fs.lstatSync, - statSync: fs.statSync -}; -function createFileSystemAdapter(fsMethods) { - if (!fsMethods) { - return exports.FILE_SYSTEM_ADAPTER; - } - return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); -} -exports.createFileSystemAdapter = createFileSystemAdapter; + let before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } + push({ type: 'dot', value }); + continue; + } -/***/ }), -/* 628 */ -/***/ (function(module, exports, __webpack_require__) { + /** + * Text + */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(629); -const stream_1 = __webpack_require__(644); -const sync_1 = __webpack_require__(645); -const settings_1 = __webpack_require__(647); -exports.Settings = settings_1.default; -function walk(dir, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); - } - new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); -} -exports.walk = walk; -function walkSync(dir, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - const provider = new sync_1.default(dir, settings); - return provider.read(); -} -exports.walkSync = walkSync; -function walkStream(dir, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - const provider = new stream_1.default(dir, settings); - return provider.read(); -} -exports.walkStream = walkStream; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} + push({ type: 'text', value }); + } + // Mark imbalanced braces and brackets as invalid + do { + block = stack.pop(); -/***/ }), -/* 629 */ -/***/ (function(module, exports, __webpack_require__) { + if (block.type !== 'root') { + block.nodes.forEach(node => { + if (!node.nodes) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + node.invalid = true; + } + }); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(630); -class AsyncProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new async_1.default(this._root, this._settings); - this._storage = new Set(); - } - read(callback) { - this._reader.onError((error) => { - callFailureCallback(callback, error); - }); - this._reader.onEntry((entry) => { - this._storage.add(entry); - }); - this._reader.onEnd(() => { - callSuccessCallback(callback, Array.from(this._storage)); - }); - this._reader.read(); - } -} -exports.default = AsyncProvider; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, entries) { - callback(null, entries); -} + // get the location of the block on parent.nodes (block's siblings) + let parent = stack[stack.length - 1]; + let index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes + parent.nodes.splice(index, 1, ...block.nodes); + } + } while (stack.length > 0); + + push({ type: 'eos' }); + return ast; +}; + +module.exports = parse; /***/ }), -/* 630 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const events_1 = __webpack_require__(379); -const fsScandir = __webpack_require__(631); -const fastq = __webpack_require__(640); -const common = __webpack_require__(642); -const reader_1 = __webpack_require__(643); -class AsyncReader extends reader_1.default { - constructor(_root, _settings) { - super(_root, _settings); - this._settings = _settings; - this._scandir = fsScandir.scandir; - this._emitter = new events_1.EventEmitter(); - this._queue = fastq(this._worker.bind(this), this._settings.concurrency); - this._isFatalError = false; - this._isDestroyed = false; - this._queue.drain = () => { - if (!this._isFatalError) { - this._emitter.emit('end'); - } - }; - } - read() { - this._isFatalError = false; - this._isDestroyed = false; - setImmediate(() => { - this._pushToQueue(this._root, this._settings.basePath); - }); - return this._emitter; - } - destroy() { - if (this._isDestroyed) { - throw new Error('The reader is already destroyed'); - } - this._isDestroyed = true; - this._queue.killAndDrain(); - } - onEntry(callback) { - this._emitter.on('entry', callback); - } - onError(callback) { - this._emitter.once('error', callback); - } - onEnd(callback) { - this._emitter.once('end', callback); - } - _pushToQueue(dir, base) { - const queueItem = { dir, base }; - this._queue.push(queueItem, (error) => { - if (error) { - this._handleError(error); - } - }); - } - _worker(item, done) { - this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { - if (error) { - return done(error, undefined); - } - for (const entry of entries) { - this._handleEntry(entry, item.base); - } - done(null, undefined); - }); - } - _handleError(error) { - if (!common.isFatalError(this._settings, error)) { - return; - } - this._isFatalError = true; - this._isDestroyed = true; - this._emitter.emit('error', error); - } - _handleEntry(entry, base) { - if (this._isDestroyed || this._isFatalError) { - return; - } - const fullpath = entry.path; - if (base !== undefined) { - entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); - } - if (common.isAppliedFilter(this._settings.entryFilter, entry)) { - this._emitEntry(entry); - } - if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { - this._pushToQueue(fullpath, entry.path); - } - } - _emitEntry(entry) { - this._emitter.emit('entry', entry); - } -} -exports.default = AsyncReader; + + +module.exports = { + MAX_LENGTH: 1024 * 64, + + // Digits + CHAR_0: '0', /* 0 */ + CHAR_9: '9', /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 'A', /* A */ + CHAR_LOWERCASE_A: 'a', /* a */ + CHAR_UPPERCASE_Z: 'Z', /* Z */ + CHAR_LOWERCASE_Z: 'z', /* z */ + + CHAR_LEFT_PARENTHESES: '(', /* ( */ + CHAR_RIGHT_PARENTHESES: ')', /* ) */ + + CHAR_ASTERISK: '*', /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: '&', /* & */ + CHAR_AT: '@', /* @ */ + CHAR_BACKSLASH: '\\', /* \ */ + CHAR_BACKTICK: '`', /* ` */ + CHAR_CARRIAGE_RETURN: '\r', /* \r */ + CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ + CHAR_COLON: ':', /* : */ + CHAR_COMMA: ',', /* , */ + CHAR_DOLLAR: '$', /* . */ + CHAR_DOT: '.', /* . */ + CHAR_DOUBLE_QUOTE: '"', /* " */ + CHAR_EQUAL: '=', /* = */ + CHAR_EXCLAMATION_MARK: '!', /* ! */ + CHAR_FORM_FEED: '\f', /* \f */ + CHAR_FORWARD_SLASH: '/', /* / */ + CHAR_HASH: '#', /* # */ + CHAR_HYPHEN_MINUS: '-', /* - */ + CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ + CHAR_LEFT_CURLY_BRACE: '{', /* { */ + CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ + CHAR_LINE_FEED: '\n', /* \n */ + CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ + CHAR_PERCENT: '%', /* % */ + CHAR_PLUS: '+', /* + */ + CHAR_QUESTION_MARK: '?', /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ + CHAR_RIGHT_CURLY_BRACE: '}', /* } */ + CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ + CHAR_SEMICOLON: ';', /* ; */ + CHAR_SINGLE_QUOTE: '\'', /* ' */ + CHAR_SPACE: ' ', /* */ + CHAR_TAB: '\t', /* \t */ + CHAR_UNDERSCORE: '_', /* _ */ + CHAR_VERTICAL_LINE: '|', /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ +}; /***/ }), -/* 631 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(632); -const sync = __webpack_require__(637); -const settings_1 = __webpack_require__(638); -exports.Settings = settings_1.default; -function scandir(path, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return async.read(path, getSettings(), optionsOrSettingsOrCallback); - } - async.read(path, getSettings(optionsOrSettingsOrCallback), callback); -} -exports.scandir = scandir; -function scandirSync(path, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - return sync.read(path, settings); -} -exports.scandirSync = scandirSync; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} + + +module.exports = __webpack_require__(620); /***/ }), -/* 632 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(623); -const rpl = __webpack_require__(633); -const constants_1 = __webpack_require__(634); -const utils = __webpack_require__(635); -function read(dir, settings, callback) { - if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { - return readdirWithFileTypes(dir, settings, callback); - } - return readdir(dir, settings, callback); -} -exports.read = read; -function readdirWithFileTypes(dir, settings, callback) { - settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { - if (readdirError) { - return callFailureCallback(callback, readdirError); - } - const entries = dirents.map((dirent) => ({ - dirent, - name: dirent.name, - path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` - })); - if (!settings.followSymbolicLinks) { - return callSuccessCallback(callback, entries); - } - const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); - rpl(tasks, (rplError, rplEntries) => { - if (rplError) { - return callFailureCallback(callback, rplError); - } - callSuccessCallback(callback, rplEntries); - }); - }); -} -exports.readdirWithFileTypes = readdirWithFileTypes; -function makeRplTaskEntry(entry, settings) { - return (done) => { - if (!entry.dirent.isSymbolicLink()) { - return done(null, entry); - } - settings.fs.stat(entry.path, (statError, stats) => { - if (statError) { - if (settings.throwErrorOnBrokenSymbolicLink) { - return done(statError); - } - return done(null, entry); - } - entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); - return done(null, entry); - }); - }; -} -function readdir(dir, settings, callback) { - settings.fs.readdir(dir, (readdirError, names) => { - if (readdirError) { - return callFailureCallback(callback, readdirError); - } - const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); - const tasks = filepaths.map((filepath) => { - return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); - }); - rpl(tasks, (rplError, results) => { - if (rplError) { - return callFailureCallback(callback, rplError); - } - const entries = []; - for (let index = 0; index < names.length; index++) { - const name = names[index]; - const stats = results[index]; - const entry = { - name, - path: filepaths[index], - dirent: utils.fs.createDirentFromStats(name, stats) - }; - if (settings.stats) { - entry.stats = stats; - } - entries.push(entry); - } - callSuccessCallback(callback, entries); - }); - }); -} -exports.readdir = readdir; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, result) { - callback(null, result); -} - -/***/ }), -/* 633 */ -/***/ (function(module, exports) { -module.exports = runParallel +const path = __webpack_require__(16); +const scan = __webpack_require__(621); +const parse = __webpack_require__(624); +const utils = __webpack_require__(622); -function runParallel (tasks, cb) { - var results, pending, keys - var isSync = true +/** + * Creates a matcher function from one or more glob patterns. The + * returned function takes a string to match as its first argument, + * and returns true if the string is a match. The returned matcher + * function also takes a boolean as the second argument that, when true, + * returns an object with additional information. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch(glob[, options]); + * + * const isMatch = picomatch('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @name picomatch + * @param {String|Array} `globs` One or more glob patterns. + * @param {Object=} `options` + * @return {Function=} Returns a matcher function. + * @api public + */ - if (Array.isArray(tasks)) { - results = [] - pending = tasks.length - } else { - keys = Object.keys(tasks) - results = {} - pending = keys.length +const picomatch = (glob, options, returnState = false) => { + if (Array.isArray(glob)) { + let fns = glob.map(input => picomatch(input, options, returnState)); + return str => { + for (let isMatch of fns) { + let state = isMatch(str); + if (state) return state; + } + return false; + }; } - function done (err) { - function end () { - if (cb) cb(err, results) - cb = null - } - if (isSync) process.nextTick(end) - else end() + if (typeof glob !== 'string' || glob === '') { + throw new TypeError('Expected pattern to be a non-empty string'); } - function each (i, err, result) { - results[i] = result - if (--pending === 0 || err) { - done(err) - } - } + let opts = options || {}; + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(glob, options, false, true); + let state = regex.state; + delete regex.state; - if (!pending) { - // empty - done(null) - } else if (keys) { - // object - keys.forEach(function (key) { - tasks[key](function (err, result) { each(key, err, result) }) - }) - } else { - // array - tasks.forEach(function (task, i) { - task(function (err, result) { each(i, err, result) }) - }) + let isIgnored = () => false; + if (opts.ignore) { + let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); } - isSync = false -} + const matcher = (input, returnObject = false) => { + let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); + let result = { glob, state, regex, posix, input, output, match, isMatch }; + if (typeof opts.onResult === 'function') { + opts.onResult(result); + } -/***/ }), -/* 634 */ -/***/ (function(module, exports, __webpack_require__) { + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); -const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); -const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); -/** - * IS `true` for Node.js 10.10 and greater. - */ -exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); + if (isIgnored(input)) { + if (typeof opts.onIgnore === 'function') { + opts.onIgnore(result); + } + result.isMatch = false; + return returnObject ? result : false; + } + if (typeof opts.onMatch === 'function') { + opts.onMatch(result); + } + return returnObject ? result : true; + }; -/***/ }), -/* 635 */ -/***/ (function(module, exports, __webpack_require__) { + if (returnState) { + matcher.state = state; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(636); -exports.fs = fs; + return matcher; +}; +/** + * Test `input` with the given `regex`. This is used by the main + * `picomatch()` function to test the input string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.test(input, regex[, options]); + * + * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); + * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } + * ``` + * @param {String} `input` String to test. + * @param {RegExp} `regex` + * @return {Object} Returns an object with matching info. + * @api public + */ -/***/ }), -/* 636 */ -/***/ (function(module, exports, __webpack_require__) { +picomatch.test = (input, regex, options, { glob, posix } = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected input to be a string'); + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -class DirentFromStats { - constructor(name, stats) { - this.name = name; - this.isBlockDevice = stats.isBlockDevice.bind(stats); - this.isCharacterDevice = stats.isCharacterDevice.bind(stats); - this.isDirectory = stats.isDirectory.bind(stats); - this.isFIFO = stats.isFIFO.bind(stats); - this.isFile = stats.isFile.bind(stats); - this.isSocket = stats.isSocket.bind(stats); - this.isSymbolicLink = stats.isSymbolicLink.bind(stats); - } -} -function createDirentFromStats(name, stats) { - return new DirentFromStats(name, stats); -} -exports.createDirentFromStats = createDirentFromStats; + if (input === '') { + return { isMatch: false, output: '' }; + } + let opts = options || {}; + let format = opts.format || (posix ? utils.toPosixSlashes : null); + let match = input === glob; + let output = (match && format) ? format(input) : input; -/***/ }), -/* 637 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(623); -const constants_1 = __webpack_require__(634); -const utils = __webpack_require__(635); -function read(dir, settings) { - if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { - return readdirWithFileTypes(dir, settings); - } - return readdir(dir, settings); -} -exports.read = read; -function readdirWithFileTypes(dir, settings) { - const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); - return dirents.map((dirent) => { - const entry = { - dirent, - name: dirent.name, - path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` - }; - if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { - try { - const stats = settings.fs.statSync(entry.path); - entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); - } - catch (error) { - if (settings.throwErrorOnBrokenSymbolicLink) { - throw error; - } - } - } - return entry; - }); -} -exports.readdirWithFileTypes = readdirWithFileTypes; -function readdir(dir, settings) { - const names = settings.fs.readdirSync(dir); - return names.map((name) => { - const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; - const stats = fsStat.statSync(entryPath, settings.fsStatSettings); - const entry = { - name, - path: entryPath, - dirent: utils.fs.createDirentFromStats(name, stats) - }; - if (settings.stats) { - entry.stats = stats; - } - return entry; - }); -} -exports.readdir = readdir; + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = picomatch.matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } -/***/ }), -/* 638 */ -/***/ (function(module, exports, __webpack_require__) { + return { isMatch: !!match, match, output }; +}; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsStat = __webpack_require__(623); -const fs = __webpack_require__(639); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); - this.fs = fs.createFileSystemAdapter(this._options.fs); - this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); - this.stats = this._getValue(this._options.stats, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); - this.fsStatSettings = new fsStat.Settings({ - followSymbolicLink: this.followSymbolicLinks, - fs: this.fs, - throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink - }); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; +/** + * Match the basename of a filepath. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.matchBase(input, glob[, options]); + * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true + * ``` + * @param {String} `input` String to test. + * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). + * @return {Boolean} + * @api public + */ +picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { + let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); + return regex.test(path.basename(input)); +}; -/***/ }), -/* 639 */ -/***/ (function(module, exports, __webpack_require__) { +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.isMatch(string, patterns[, options]); + * + * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String|Array} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -exports.FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - stat: fs.stat, - lstatSync: fs.lstatSync, - statSync: fs.statSync, - readdir: fs.readdir, - readdirSync: fs.readdirSync -}; -function createFileSystemAdapter(fsMethods) { - if (!fsMethods) { - return exports.FILE_SYSTEM_ADAPTER; - } - return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); -} -exports.createFileSystemAdapter = createFileSystemAdapter; +picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const picomatch = require('picomatch'); + * const result = picomatch.parse(glob[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as a regex source string. + * @api public + */ -/***/ }), -/* 640 */ -/***/ (function(module, exports, __webpack_require__) { +picomatch.parse = (glob, options) => parse(glob, options); -"use strict"; +/** + * Scan a glob pattern to separate the pattern into segments. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.scan(input[, options]); + * + * const result = picomatch.scan('!./foo/*.js'); + * console.log(result); + * // { prefix: '!./', + * // input: '!./foo/*.js', + * // base: 'foo', + * // glob: '*.js', + * // negated: true, + * // isGlob: true } + * ``` + * @param {String} `input` Glob pattern to scan. + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ +picomatch.scan = (input, options) => scan(input, options); -var reusify = __webpack_require__(641) +/** + * Create a regular expression from a glob pattern. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.makeRe(input[, options]); + * + * console.log(picomatch.makeRe('*.js')); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `input` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ -function fastqueue (context, worker, concurrency) { - if (typeof context === 'function') { - concurrency = worker - worker = context - context = null +picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { + if (!input || typeof input !== 'string') { + throw new TypeError('Expected a non-empty string'); } - var cache = reusify(Task) - var queueHead = null - var queueTail = null - var _running = 0 + let opts = options || {}; + let prepend = opts.contains ? '' : '^'; + let append = opts.contains ? '' : '$'; + let state = { negated: false, fastpaths: true }; + let prefix = ''; + let output; - var self = { - push: push, - drain: noop, - saturated: noop, - pause: pause, - paused: false, - concurrency: concurrency, - running: running, - resume: resume, - idle: idle, - length: length, - unshift: unshift, - empty: noop, - kill: kill, - killAndDrain: killAndDrain + if (input.startsWith('./')) { + input = input.slice(2); + prefix = state.prefix = './'; } - return self - - function running () { - return _running + if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { + output = parse.fastpaths(input, options); } - function pause () { - self.paused = true + if (output === void 0) { + state = picomatch.parse(input, options); + state.prefix = prefix + (state.prefix || ''); + output = state.output; } - function length () { - var current = queueHead - var counter = 0 - - while (current) { - current = current.next - counter++ - } - - return counter + if (returnOutput === true) { + return output; } - function resume () { - if (!self.paused) return - self.paused = false - for (var i = 0; i < self.concurrency; i++) { - _running++ - release() - } + let source = `${prepend}(?:${output})${append}`; + if (state && state.negated === true) { + source = `^(?!${source}).*$`; } - function idle () { - return _running === 0 && self.length() === 0 + let regex = picomatch.toRegex(source, options); + if (returnState === true) { + regex.state = state; } - function push (value, done) { - var current = cache.get() + return regex; +}; - current.context = context - current.release = release - current.value = value - current.callback = done || noop +/** + * Create a regular expression from the given regex source string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.toRegex(source[, options]); + * + * const { output } = picomatch.parse('*.js'); + * console.log(picomatch.toRegex(output)); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `source` Regular expression source string. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ - if (_running === self.concurrency || self.paused) { - if (queueTail) { - queueTail.next = current - queueTail = current - } else { - queueHead = current - queueTail = current - self.saturated() - } - } else { - _running++ - worker.call(context, current.value, current.worked) - } +picomatch.toRegex = (source, options) => { + try { + let opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; } +}; - function unshift (value, done) { - var current = cache.get() +/** + * Picomatch constants. + * @return {Object} + */ - current.context = context - current.release = release - current.value = value - current.callback = done || noop +picomatch.constants = __webpack_require__(623); - if (_running === self.concurrency || self.paused) { - if (queueHead) { - current.next = queueHead - queueHead = current - } else { - queueHead = current - queueTail = current - self.saturated() - } - } else { - _running++ - worker.call(context, current.value, current.worked) - } - } +/** + * Expose "picomatch" + */ - function release (holder) { - if (holder) { - cache.release(holder) - } - var next = queueHead - if (next) { - if (!self.paused) { - if (queueTail === queueHead) { - queueTail = null - } - queueHead = next.next - next.next = null - worker.call(context, next.value, next.worked) - if (queueTail === null) { - self.empty() - } - } else { - _running-- - } - } else if (--_running === 0) { - self.drain() - } - } +module.exports = picomatch; - function kill () { - queueHead = null - queueTail = null - self.drain = noop - } - function killAndDrain () { - queueHead = null - queueTail = null - self.drain() - self.drain = noop - } -} +/***/ }), +/* 621 */ +/***/ (function(module, exports, __webpack_require__) { -function noop () {} +"use strict"; -function Task () { - this.value = null - this.callback = noop - this.next = null - this.release = noop - this.context = null - var self = this +const utils = __webpack_require__(622); - this.worked = function worked (err, result) { - var callback = self.callback - self.value = null - self.callback = noop - callback.call(self.context, err, result) - self.release(self) - } -} +const { + CHAR_ASTERISK, /* * */ + CHAR_AT, /* @ */ + CHAR_BACKWARD_SLASH, /* \ */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_EXCLAMATION_MARK, /* ! */ + CHAR_FORWARD_SLASH, /* / */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_PLUS, /* + */ + CHAR_QUESTION_MARK, /* ? */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_RIGHT_SQUARE_BRACKET /* ] */ +} = __webpack_require__(623); -module.exports = fastqueue +const isPathSeparator = code => { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +}; +/** + * Quickly scans a glob pattern and returns an object with a handful of + * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), + * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). + * + * ```js + * const pm = require('picomatch'); + * console.log(pm.scan('foo/bar/*.js')); + * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {Object} Returns an object with tokens and regex source string. + * @api public + */ -/***/ }), -/* 641 */ -/***/ (function(module, exports, __webpack_require__) { +module.exports = (input, options) => { + let opts = options || {}; + let length = input.length - 1; + let index = -1; + let start = 0; + let lastIndex = 0; + let isGlob = false; + let backslashes = false; + let negated = false; + let braces = 0; + let prev; + let code; -"use strict"; + let braceEscaped = false; + let eos = () => index >= length; + let advance = () => { + prev = code; + return input.charCodeAt(++index); + }; -function reusify (Constructor) { - var head = new Constructor() - var tail = head + while (index < length) { + code = advance(); + let next; - function get () { - var current = head + if (code === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); - if (current.next) { - head = current.next - } else { - head = new Constructor() - tail = head + if (next === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; } - current.next = null + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; - return current - } + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } - function release (obj) { - tail.next = obj - tail = obj - } + if (next === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; + } - return { - get: get, - release: release - } -} + if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { + isGlob = true; + break; + } -module.exports = reusify + if (!braceEscaped && next === CHAR_COMMA) { + isGlob = true; + break; + } + if (next === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + break; + } + } + } + } -/***/ }), -/* 642 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function isFatalError(settings, error) { - if (settings.errorFilter === null) { - return true; - } - return !settings.errorFilter(error); -} -exports.isFatalError = isFatalError; -function isAppliedFilter(filter, value) { - return filter === null || filter(value); -} -exports.isAppliedFilter = isAppliedFilter; -function replacePathSegmentSeparator(filepath, separator) { - return filepath.split(/[\\\/]/).join(separator); -} -exports.replacePathSegmentSeparator = replacePathSegmentSeparator; -function joinPathSegments(a, b, separator) { - if (a === '') { - return b; - } - return a + separator + b; -} -exports.joinPathSegments = joinPathSegments; + if (code === CHAR_FORWARD_SLASH) { + if (prev === CHAR_DOT && index === (start + 1)) { + start += 2; + continue; + } + lastIndex = index + 1; + continue; + } -/***/ }), -/* 643 */ -/***/ (function(module, exports, __webpack_require__) { + if (code === CHAR_ASTERISK) { + isGlob = true; + break; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const common = __webpack_require__(642); -class Reader { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); - } -} -exports.default = Reader; + if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { + isGlob = true; + break; + } + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } -/***/ }), -/* 644 */ -/***/ (function(module, exports, __webpack_require__) { + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isGlob = true; + break; + } + } + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(27); -const async_1 = __webpack_require__(630); -class StreamProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new async_1.default(this._root, this._settings); - this._stream = new stream_1.Readable({ - objectMode: true, - read: () => { }, - destroy: this._reader.destroy.bind(this._reader) - }); - } - read() { - this._reader.onError((error) => { - this._stream.emit('error', error); - }); - this._reader.onEntry((entry) => { - this._stream.push(entry); - }); - this._reader.onEnd(() => { - this._stream.push(null); - }); - this._reader.read(); - return this._stream; - } -} -exports.default = StreamProvider; + let isExtglobChar = code === CHAR_PLUS + || code === CHAR_AT + || code === CHAR_EXCLAMATION_MARK; + if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { + isGlob = true; + break; + } -/***/ }), -/* 645 */ -/***/ (function(module, exports, __webpack_require__) { + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negated = true; + start++; + continue; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(646); -class SyncProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new sync_1.default(this._root, this._settings); - } - read() { - return this._reader.read(); - } -} -exports.default = SyncProvider; + if (code === CHAR_LEFT_PARENTHESES) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } + if (next === CHAR_RIGHT_PARENTHESES) { + isGlob = true; + break; + } + } + } -/***/ }), -/* 646 */ -/***/ (function(module, exports, __webpack_require__) { + if (isGlob) { + break; + } + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsScandir = __webpack_require__(631); -const common = __webpack_require__(642); -const reader_1 = __webpack_require__(643); -class SyncReader extends reader_1.default { - constructor() { - super(...arguments); - this._scandir = fsScandir.scandirSync; - this._storage = new Set(); - this._queue = new Set(); - } - read() { - this._pushToQueue(this._root, this._settings.basePath); - this._handleQueue(); - return Array.from(this._storage); - } - _pushToQueue(dir, base) { - this._queue.add({ dir, base }); - } - _handleQueue() { - for (const item of this._queue.values()) { - this._handleDirectory(item.dir, item.base); - } - } - _handleDirectory(dir, base) { - try { - const entries = this._scandir(dir, this._settings.fsScandirSettings); - for (const entry of entries) { - this._handleEntry(entry, base); - } - } - catch (error) { - this._handleError(error); - } - } - _handleError(error) { - if (!common.isFatalError(this._settings, error)) { - return; - } - throw error; - } - _handleEntry(entry, base) { - const fullpath = entry.path; - if (base !== undefined) { - entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); - } - if (common.isAppliedFilter(this._settings.entryFilter, entry)) { - this._pushToStorage(entry); - } - if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { - this._pushToQueue(fullpath, entry.path); - } - } - _pushToStorage(entry) { - this._storage.add(entry); - } -} -exports.default = SyncReader; + let prefix = ''; + let orig = input; + let base = input; + let glob = ''; + if (start > 0) { + prefix = input.slice(0, start); + input = input.slice(start); + lastIndex -= start; + } -/***/ }), -/* 647 */ -/***/ (function(module, exports, __webpack_require__) { + if (base && isGlob === true && lastIndex > 0) { + base = input.slice(0, lastIndex); + glob = input.slice(lastIndex); + } else if (isGlob === true) { + base = ''; + glob = input; + } else { + base = input; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsScandir = __webpack_require__(631); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.basePath = this._getValue(this._options.basePath, undefined); - this.concurrency = this._getValue(this._options.concurrency, Infinity); - this.deepFilter = this._getValue(this._options.deepFilter, null); - this.entryFilter = this._getValue(this._options.entryFilter, null); - this.errorFilter = this._getValue(this._options.errorFilter, null); - this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); - this.fsScandirSettings = new fsScandir.Settings({ - followSymbolicLinks: this._options.followSymbolicLinks, - fs: this._options.fs, - pathSegmentSeparator: this._options.pathSegmentSeparator, - stats: this._options.stats, - throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink - }); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; + if (base && base !== '' && base !== '/' && base !== input) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); -/***/ }), -/* 648 */ -/***/ (function(module, exports, __webpack_require__) { + if (base && backslashes === true) { + base = utils.removeBackslashes(base); + } + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsStat = __webpack_require__(623); -const utils = __webpack_require__(594); -class Reader { - constructor(_settings) { - this._settings = _settings; - this._fsStatSettings = new fsStat.Settings({ - followSymbolicLink: this._settings.followSymbolicLinks, - fs: this._settings.fs, - throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks - }); - } - _getFullEntryPath(filepath) { - return path.resolve(this._settings.cwd, filepath); - } - _makeEntry(stats, pattern) { - const entry = { - name: pattern, - path: pattern, - dirent: utils.fs.createDirentFromStats(pattern, stats) - }; - if (this._settings.stats) { - entry.stats = stats; - } - return entry; - } - _isFatalError(error) { - return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; - } -} -exports.default = Reader; + return { prefix, input: orig, base, glob, negated, isGlob }; +}; /***/ }), -/* 649 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const deep_1 = __webpack_require__(650); -const entry_1 = __webpack_require__(651); -const error_1 = __webpack_require__(652); -const entry_2 = __webpack_require__(653); -class Provider { - constructor(_settings) { - this._settings = _settings; - this.errorFilter = new error_1.default(this._settings); - this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); - this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); - this.entryTransformer = new entry_2.default(this._settings); - } - _getRootDirectory(task) { - return path.resolve(this._settings.cwd, task.base); - } - _getReaderOptions(task) { - const basePath = task.base === '.' ? '' : task.base; - return { - basePath, - pathSegmentSeparator: '/', - concurrency: this._settings.concurrency, - deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), - entryFilter: this.entryFilter.getFilter(task.positive, task.negative), - errorFilter: this.errorFilter.getFilter(), - followSymbolicLinks: this._settings.followSymbolicLinks, - fs: this._settings.fs, - stats: this._settings.stats, - throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, - transform: this.entryTransformer.getTransformer() - }; - } - _getMicromatchOptions() { - return { - dot: this._settings.dot, - matchBase: this._settings.baseNameMatch, - nobrace: !this._settings.braceExpansion, - nocase: !this._settings.caseSensitiveMatch, - noext: !this._settings.extglob, - noglobstar: !this._settings.globstar, - posix: true, - strictSlashes: false - }; - } -} -exports.default = Provider; -/***/ }), -/* 650 */ -/***/ (function(module, exports, __webpack_require__) { +const path = __webpack_require__(16); +const win32 = process.platform === 'win32'; +const { + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL, + REGEX_REMOVE_BACKSLASH +} = __webpack_require__(623); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(594); -class DeepFilter { - constructor(_settings, _micromatchOptions) { - this._settings = _settings; - this._micromatchOptions = _micromatchOptions; - } - getFilter(basePath, positive, negative) { - const maxPatternDepth = this._getMaxPatternDepth(positive); - const negativeRe = this._getNegativePatternsRe(negative); - return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); - } - _getMaxPatternDepth(patterns) { - const globstar = patterns.some(utils.pattern.hasGlobStar); - return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); - } - _getNegativePatternsRe(patterns) { - const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); - return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); - } - _filter(basePath, entry, negativeRe, maxPatternDepth) { - const depth = this._getEntryDepth(basePath, entry.path); - if (this._isSkippedByDeep(depth)) { - return false; - } - if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { - return false; - } - if (this._isSkippedSymbolicLink(entry)) { - return false; - } - if (this._isSkippedDotDirectory(entry)) { - return false; - } - return this._isSkippedByNegativePatterns(entry, negativeRe); - } - _getEntryDepth(basePath, entryPath) { - const basePathDepth = basePath.split('/').length; - const entryPathDepth = entryPath.split('/').length; - return entryPathDepth - (basePath === '' ? 0 : basePathDepth); - } - _isSkippedByDeep(entryDepth) { - return entryDepth >= this._settings.deep; - } - _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { - return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; - } - _isSkippedSymbolicLink(entry) { - return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); - } - _isSkippedDotDirectory(entry) { - return !this._settings.dot && entry.name.startsWith('.'); - } - _isSkippedByNegativePatterns(entry, negativeRe) { - return !utils.pattern.matchAny(entry.path, negativeRe); - } -} -exports.default = DeepFilter; +exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); +exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); +exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); +exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); +exports.toPosixSlashes = str => str.replace(/\\/g, '/'); +exports.removeBackslashes = str => { + return str.replace(REGEX_REMOVE_BACKSLASH, match => { + return match === '\\' ? '' : match; + }); +} -/***/ }), -/* 651 */ -/***/ (function(module, exports, __webpack_require__) { +exports.supportsLookbehinds = () => { + let segs = process.version.slice(1).split('.'); + if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { + return true; + } + return false; +}; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(594); -class EntryFilter { - constructor(_settings, _micromatchOptions) { - this._settings = _settings; - this._micromatchOptions = _micromatchOptions; - this.index = new Map(); - } - getFilter(positive, negative) { - const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); - const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); - return (entry) => this._filter(entry, positiveRe, negativeRe); - } - _filter(entry, positiveRe, negativeRe) { - if (this._settings.unique) { - if (this._isDuplicateEntry(entry)) { - return false; - } - this._createIndexRecord(entry); - } - if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { - return false; - } - if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { - return false; - } - const filepath = this._settings.baseNameMatch ? entry.name : entry.path; - return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); - } - _isDuplicateEntry(entry) { - return this.index.has(entry.path); - } - _createIndexRecord(entry) { - this.index.set(entry.path, undefined); - } - _onlyFileFilter(entry) { - return this._settings.onlyFiles && !entry.dirent.isFile(); - } - _onlyDirectoryFilter(entry) { - return this._settings.onlyDirectories && !entry.dirent.isDirectory(); - } - _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { - if (!this._settings.absolute) { - return false; - } - const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); - return this._isMatchToPatterns(fullpath, negativeRe); - } - _isMatchToPatterns(filepath, patternsRe) { - return utils.pattern.matchAny(filepath, patternsRe); - } -} -exports.default = EntryFilter; +exports.isWindows = options => { + if (options && typeof options.windows === 'boolean') { + return options.windows; + } + return win32 === true || path.sep === '\\'; +}; + +exports.escapeLast = (input, char, lastIdx) => { + let idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); + return input.slice(0, idx) + '\\' + input.slice(idx); +}; /***/ }), -/* 652 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(594); -class ErrorFilter { - constructor(_settings) { - this._settings = _settings; - } - getFilter() { - return (error) => this._isNonFatalError(error); - } - _isNonFatalError(error) { - return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; - } -} -exports.default = ErrorFilter; + + +const path = __webpack_require__(16); +const WIN_SLASH = '\\\\/'; +const WIN_NO_SLASH = `[^${WIN_SLASH}]`; + +/** + * Posix glob regex + */ + +const DOT_LITERAL = '\\.'; +const PLUS_LITERAL = '\\+'; +const QMARK_LITERAL = '\\?'; +const SLASH_LITERAL = '\\/'; +const ONE_CHAR = '(?=.)'; +const QMARK = '[^/]'; +const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; +const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; +const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; +const NO_DOT = `(?!${DOT_LITERAL})`; +const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; +const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; +const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; +const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; +const STAR = `${QMARK}*?`; + +const POSIX_CHARS = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR +}; + +/** + * Windows glob regex + */ + +const WINDOWS_CHARS = { + ...POSIX_CHARS, + + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)` +}; + +/** + * POSIX Bracket Regex + */ + +const POSIX_REGEX_SOURCE = { + alnum: 'a-zA-Z0-9', + alpha: 'a-zA-Z', + ascii: '\\x00-\\x7F', + blank: ' \\t', + cntrl: '\\x00-\\x1F\\x7F', + digit: '0-9', + graph: '\\x21-\\x7E', + lower: 'a-z', + print: '\\x20-\\x7E ', + punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', + space: ' \\t\\r\\n\\v\\f', + upper: 'A-Z', + word: 'A-Za-z0-9_', + xdigit: 'A-Fa-f0-9' +}; + +module.exports = { + MAX_LENGTH: 1024 * 64, + POSIX_REGEX_SOURCE, + + // regular expressions + REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, + REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, + REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, + REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, + REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, + REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, + + // Replace globs with equivalent patterns to reduce parsing time. + REPLACEMENTS: { + '***': '*', + '**/**': '**', + '**/**/**': '**' + }, + + // Digits + CHAR_0: 48, /* 0 */ + CHAR_9: 57, /* 9 */ + + // Alphabet chars. + CHAR_UPPERCASE_A: 65, /* A */ + CHAR_LOWERCASE_A: 97, /* a */ + CHAR_UPPERCASE_Z: 90, /* Z */ + CHAR_LOWERCASE_Z: 122, /* z */ + + CHAR_LEFT_PARENTHESES: 40, /* ( */ + CHAR_RIGHT_PARENTHESES: 41, /* ) */ + + CHAR_ASTERISK: 42, /* * */ + + // Non-alphabetic chars. + CHAR_AMPERSAND: 38, /* & */ + CHAR_AT: 64, /* @ */ + CHAR_BACKWARD_SLASH: 92, /* \ */ + CHAR_CARRIAGE_RETURN: 13, /* \r */ + CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ + CHAR_COLON: 58, /* : */ + CHAR_COMMA: 44, /* , */ + CHAR_DOT: 46, /* . */ + CHAR_DOUBLE_QUOTE: 34, /* " */ + CHAR_EQUAL: 61, /* = */ + CHAR_EXCLAMATION_MARK: 33, /* ! */ + CHAR_FORM_FEED: 12, /* \f */ + CHAR_FORWARD_SLASH: 47, /* / */ + CHAR_GRAVE_ACCENT: 96, /* ` */ + CHAR_HASH: 35, /* # */ + CHAR_HYPHEN_MINUS: 45, /* - */ + CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ + CHAR_LEFT_CURLY_BRACE: 123, /* { */ + CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ + CHAR_LINE_FEED: 10, /* \n */ + CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ + CHAR_PERCENT: 37, /* % */ + CHAR_PLUS: 43, /* + */ + CHAR_QUESTION_MARK: 63, /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ + CHAR_RIGHT_CURLY_BRACE: 125, /* } */ + CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ + CHAR_SEMICOLON: 59, /* ; */ + CHAR_SINGLE_QUOTE: 39, /* ' */ + CHAR_SPACE: 32, /* */ + CHAR_TAB: 9, /* \t */ + CHAR_UNDERSCORE: 95, /* _ */ + CHAR_VERTICAL_LINE: 124, /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ + + SEP: path.sep, + + /** + * Create EXTGLOB_CHARS + */ + + extglobChars(chars) { + return { + '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, + '?': { type: 'qmark', open: '(?:', close: ')?' }, + '+': { type: 'plus', open: '(?:', close: ')+' }, + '*': { type: 'star', open: '(?:', close: ')*' }, + '@': { type: 'at', open: '(?:', close: ')' } + }; + }, + + /** + * Create GLOB_CHARS + */ + + globChars(win32) { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + } +}; + + +/***/ }), +/* 624 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const utils = __webpack_require__(622); +const constants = __webpack_require__(623); + +/** + * Constants + */ + +const { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHAR, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS +} = constants; + +/** + * Helpers + */ + +const expandRange = (args, options) => { + if (typeof options.expandRange === 'function') { + return options.expandRange(...args, options); + } + + args.sort(); + let value = `[${args.join('-')}]`; + + try { + /* eslint-disable no-new */ + new RegExp(value); + } catch (ex) { + return args.map(v => utils.escapeRegex(v)).join('..'); + } + + return value; +}; + +const negate = state => { + let count = 1; + + while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { + state.advance(); + state.start++; + count++; + } + + if (count % 2 === 0) { + return false; + } + + state.negated = true; + state.start++; + return true; +}; + +/** + * Create the message for a syntax error + */ + +const syntaxError = (type, char) => { + return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; +}; + +/** + * Parse the given input string. + * @param {String} input + * @param {Object} options + * @return {Object} + */ + +const parse = (input, options) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + + input = REPLACEMENTS[input] || input; + + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + let bos = { type: 'bos', value: '', output: opts.prepend || '' }; + let tokens = [bos]; + + let capture = opts.capture ? '' : '?:'; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const PLATFORM_CHARS = constants.globChars(win32); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR + } = PLATFORM_CHARS; + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + let nodot = opts.dot ? '' : NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + // minimatch options support + if (typeof opts.noext === 'boolean') { + opts.noextglob = opts.noext; + } + + let state = { + index: -1, + start: 0, + consumed: '', + output: '', + backtrack: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + tokens + }; + + let extglobs = []; + let stack = []; + let prev = bos; + let value; + + /** + * Tokenizing helpers + */ + + const eos = () => state.index === len - 1; + const peek = state.peek = (n = 1) => input[state.index + n]; + const advance = state.advance = () => input[++state.index]; + const append = token => { + state.output += token.output != null ? token.output : token.value; + state.consumed += token.value || ''; + }; + + const increment = type => { + state[type]++; + stack.push(type); + }; + + const decrement = type => { + state[type]--; + stack.pop(); + }; + + /** + * Push tokens onto the tokens array. This helper speeds up + * tokenizing by 1) helping us avoid backtracking as much as possible, + * and 2) helping us avoid creating extra tokens when consecutive + * characters are plain text. This improves performance and simplifies + * lookbehinds. + */ + + const push = tok => { + if (prev.type === 'globstar') { + let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); + let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); + if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = 'star'; + prev.value = '*'; + prev.output = star; + state.output += prev.output; + } + } + + if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { + extglobs[extglobs.length - 1].inner += tok.value; + } + + if (tok.value || tok.output) append(tok); + if (prev && prev.type === 'text' && tok.type === 'text') { + prev.value += tok.value; + return; + } + + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; + + const extglobOpen = (type, value) => { + let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; + + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + let output = (opts.capture ? '(' : '') + token.open; + + push({ type, value, output: state.output ? '' : ONE_CHAR }); + push({ type: 'paren', extglob: true, value: advance(), output }); + increment('parens'); + extglobs.push(token); + }; + + const extglobClose = token => { + let output = token.close + (opts.capture ? ')' : ''); + + if (token.type === 'negate') { + let extglobStar = star; + + if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { + extglobStar = globstar(opts); + } + + if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { + output = token.close = ')$))' + extglobStar; + } + + if (token.prev.type === 'bos' && eos()) { + state.negatedExtglob = true; + } + } + + push({ type: 'paren', extglob: true, value, output }); + decrement('parens'); + }; + + if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { + let backslashes = false; + + let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { + if (first === '\\') { + backslashes = true; + return m; + } + + if (first === '?') { + if (esc) { + return esc + first + (rest ? QMARK.repeat(rest.length) : ''); + } + if (index === 0) { + return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); + } + return QMARK.repeat(chars.length); + } + + if (first === '.') { + return DOT_LITERAL.repeat(chars.length); + } + + if (first === '*') { + if (esc) { + return esc + first + (rest ? star : ''); + } + return star; + } + return esc ? m : '\\' + m; + }); + + if (backslashes === true) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ''); + } else { + output = output.replace(/\\+/g, m => { + return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); + }); + } + } + + state.output = output; + return state; + } + + /** + * Tokenize input until we reach end-of-string + */ + + while (!eos()) { + value = advance(); + + if (value === '\u0000') { + continue; + } + + /** + * Escaped characters + */ + + if (value === '\\') { + let next = peek(); + + if (next === '/' && opts.bash !== true) { + continue; + } + + if (next === '.' || next === ';') { + continue; + } + + if (!next) { + value += '\\'; + push({ type: 'text', value }); + continue; + } + + // collapse slashes to reduce potential for exploits + let match = /^\\+/.exec(input.slice(state.index + 1)); + let slashes = 0; + + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) { + value += '\\'; + } + } + + if (opts.unescape === true) { + value = advance() || ''; + } else { + value += advance() || ''; + } + + if (state.brackets === 0) { + push({ type: 'text', value }); + continue; + } + } + + /** + * If we're inside a regex character class, continue + * until we reach the closing bracket. + */ + + if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { + if (opts.posix !== false && value === ':') { + let inner = prev.value.slice(1); + if (inner.includes('[')) { + prev.posix = true; + + if (inner.includes(':')) { + let idx = prev.value.lastIndexOf('['); + let pre = prev.value.slice(0, idx); + let rest = prev.value.slice(idx + 2); + let posix = POSIX_REGEX_SOURCE[rest]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); + + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + + if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { + value = '\\' + value; + } + + if (value === ']' && (prev.value === '[' || prev.value === '[^')) { + value = '\\' + value; + } + + if (opts.posix === true && value === '!' && prev.value === '[') { + value = '^'; + } + + prev.value += value; + append({ value }); + continue; + } + + /** + * If we're inside a quoted string, continue + * until we reach the closing double quote. + */ + + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; + } + + /** + * Double quotes + */ + + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: 'text', value }); + } + continue; + } + + /** + * Parentheses + */ + + if (value === '(') { + push({ type: 'paren', value }); + increment('parens'); + continue; + } + + if (value === ')') { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '(')); + } + + let extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()); + continue; + } + + push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); + decrement('parens'); + continue; + } + + /** + * Brackets + */ + + if (value === '[') { + if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('closing', ']')); + } + + value = '\\' + value; + } else { + increment('brackets'); + } + + push({ type: 'bracket', value }); + continue; + } + + if (value === ']') { + if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '[')); + } + + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + decrement('brackets'); + + let prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { + value = '/' + value; + } + + prev.value += value; + append({ value }); + + // when literal brackets are explicitly disabled + // assume we should match with a regex character class + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + + let escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); + + // when literal brackets are explicitly enabled + // assume we should escape the brackets to match literal characters + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + + // when the user specifies nothing, try to match both + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } + + /** + * Braces + */ + + if (value === '{' && opts.nobrace !== true) { + push({ type: 'brace', value, output: '(' }); + increment('braces'); + continue; + } + + if (value === '}') { + if (opts.nobrace === true || state.braces === 0) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } + + let output = ')'; + + if (state.dots === true) { + let arr = tokens.slice(); + let range = []; + + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === 'brace') { + break; + } + if (arr[i].type !== 'dots') { + range.unshift(arr[i].value); + } + } + + output = expandRange(range, opts); + state.backtrack = true; + } + + push({ type: 'brace', value, output }); + decrement('braces'); + continue; + } + + /** + * Pipes + */ + + if (value === '|') { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: 'text', value }); + continue; + } + + /** + * Commas + */ + + if (value === ',') { + let output = value; + + if (state.braces > 0 && stack[stack.length - 1] === 'braces') { + output = '|'; + } + + push({ type: 'comma', value, output }); + continue; + } + + /** + * Slashes + */ + + if (value === '/') { + // if the beginning of the glob is "./", advance the start + // to the current index, and don't add the "./" characters + // to the state. This greatly simplifies lookbehinds when + // checking for BOS characters like "!" and "." (not "./") + if (prev.type === 'dot' && state.index === 1) { + state.start = state.index + 1; + state.consumed = ''; + state.output = ''; + tokens.pop(); + prev = bos; // reset "prev" to the first token + continue; + } + + push({ type: 'slash', value, output: SLASH_LITERAL }); + continue; + } + + /** + * Dots + */ + + if (value === '.') { + if (state.braces > 0 && prev.type === 'dot') { + if (prev.value === '.') prev.output = DOT_LITERAL; + prev.type = 'dots'; + prev.output += value; + prev.value += value; + state.dots = true; + continue; + } + + push({ type: 'dot', value, output: DOT_LITERAL }); + continue; + } + + /** + * Question marks + */ + + if (value === '?') { + if (prev && prev.type === 'paren') { + let next = peek(); + let output = value; + + if (next === '<' && !utils.supportsLookbehinds()) { + throw new Error('Node.js v10 or higher is required for regex lookbehinds'); + } + + if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { + output = '\\' + value; + } + + push({ type: 'text', value, output }); + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('qmark', value); + continue; + } + + if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { + push({ type: 'qmark', value, output: QMARK_NO_DOT }); + continue; + } + + push({ type: 'qmark', value, output: QMARK }); + continue; + } + + /** + * Exclamation + */ + + if (value === '!') { + if (opts.noextglob !== true && peek() === '(') { + if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { + extglobOpen('negate', value); + continue; + } + } + + if (opts.nonegate !== true && state.index === 0) { + negate(state); + continue; + } + } + + /** + * Plus + */ + + if (value === '+') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('plus', value); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { + let output = prev.extglob === true ? '\\' + value : value; + push({ type: 'plus', value, output }); + continue; + } + + // use regex behavior inside parens + if (state.parens > 0 && opts.regex !== false) { + push({ type: 'plus', value }); + continue; + } + + push({ type: 'plus', value: PLUS_LITERAL }); + continue; + } + + /** + * Plain text + */ + + if (value === '@') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + push({ type: 'at', value, output: '' }); + continue; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Plain text + */ + + if (value !== '*') { + if (value === '$' || value === '^') { + value = '\\' + value; + } + + let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); + if (match) { + value += match[0]; + state.index += match[0].length; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Stars + */ + + if (prev && (prev.type === 'globstar' || prev.star === true)) { + prev.type = 'star'; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.consumed += value; + continue; + } + + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('star', value); + continue; + } + + if (prev.type === 'star') { + if (opts.noglobstar === true) { + state.consumed += value; + continue; + } + + let prior = prev.prev; + let before = prior.prev; + let isStart = prior.type === 'slash' || prior.type === 'bos'; + let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + + if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { + push({ type: 'star', value, output: '' }); + continue; + } + + let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); + let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); + if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { + push({ type: 'star', value, output: '' }); + continue; + } + + // strip consecutive `/**/` + while (input.slice(state.index + 1, state.index + 4) === '/**') { + let after = input[state.index + 4]; + if (after && after !== '/') { + break; + } + state.consumed += '/**'; + state.index += 3; + } + + if (prior.type === 'bos' && eos()) { + prev.type = 'globstar'; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.consumed += value; + continue; + } + + if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = globstar(opts) + '|$)'; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value; + continue; + } + + let next = peek(); + if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { + let end = peek(2) !== void 0 ? '|$' : ''; + + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + + prev.type = 'globstar'; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; + + state.output += prior.output + prev.output; + state.consumed += value + advance(); + + push({ type: 'slash', value, output: '' }); + continue; + } + + if (prior.type === 'bos' && next === '/') { + prev.type = 'globstar'; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.consumed += value + advance(); + push({ type: 'slash', value, output: '' }); + continue; + } + + // remove single star from output + state.output = state.output.slice(0, -prev.output.length); + + // reset previous token to globstar + prev.type = 'globstar'; + prev.output = globstar(opts); + prev.value += value; + + // reset output with globstar + state.output += prev.output; + state.consumed += value; + continue; + } + + let token = { type: 'star', value, output: star }; + + if (opts.bash === true) { + token.output = '.*?'; + if (prev.type === 'bos' || prev.type === 'slash') { + token.output = nodot + token.output; + } + push(token); + continue; + } + + if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { + token.output = value; + push(token); + continue; + } + + if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { + if (prev.type === 'dot') { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; + + } else { + state.output += nodot; + prev.output += nodot; + } + + if (peek() !== '*') { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } + + push(token); + } + + while (state.brackets > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); + state.output = utils.escapeLast(state.output, '['); + decrement('brackets'); + } + + while (state.parens > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); + state.output = utils.escapeLast(state.output, '('); + decrement('parens'); + } + + while (state.braces > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); + state.output = utils.escapeLast(state.output, '{'); + decrement('braces'); + } + + if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { + push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); + } + + // rebuild the output if we had to backtrack at any point + if (state.backtrack === true) { + state.output = ''; + + for (let token of state.tokens) { + state.output += token.output != null ? token.output : token.value; + + if (token.suffix) { + state.output += token.suffix; + } + } + } + + return state; +}; + +/** + * Fast paths for creating regular expressions for common glob patterns. + * This can significantly speed up processing and has very little downside + * impact when none of the fast paths match. + */ + +parse.fastpaths = (input, options) => { + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } + + input = REPLACEMENTS[input] || input; + let win32 = utils.isWindows(options); + + // create constants based on platform, for windows or posix + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR + } = constants.globChars(win32); + + let capture = opts.capture ? '' : '?:'; + let star = opts.bash === true ? '.*?' : STAR; + let nodot = opts.dot ? NO_DOTS : NO_DOT; + let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + + if (opts.capture) { + star = `(${star})`; + } + + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; + + const create = str => { + switch (str) { + case '*': + return `${nodot}${ONE_CHAR}${star}`; + + case '.*': + return `${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*.*': + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '*/*': + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + + case '**': + return nodot + globstar(opts); + + case '**/*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + + case '**/*.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + + case '**/.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; + + default: { + let match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; + + let source = create(match[1], options); + if (!source) return; + + return source + DOT_LITERAL + match[2]; + } + } + }; + + let output = create(input); + if (output && opts.strictSlashes !== true) { + output += `${SLASH_LITERAL}?`; + } + + return output; +}; + +module.exports = parse; + + +/***/ }), +/* 625 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const merge2 = __webpack_require__(591); +function merge(streams) { + const mergedStream = merge2(streams); + streams.forEach((stream) => { + stream.once('error', (err) => mergedStream.emit('error', err)); + }); + return mergedStream; +} +exports.merge = merge; + + +/***/ }), +/* 626 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(627); +const provider_1 = __webpack_require__(654); +class ProviderAsync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = []; + return new Promise((resolve, reject) => { + const stream = this.api(root, task, options); + stream.once('error', reject); + stream.on('data', (entry) => entries.push(options.transform(entry))); + stream.once('end', () => resolve(entries)); + }); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderAsync; + + +/***/ }), +/* 627 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const fsStat = __webpack_require__(628); +const fsWalk = __webpack_require__(633); +const reader_1 = __webpack_require__(653); +class ReaderStream extends reader_1.default { + constructor() { + super(...arguments); + this._walkStream = fsWalk.walkStream; + this._stat = fsStat.stat; + } + dynamic(root, options) { + return this._walkStream(root, options); + } + static(patterns, options) { + const filepaths = patterns.map(this._getFullEntryPath, this); + const stream = new stream_1.PassThrough({ objectMode: true }); + stream._write = (index, _enc, done) => { + return this._getEntry(filepaths[index], patterns[index], options) + .then((entry) => { + if (entry !== null && options.entryFilter(entry)) { + stream.push(entry); + } + if (index === filepaths.length - 1) { + stream.end(); + } + done(); + }) + .catch(done); + }; + for (let i = 0; i < filepaths.length; i++) { + stream.write(i); + } + return stream; + } + _getEntry(filepath, pattern, options) { + return this._getStat(filepath) + .then((stats) => this._makeEntry(stats, pattern)) + .catch((error) => { + if (options.errorFilter(error)) { + return null; + } + throw error; + }); + } + _getStat(filepath) { + return new Promise((resolve, reject) => { + this._stat(filepath, this._fsStatSettings, (error, stats) => { + error ? reject(error) : resolve(stats); + }); + }); + } +} +exports.default = ReaderStream; + + +/***/ }), +/* 628 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(629); +const sync = __webpack_require__(630); +const settings_1 = __webpack_require__(631); +exports.Settings = settings_1.default; +function stat(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.stat = stat; +function statSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.statSync = statSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 629 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings, callback) { + settings.fs.lstat(path, (lstatError, lstat) => { + if (lstatError) { + return callFailureCallback(callback, lstatError); + } + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return callSuccessCallback(callback, lstat); + } + settings.fs.stat(path, (statError, stat) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return callFailureCallback(callback, statError); + } + return callSuccessCallback(callback, lstat); + } + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + callSuccessCallback(callback, stat); + }); + }); +} +exports.read = read; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 630 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings) { + const lstat = settings.fs.lstatSync(path); + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return lstat; + } + try { + const stat = settings.fs.statSync(path); + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + return stat; + } + catch (error) { + if (!settings.throwErrorOnBrokenSymbolicLink) { + return lstat; + } + throw error; + } +} +exports.read = read; + + +/***/ }), +/* 631 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(632); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 632 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 633 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(634); +const stream_1 = __webpack_require__(649); +const sync_1 = __webpack_require__(650); +const settings_1 = __webpack_require__(652); +exports.Settings = settings_1.default; +function walk(dir, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); + } + new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); +} +exports.walk = walk; +function walkSync(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new sync_1.default(dir, settings); + return provider.read(); +} +exports.walkSync = walkSync; +function walkStream(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new stream_1.default(dir, settings); + return provider.read(); +} +exports.walkStream = walkStream; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 634 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(635); +class AsyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._storage = new Set(); + } + read(callback) { + this._reader.onError((error) => { + callFailureCallback(callback, error); + }); + this._reader.onEntry((entry) => { + this._storage.add(entry); + }); + this._reader.onEnd(() => { + callSuccessCallback(callback, Array.from(this._storage)); + }); + this._reader.read(); + } +} +exports.default = AsyncProvider; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, entries) { + callback(null, entries); +} + + +/***/ }), +/* 635 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __webpack_require__(379); +const fsScandir = __webpack_require__(636); +const fastq = __webpack_require__(645); +const common = __webpack_require__(647); +const reader_1 = __webpack_require__(648); +class AsyncReader extends reader_1.default { + constructor(_root, _settings) { + super(_root, _settings); + this._settings = _settings; + this._scandir = fsScandir.scandir; + this._emitter = new events_1.EventEmitter(); + this._queue = fastq(this._worker.bind(this), this._settings.concurrency); + this._isFatalError = false; + this._isDestroyed = false; + this._queue.drain = () => { + if (!this._isFatalError) { + this._emitter.emit('end'); + } + }; + } + read() { + this._isFatalError = false; + this._isDestroyed = false; + setImmediate(() => { + this._pushToQueue(this._root, this._settings.basePath); + }); + return this._emitter; + } + destroy() { + if (this._isDestroyed) { + throw new Error('The reader is already destroyed'); + } + this._isDestroyed = true; + this._queue.killAndDrain(); + } + onEntry(callback) { + this._emitter.on('entry', callback); + } + onError(callback) { + this._emitter.once('error', callback); + } + onEnd(callback) { + this._emitter.once('end', callback); + } + _pushToQueue(dir, base) { + const queueItem = { dir, base }; + this._queue.push(queueItem, (error) => { + if (error) { + this._handleError(error); + } + }); + } + _worker(item, done) { + this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { + if (error) { + return done(error, undefined); + } + for (const entry of entries) { + this._handleEntry(entry, item.base); + } + done(null, undefined); + }); + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + this._isFatalError = true; + this._isDestroyed = true; + this._emitter.emit('error', error); + } + _handleEntry(entry, base) { + if (this._isDestroyed || this._isFatalError) { + return; + } + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._emitEntry(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _emitEntry(entry) { + this._emitter.emit('entry', entry); + } +} +exports.default = AsyncReader; + + +/***/ }), +/* 636 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(637); +const sync = __webpack_require__(642); +const settings_1 = __webpack_require__(643); +exports.Settings = settings_1.default; +function scandir(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.scandir = scandir; +function scandirSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.scandirSync = scandirSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} + + +/***/ }), +/* 637 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(628); +const rpl = __webpack_require__(638); +const constants_1 = __webpack_require__(639); +const utils = __webpack_require__(640); +function read(dir, settings, callback) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings, callback); + } + return readdir(dir, settings, callback); +} +exports.read = read; +function readdirWithFileTypes(dir, settings, callback) { + settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const entries = dirents.map((dirent) => ({ + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + })); + if (!settings.followSymbolicLinks) { + return callSuccessCallback(callback, entries); + } + const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); + rpl(tasks, (rplError, rplEntries) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + callSuccessCallback(callback, rplEntries); + }); + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function makeRplTaskEntry(entry, settings) { + return (done) => { + if (!entry.dirent.isSymbolicLink()) { + return done(null, entry); + } + settings.fs.stat(entry.path, (statError, stats) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return done(statError); + } + return done(null, entry); + } + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + return done(null, entry); + }); + }; +} +function readdir(dir, settings, callback) { + settings.fs.readdir(dir, (readdirError, names) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); + const tasks = filepaths.map((filepath) => { + return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); + }); + rpl(tasks, (rplError, results) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + const entries = []; + for (let index = 0; index < names.length; index++) { + const name = names[index]; + const stats = results[index]; + const entry = { + name, + path: filepaths[index], + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + entries.push(entry); + } + callSuccessCallback(callback, entries); + }); + }); +} +exports.readdir = readdir; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} + + +/***/ }), +/* 638 */ +/***/ (function(module, exports) { + +module.exports = runParallel + +function runParallel (tasks, cb) { + var results, pending, keys + var isSync = true + + if (Array.isArray(tasks)) { + results = [] + pending = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = keys.length + } + + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } + + function each (i, err, result) { + results[i] = result + if (--pending === 0 || err) { + done(err) + } + } + + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.forEach(function (key) { + tasks[key](function (err, result) { each(key, err, result) }) + }) + } else { + // array + tasks.forEach(function (task, i) { + task(function (err, result) { each(i, err, result) }) + }) + } + + isSync = false +} + + +/***/ }), +/* 639 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); +const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); +const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); +/** + * IS `true` for Node.js 10.10 and greater. + */ +exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); + + +/***/ }), +/* 640 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(641); +exports.fs = fs; + + +/***/ }), +/* 641 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; + + +/***/ }), +/* 642 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(628); +const constants_1 = __webpack_require__(639); +const utils = __webpack_require__(640); +function read(dir, settings) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings); + } + return readdir(dir, settings); +} +exports.read = read; +function readdirWithFileTypes(dir, settings) { + const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); + return dirents.map((dirent) => { + const entry = { + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + }; + if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { + try { + const stats = settings.fs.statSync(entry.path); + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + } + catch (error) { + if (settings.throwErrorOnBrokenSymbolicLink) { + throw error; + } + } + } + return entry; + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function readdir(dir, settings) { + const names = settings.fs.readdirSync(dir); + return names.map((name) => { + const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; + const stats = fsStat.statSync(entryPath, settings.fsStatSettings); + const entry = { + name, + path: entryPath, + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + return entry; + }); +} +exports.readdir = readdir; + + +/***/ }), +/* 643 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(628); +const fs = __webpack_require__(644); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.stats = this._getValue(this._options.stats, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + this.fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this.followSymbolicLinks, + fs: this.fs, + throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 644 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; + + +/***/ }), +/* 645 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var reusify = __webpack_require__(646) + +function fastqueue (context, worker, concurrency) { + if (typeof context === 'function') { + concurrency = worker + worker = context + context = null + } + + var cache = reusify(Task) + var queueHead = null + var queueTail = null + var _running = 0 + + var self = { + push: push, + drain: noop, + saturated: noop, + pause: pause, + paused: false, + concurrency: concurrency, + running: running, + resume: resume, + idle: idle, + length: length, + unshift: unshift, + empty: noop, + kill: kill, + killAndDrain: killAndDrain + } + + return self + + function running () { + return _running + } + + function pause () { + self.paused = true + } + + function length () { + var current = queueHead + var counter = 0 + + while (current) { + current = current.next + counter++ + } + + return counter + } + + function resume () { + if (!self.paused) return + self.paused = false + for (var i = 0; i < self.concurrency; i++) { + _running++ + release() + } + } + + function idle () { + return _running === 0 && self.length() === 0 + } + + function push (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueTail) { + queueTail.next = current + queueTail = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function unshift (value, done) { + var current = cache.get() + + current.context = context + current.release = release + current.value = value + current.callback = done || noop + + if (_running === self.concurrency || self.paused) { + if (queueHead) { + current.next = queueHead + queueHead = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + + function release (holder) { + if (holder) { + cache.release(holder) + } + var next = queueHead + if (next) { + if (!self.paused) { + if (queueTail === queueHead) { + queueTail = null + } + queueHead = next.next + next.next = null + worker.call(context, next.value, next.worked) + if (queueTail === null) { + self.empty() + } + } else { + _running-- + } + } else if (--_running === 0) { + self.drain() + } + } + + function kill () { + queueHead = null + queueTail = null + self.drain = noop + } + + function killAndDrain () { + queueHead = null + queueTail = null + self.drain() + self.drain = noop + } +} + +function noop () {} + +function Task () { + this.value = null + this.callback = noop + this.next = null + this.release = noop + this.context = null + + var self = this + + this.worked = function worked (err, result) { + var callback = self.callback + self.value = null + self.callback = noop + callback.call(self.context, err, result) + self.release(self) + } +} + +module.exports = fastqueue + + +/***/ }), +/* 646 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +function reusify (Constructor) { + var head = new Constructor() + var tail = head + + function get () { + var current = head + + if (current.next) { + head = current.next + } else { + head = new Constructor() + tail = head + } + + current.next = null + + return current + } + + function release (obj) { + tail.next = obj + tail = obj + } + + return { + get: get, + release: release + } +} + +module.exports = reusify + + +/***/ }), +/* 647 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isFatalError(settings, error) { + if (settings.errorFilter === null) { + return true; + } + return !settings.errorFilter(error); +} +exports.isFatalError = isFatalError; +function isAppliedFilter(filter, value) { + return filter === null || filter(value); +} +exports.isAppliedFilter = isAppliedFilter; +function replacePathSegmentSeparator(filepath, separator) { + return filepath.split(/[\\\/]/).join(separator); +} +exports.replacePathSegmentSeparator = replacePathSegmentSeparator; +function joinPathSegments(a, b, separator) { + if (a === '') { + return b; + } + return a + separator + b; +} +exports.joinPathSegments = joinPathSegments; + + +/***/ }), +/* 648 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const common = __webpack_require__(647); +class Reader { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); + } +} +exports.default = Reader; + + +/***/ }), +/* 649 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const async_1 = __webpack_require__(635); +class StreamProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._stream = new stream_1.Readable({ + objectMode: true, + read: () => { }, + destroy: this._reader.destroy.bind(this._reader) + }); + } + read() { + this._reader.onError((error) => { + this._stream.emit('error', error); + }); + this._reader.onEntry((entry) => { + this._stream.push(entry); + }); + this._reader.onEnd(() => { + this._stream.push(null); + }); + this._reader.read(); + return this._stream; + } +} +exports.default = StreamProvider; + + +/***/ }), +/* 650 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(651); +class SyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new sync_1.default(this._root, this._settings); + } + read() { + return this._reader.read(); + } +} +exports.default = SyncProvider; + + +/***/ }), +/* 651 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsScandir = __webpack_require__(636); +const common = __webpack_require__(647); +const reader_1 = __webpack_require__(648); +class SyncReader extends reader_1.default { + constructor() { + super(...arguments); + this._scandir = fsScandir.scandirSync; + this._storage = new Set(); + this._queue = new Set(); + } + read() { + this._pushToQueue(this._root, this._settings.basePath); + this._handleQueue(); + return Array.from(this._storage); + } + _pushToQueue(dir, base) { + this._queue.add({ dir, base }); + } + _handleQueue() { + for (const item of this._queue.values()) { + this._handleDirectory(item.dir, item.base); + } + } + _handleDirectory(dir, base) { + try { + const entries = this._scandir(dir, this._settings.fsScandirSettings); + for (const entry of entries) { + this._handleEntry(entry, base); + } + } + catch (error) { + this._handleError(error); + } + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + throw error; + } + _handleEntry(entry, base) { + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._pushToStorage(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _pushToStorage(entry) { + this._storage.add(entry); + } +} +exports.default = SyncReader; + + +/***/ }), +/* 652 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsScandir = __webpack_require__(636); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.basePath = this._getValue(this._options.basePath, undefined); + this.concurrency = this._getValue(this._options.concurrency, Infinity); + this.deepFilter = this._getValue(this._options.deepFilter, null); + this.entryFilter = this._getValue(this._options.entryFilter, null); + this.errorFilter = this._getValue(this._options.errorFilter, null); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.fsScandirSettings = new fsScandir.Settings({ + followSymbolicLinks: this._options.followSymbolicLinks, + fs: this._options.fs, + pathSegmentSeparator: this._options.pathSegmentSeparator, + stats: this._options.stats, + throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; + + +/***/ }), +/* 653 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(628); +const utils = __webpack_require__(599); +class Reader { + constructor(_settings) { + this._settings = _settings; + this._fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this._settings.followSymbolicLinks, + fs: this._settings.fs, + throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks + }); + } + _getFullEntryPath(filepath) { + return path.resolve(this._settings.cwd, filepath); + } + _makeEntry(stats, pattern) { + const entry = { + name: pattern, + path: pattern, + dirent: utils.fs.createDirentFromStats(pattern, stats) + }; + if (this._settings.stats) { + entry.stats = stats; + } + return entry; + } + _isFatalError(error) { + return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; + } +} +exports.default = Reader; /***/ }), -/* 653 */ +/* 654 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const deep_1 = __webpack_require__(655); +const entry_1 = __webpack_require__(656); +const error_1 = __webpack_require__(657); +const entry_2 = __webpack_require__(658); +class Provider { + constructor(_settings) { + this._settings = _settings; + this.errorFilter = new error_1.default(this._settings); + this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); + this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); + this.entryTransformer = new entry_2.default(this._settings); + } + _getRootDirectory(task) { + return path.resolve(this._settings.cwd, task.base); + } + _getReaderOptions(task) { + const basePath = task.base === '.' ? '' : task.base; + return { + basePath, + pathSegmentSeparator: '/', + concurrency: this._settings.concurrency, + deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), + entryFilter: this.entryFilter.getFilter(task.positive, task.negative), + errorFilter: this.errorFilter.getFilter(), + followSymbolicLinks: this._settings.followSymbolicLinks, + fs: this._settings.fs, + stats: this._settings.stats, + throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, + transform: this.entryTransformer.getTransformer() + }; + } + _getMicromatchOptions() { + return { + dot: this._settings.dot, + matchBase: this._settings.baseNameMatch, + nobrace: !this._settings.braceExpansion, + nocase: !this._settings.caseSensitiveMatch, + noext: !this._settings.extglob, + noglobstar: !this._settings.globstar, + posix: true, + strictSlashes: false + }; + } +} +exports.default = Provider; + + +/***/ }), +/* 655 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(599); +class DeepFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + } + getFilter(basePath, positive, negative) { + const maxPatternDepth = this._getMaxPatternDepth(positive); + const negativeRe = this._getNegativePatternsRe(negative); + return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); + } + _getMaxPatternDepth(patterns) { + const globstar = patterns.some(utils.pattern.hasGlobStar); + return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); + } + _getNegativePatternsRe(patterns) { + const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); + return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); + } + _filter(basePath, entry, negativeRe, maxPatternDepth) { + const depth = this._getEntryDepth(basePath, entry.path); + if (this._isSkippedByDeep(depth)) { + return false; + } + if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { + return false; + } + if (this._isSkippedSymbolicLink(entry)) { + return false; + } + if (this._isSkippedDotDirectory(entry)) { + return false; + } + return this._isSkippedByNegativePatterns(entry, negativeRe); + } + _getEntryDepth(basePath, entryPath) { + const basePathDepth = basePath.split('/').length; + const entryPathDepth = entryPath.split('/').length; + return entryPathDepth - (basePath === '' ? 0 : basePathDepth); + } + _isSkippedByDeep(entryDepth) { + return entryDepth >= this._settings.deep; + } + _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { + return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; + } + _isSkippedSymbolicLink(entry) { + return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); + } + _isSkippedDotDirectory(entry) { + return !this._settings.dot && entry.name.startsWith('.'); + } + _isSkippedByNegativePatterns(entry, negativeRe) { + return !utils.pattern.matchAny(entry.path, negativeRe); + } +} +exports.default = DeepFilter; + + +/***/ }), +/* 656 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(599); +class EntryFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + this.index = new Map(); + } + getFilter(positive, negative) { + const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); + const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); + return (entry) => this._filter(entry, positiveRe, negativeRe); + } + _filter(entry, positiveRe, negativeRe) { + if (this._settings.unique) { + if (this._isDuplicateEntry(entry)) { + return false; + } + this._createIndexRecord(entry); + } + if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { + return false; + } + if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { + return false; + } + const filepath = this._settings.baseNameMatch ? entry.name : entry.path; + return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); + } + _isDuplicateEntry(entry) { + return this.index.has(entry.path); + } + _createIndexRecord(entry) { + this.index.set(entry.path, undefined); + } + _onlyFileFilter(entry) { + return this._settings.onlyFiles && !entry.dirent.isFile(); + } + _onlyDirectoryFilter(entry) { + return this._settings.onlyDirectories && !entry.dirent.isDirectory(); + } + _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { + if (!this._settings.absolute) { + return false; + } + const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); + return this._isMatchToPatterns(fullpath, negativeRe); + } + _isMatchToPatterns(filepath, patternsRe) { + return utils.pattern.matchAny(filepath, patternsRe); + } +} +exports.default = EntryFilter; + + +/***/ }), +/* 657 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(599); +class ErrorFilter { + constructor(_settings) { + this._settings = _settings; + } + getFilter() { + return (error) => this._isNonFatalError(error); + } + _isNonFatalError(error) { + return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; + } +} +exports.default = ErrorFilter; + + +/***/ }), +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(594); +const utils = __webpack_require__(599); class EntryTransformer { constructor(_settings) { this._settings = _settings; @@ -75236,2515 +76824,3068 @@ exports.default = EntryTransformer; /***/ }), -/* 654 */ +/* 659 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const stream_2 = __webpack_require__(627); +const provider_1 = __webpack_require__(654); +class ProviderStream extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_2.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const source = this.api(root, task, options); + const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); + source + .once('error', (error) => dest.emit('error', error)) + .on('data', (entry) => dest.emit('data', options.transform(entry))) + .once('end', () => dest.emit('end')); + return dest; + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderStream; + + +/***/ }), +/* 660 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(661); +const provider_1 = __webpack_require__(654); +class ProviderSync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new sync_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = this.api(root, task, options); + return entries.map(options.transform); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderSync; + + +/***/ }), +/* 661 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(628); +const fsWalk = __webpack_require__(633); +const reader_1 = __webpack_require__(653); +class ReaderSync extends reader_1.default { + constructor() { + super(...arguments); + this._walkSync = fsWalk.walkSync; + this._statSync = fsStat.statSync; + } + dynamic(root, options) { + return this._walkSync(root, options); + } + static(patterns, options) { + const entries = []; + for (const pattern of patterns) { + const filepath = this._getFullEntryPath(pattern); + const entry = this._getEntry(filepath, pattern, options); + if (entry === null || !options.entryFilter(entry)) { + continue; + } + entries.push(entry); + } + return entries; + } + _getEntry(filepath, pattern, options) { + try { + const stats = this._getStat(filepath); + return this._makeEntry(stats, pattern); + } + catch (error) { + if (options.errorFilter(error)) { + return null; + } + throw error; + } + } + _getStat(filepath) { + return this._statSync(filepath, this._fsStatSettings); + } +} +exports.default = ReaderSync; + + +/***/ }), +/* 662 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +const os = __webpack_require__(11); +const CPU_COUNT = os.cpus().length; +exports.DEFAULT_FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + lstatSync: fs.lstatSync, + stat: fs.stat, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +// tslint:enable no-redundant-jsdoc +class Settings { + constructor(_options = {}) { + this._options = _options; + this.absolute = this._getValue(this._options.absolute, false); + this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); + this.braceExpansion = this._getValue(this._options.braceExpansion, true); + this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); + this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); + this.cwd = this._getValue(this._options.cwd, process.cwd()); + this.deep = this._getValue(this._options.deep, Infinity); + this.dot = this._getValue(this._options.dot, false); + this.extglob = this._getValue(this._options.extglob, true); + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); + this.fs = this._getFileSystemMethods(this._options.fs); + this.globstar = this._getValue(this._options.globstar, true); + this.ignore = this._getValue(this._options.ignore, []); + this.markDirectories = this._getValue(this._options.markDirectories, false); + this.objectMode = this._getValue(this._options.objectMode, false); + this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); + this.onlyFiles = this._getValue(this._options.onlyFiles, true); + this.stats = this._getValue(this._options.stats, false); + this.suppressErrors = this._getValue(this._options.suppressErrors, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); + this.unique = this._getValue(this._options.unique, true); + if (this.onlyDirectories) { + this.onlyFiles = false; + } + if (this.stats) { + this.objectMode = true; + } + } + _getValue(option, value) { + return option === undefined ? value : option; + } + _getFileSystemMethods(methods = {}) { + return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); + } +} +exports.default = Settings; + + +/***/ }), +/* 663 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const pathType = __webpack_require__(664); + +const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; + +const getPath = (filepath, cwd) => { + const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; + return path.isAbsolute(pth) ? pth : path.join(cwd, pth); +}; + +const addExtensions = (file, extensions) => { + if (path.extname(file)) { + return `**/${file}`; + } + + return `**/${file}.${getExtensions(extensions)}`; +}; + +const getGlob = (directory, options) => { + if (options.files && !Array.isArray(options.files)) { + throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); + } + + if (options.extensions && !Array.isArray(options.extensions)) { + throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); + } + + if (options.files && options.extensions) { + return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); + } + + if (options.files) { + return options.files.map(x => path.posix.join(directory, `**/${x}`)); + } + + if (options.extensions) { + return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; + } + + return [path.posix.join(directory, '**')]; +}; + +module.exports = async (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = await Promise.all([].concat(input).map(async x => { + const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); + return isDirectory ? getGlob(x, options) : x; + })); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + +module.exports.sync = (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + + const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); + + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; + + +/***/ }), +/* 664 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); + +async function isType(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + const stats = await promisify(fs[fsStatType])(filePath); + return stats[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +function isTypeSync(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + + try { + return fs[fsStatType](filePath)[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } +} + +exports.isFile = isType.bind(null, 'stat', 'isFile'); +exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); +exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); +exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); +exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); +exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); + + +/***/ }), +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(27); -const stream_2 = __webpack_require__(622); -const provider_1 = __webpack_require__(649); -class ProviderStream extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new stream_2.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const source = this.api(root, task, options); - const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); - source - .once('error', (error) => dest.emit('error', error)) - .on('data', (entry) => dest.emit('data', options.transform(entry))) - .once('end', () => dest.emit('end')); - return dest; - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderStream; + +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); +const path = __webpack_require__(16); +const fastGlob = __webpack_require__(597); +const gitIgnore = __webpack_require__(666); +const slash = __webpack_require__(667); + +const DEFAULT_IGNORE = [ + '**/node_modules/**', + '**/flow-typed/**', + '**/coverage/**', + '**/.git' +]; + +const readFileP = promisify(fs.readFile); + +const mapGitIgnorePatternTo = base => ignore => { + if (ignore.startsWith('!')) { + return '!' + path.posix.join(base, ignore.slice(1)); + } + + return path.posix.join(base, ignore); +}; + +const parseGitIgnore = (content, options) => { + const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); + + return content + .split(/\r?\n/) + .filter(Boolean) + .filter(line => !line.startsWith('#')) + .map(mapGitIgnorePatternTo(base)); +}; + +const reduceIgnore = files => { + return files.reduce((ignores, file) => { + ignores.add(parseGitIgnore(file.content, { + cwd: file.cwd, + fileName: file.filePath + })); + return ignores; + }, gitIgnore()); +}; + +const ensureAbsolutePathForCwd = (cwd, p) => { + if (path.isAbsolute(p)) { + if (p.startsWith(cwd)) { + return p; + } + + throw new Error(`Path ${p} is not in cwd ${cwd}`); + } + + return path.join(cwd, p); +}; + +const getIsIgnoredPredecate = (ignores, cwd) => { + return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); +}; + +const getFile = async (file, cwd) => { + const filePath = path.join(cwd, file); + const content = await readFileP(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const getFileSync = (file, cwd) => { + const filePath = path.join(cwd, file); + const content = fs.readFileSync(filePath, 'utf8'); + + return { + cwd, + filePath, + content + }; +}; + +const normalizeOptions = ({ + ignore = [], + cwd = process.cwd() +} = {}) => { + return {ignore, cwd}; +}; + +module.exports = async options => { + options = normalizeOptions(options); + + const paths = await fastGlob('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; + +module.exports.sync = options => { + options = normalizeOptions(options); + + const paths = fastGlob.sync('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + + const files = paths.map(file => getFileSync(file, options.cwd)); + const ignores = reduceIgnore(files); + + return getIsIgnoredPredecate(ignores, options.cwd); +}; /***/ }), -/* 655 */ -/***/ (function(module, exports, __webpack_require__) { +/* 666 */ +/***/ (function(module, exports) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(656); -const provider_1 = __webpack_require__(649); -class ProviderSync extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new sync_1.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const entries = this.api(root, task, options); - return entries.map(options.transform); - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderSync; +// A simple implementation of make-array +function makeArray (subject) { + return Array.isArray(subject) + ? subject + : [subject] +} + +const REGEX_TEST_BLANK_LINE = /^\s+$/ +const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ +const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ +const REGEX_SPLITALL_CRLF = /\r?\n/g +// /foo, +// ./foo, +// ../foo, +// . +// .. +const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ + +const SLASH = '/' +const KEY_IGNORE = typeof Symbol !== 'undefined' + ? Symbol.for('node-ignore') + /* istanbul ignore next */ + : 'node-ignore' + +const define = (object, key, value) => + Object.defineProperty(object, key, {value}) + +const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + +// Sanitize the range of a regular expression +// The cases are complicated, see test cases for details +const sanitizeRange = range => range.replace( + REGEX_REGEXP_RANGE, + (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) + ? match + // Invalid range (out of order) which is ok for gitignore rules but + // fatal for JavaScript regular expression, so eliminate it. + : '' +) + +// > If the pattern ends with a slash, +// > it is removed for the purpose of the following description, +// > but it would only find a match with a directory. +// > In other words, foo/ will match a directory foo and paths underneath it, +// > but will not match a regular file or a symbolic link foo +// > (this is consistent with the way how pathspec works in general in Git). +// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' +// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call +// you could use option `mark: true` with `glob` + +// '`foo/`' should not continue with the '`..`' +const DEFAULT_REPLACER_PREFIX = [ + + // > Trailing spaces are ignored unless they are quoted with backslash ("\") + [ + // (a\ ) -> (a ) + // (a ) -> (a) + // (a \ ) -> (a ) + /\\?\s+$/, + match => match.indexOf('\\') === 0 + ? ' ' + : '' + ], + + // replace (\ ) with ' ' + [ + /\\\s/g, + () => ' ' + ], + + // Escape metacharacters + // which is written down by users but means special for regular expressions. + + // > There are 12 characters with special meanings: + // > - the backslash \, + // > - the caret ^, + // > - the dollar sign $, + // > - the period or dot ., + // > - the vertical bar or pipe symbol |, + // > - the question mark ?, + // > - the asterisk or star *, + // > - the plus sign +, + // > - the opening parenthesis (, + // > - the closing parenthesis ), + // > - and the opening square bracket [, + // > - the opening curly brace {, + // > These special characters are often called "metacharacters". + [ + /[\\^$.|*+(){]/g, + match => `\\${match}` + ], + + [ + // > [abc] matches any character inside the brackets + // > (in this case a, b, or c); + /\[([^\]/]*)($|\])/g, + (match, p1, p2) => p2 === ']' + ? `[${sanitizeRange(p1)}]` + : `\\${match}` + ], + + [ + // > a question mark (?) matches a single character + /(?!\\)\?/g, + () => '[^/]' + ], + + // leading slash + [ + + // > A leading slash matches the beginning of the pathname. + // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". + // A leading slash matches the beginning of the pathname + /^\//, + () => '^' + ], + + // replace special metacharacter slash after the leading slash + [ + /\//g, + () => '\\/' + ], + + [ + // > A leading "**" followed by a slash means match in all directories. + // > For example, "**/foo" matches file or directory "foo" anywhere, + // > the same as pattern "foo". + // > "**/foo/bar" matches file or directory "bar" anywhere that is directly + // > under directory "foo". + // Notice that the '*'s have been replaced as '\\*' + /^\^*\\\*\\\*\\\//, + + // '**/foo' <-> 'foo' + () => '^(?:.*\\/)?' + ] +] + +const DEFAULT_REPLACER_SUFFIX = [ + // starting + [ + // there will be no leading '/' + // (which has been replaced by section "leading slash") + // If starts with '**', adding a '^' to the regular expression also works + /^(?=[^^])/, + function startingReplacer () { + return !/\/(?!$)/.test(this) + // > If the pattern does not contain a slash /, + // > Git treats it as a shell glob pattern + // Actually, if there is only a trailing slash, + // git also treats it as a shell glob pattern + ? '(?:^|\\/)' + + // > Otherwise, Git treats the pattern as a shell glob suitable for + // > consumption by fnmatch(3) + : '^' + } + ], + + // two globstars + [ + // Use lookahead assertions so that we could match more than one `'/**'` + /\\\/\\\*\\\*(?=\\\/|$)/g, + + // Zero, one or several directories + // should not use '*', or it will be replaced by the next replacer + + // Check if it is not the last `'/**'` + (_, index, str) => index + 6 < str.length + + // case: /**/ + // > A slash followed by two consecutive asterisks then a slash matches + // > zero or more directories. + // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. + // '/**/' + ? '(?:\\/[^\\/]+)*' + + // case: /** + // > A trailing `"/**"` matches everything inside. + + // #21: everything inside but it should not include the current folder + : '\\/.+' + ], + + // intermediate wildcards + [ + // Never replace escaped '*' + // ignore rule '\*' will match the path '*' + + // 'abc.*/' -> go + // 'abc.*' -> skip this rule + /(^|[^\\]+)\\\*(?=.+)/g, + + // '*.js' matches '.js' + // '*.js' doesn't match 'abc' + (_, p1) => `${p1}[^\\/]*` + ], + + // trailing wildcard + [ + /(\^|\\\/)?\\\*$/, + (_, p1) => { + const prefix = p1 + // '\^': + // '/*' does not match '' + // '/*' does not match everything + + // '\\\/': + // 'abc/*' does not match 'abc/' + ? `${p1}[^/]+` + + // 'a*' matches 'a' + // 'a*' matches 'aa' + : '[^/]*' + + return `${prefix}(?=$|\\/$)` + } + ], + + [ + // unescape + /\\\\\\/g, + () => '\\' + ] +] + +const POSITIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + + // 'f' + // matches + // - /f(end) + // - /f/ + // - (start)f(end) + // - (start)f/ + // doesn't match + // - oof + // - foo + // pseudo: + // -> (^|/)f(/|$) + + // ending + [ + // 'js' will not match 'js.' + // 'ab' will not match 'abc' + /(?:[^*/])$/, + + // 'js*' will not match 'a.js' + // 'js/' will not match 'a.js' + // 'js' will match 'a.js' and 'a.js/' + match => `${match}(?=$|\\/)` + ], + + ...DEFAULT_REPLACER_SUFFIX +] + +const NEGATIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + + // #24, #38 + // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) + // A negative pattern without a trailing wildcard should not + // re-include the things inside that directory. + + // eg: + // ['node_modules/*', '!node_modules'] + // should ignore `node_modules/a.js` + [ + /(?:[^*])$/, + match => `${match}(?=$|\\/$)` + ], + + ...DEFAULT_REPLACER_SUFFIX +] + +// A simple cache, because an ignore rule only has only one certain meaning +const regexCache = Object.create(null) + +// @param {pattern} +const makeRegex = (pattern, negative, ignorecase) => { + const r = regexCache[pattern] + if (r) { + return r + } + + const replacers = negative + ? NEGATIVE_REPLACERS + : POSITIVE_REPLACERS + + const source = replacers.reduce( + (prev, current) => prev.replace(current[0], current[1].bind(pattern)), + pattern + ) + + return regexCache[pattern] = ignorecase + ? new RegExp(source, 'i') + : new RegExp(source) +} + +const isString = subject => typeof subject === 'string' + +// > A blank line matches no files, so it can serve as a separator for readability. +const checkPattern = pattern => pattern + && isString(pattern) + && !REGEX_TEST_BLANK_LINE.test(pattern) + + // > A line starting with # serves as a comment. + && pattern.indexOf('#') !== 0 + +const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) + +class IgnoreRule { + constructor ( + origin, + pattern, + negative, + regex + ) { + this.origin = origin + this.pattern = pattern + this.negative = negative + this.regex = regex + } +} + +const createRule = (pattern, ignorecase) => { + const origin = pattern + let negative = false + + // > An optional prefix "!" which negates the pattern; + if (pattern.indexOf('!') === 0) { + negative = true + pattern = pattern.substr(1) + } + pattern = pattern + // > Put a backslash ("\") in front of the first "!" for patterns that + // > begin with a literal "!", for example, `"\!important!.txt"`. + .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') + // > Put a backslash ("\") in front of the first hash for patterns that + // > begin with a hash. + .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') -/***/ }), -/* 656 */ -/***/ (function(module, exports, __webpack_require__) { + const regex = makeRegex(pattern, negative, ignorecase) -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(623); -const fsWalk = __webpack_require__(628); -const reader_1 = __webpack_require__(648); -class ReaderSync extends reader_1.default { - constructor() { - super(...arguments); - this._walkSync = fsWalk.walkSync; - this._statSync = fsStat.statSync; - } - dynamic(root, options) { - return this._walkSync(root, options); - } - static(patterns, options) { - const entries = []; - for (const pattern of patterns) { - const filepath = this._getFullEntryPath(pattern); - const entry = this._getEntry(filepath, pattern, options); - if (entry === null || !options.entryFilter(entry)) { - continue; - } - entries.push(entry); - } - return entries; - } - _getEntry(filepath, pattern, options) { - try { - const stats = this._getStat(filepath); - return this._makeEntry(stats, pattern); - } - catch (error) { - if (options.errorFilter(error)) { - return null; - } - throw error; - } - } - _getStat(filepath) { - return this._statSync(filepath, this._fsStatSettings); - } -} -exports.default = ReaderSync; + return new IgnoreRule( + origin, + pattern, + negative, + regex + ) +} +const throwError = (message, Ctor) => { + throw new Ctor(message) +} -/***/ }), -/* 657 */ -/***/ (function(module, exports, __webpack_require__) { +const checkPath = (path, originalPath, doThrow) => { + if (!isString(path)) { + return doThrow( + `path must be a string, but got \`${originalPath}\``, + TypeError + ) + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -const os = __webpack_require__(11); -const CPU_COUNT = os.cpus().length; -exports.DEFAULT_FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - lstatSync: fs.lstatSync, - stat: fs.stat, - statSync: fs.statSync, - readdir: fs.readdir, - readdirSync: fs.readdirSync -}; -// tslint:enable no-redundant-jsdoc -class Settings { - constructor(_options = {}) { - this._options = _options; - this.absolute = this._getValue(this._options.absolute, false); - this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); - this.braceExpansion = this._getValue(this._options.braceExpansion, true); - this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); - this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); - this.cwd = this._getValue(this._options.cwd, process.cwd()); - this.deep = this._getValue(this._options.deep, Infinity); - this.dot = this._getValue(this._options.dot, false); - this.extglob = this._getValue(this._options.extglob, true); - this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); - this.fs = this._getFileSystemMethods(this._options.fs); - this.globstar = this._getValue(this._options.globstar, true); - this.ignore = this._getValue(this._options.ignore, []); - this.markDirectories = this._getValue(this._options.markDirectories, false); - this.objectMode = this._getValue(this._options.objectMode, false); - this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); - this.onlyFiles = this._getValue(this._options.onlyFiles, true); - this.stats = this._getValue(this._options.stats, false); - this.suppressErrors = this._getValue(this._options.suppressErrors, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); - this.unique = this._getValue(this._options.unique, true); - if (this.onlyDirectories) { - this.onlyFiles = false; - } - if (this.stats) { - this.objectMode = true; - } - } - _getValue(option, value) { - return option === undefined ? value : option; - } - _getFileSystemMethods(methods = {}) { - return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); - } -} -exports.default = Settings; + // We don't know if we should ignore '', so throw + if (!path) { + return doThrow(`path must not be empty`, TypeError) + } + // Check if it is a relative path + if (checkPath.isNotRelative(path)) { + const r = '`path.relative()`d' + return doThrow( + `path should be a ${r} string, but got "${originalPath}"`, + RangeError + ) + } -/***/ }), -/* 658 */ -/***/ (function(module, exports, __webpack_require__) { + return true +} -"use strict"; +const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) -const path = __webpack_require__(16); -const pathType = __webpack_require__(659); +checkPath.isNotRelative = isNotRelative +checkPath.convert = p => p -const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; +class Ignore { + constructor ({ + ignorecase = true + } = {}) { + this._rules = [] + this._ignorecase = ignorecase + define(this, KEY_IGNORE, true) + this._initCache() + } -const getPath = (filepath, cwd) => { - const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; - return path.isAbsolute(pth) ? pth : path.join(cwd, pth); -}; + _initCache () { + this._ignoreCache = Object.create(null) + this._testCache = Object.create(null) + } -const addExtensions = (file, extensions) => { - if (path.extname(file)) { - return `**/${file}`; - } + _addPattern (pattern) { + // #32 + if (pattern && pattern[KEY_IGNORE]) { + this._rules = this._rules.concat(pattern._rules) + this._added = true + return + } - return `**/${file}.${getExtensions(extensions)}`; -}; + if (checkPattern(pattern)) { + const rule = createRule(pattern, this._ignorecase) + this._added = true + this._rules.push(rule) + } + } -const getGlob = (directory, options) => { - if (options.files && !Array.isArray(options.files)) { - throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); - } + // @param {Array<string> | string | Ignore} pattern + add (pattern) { + this._added = false - if (options.extensions && !Array.isArray(options.extensions)) { - throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); - } + makeArray( + isString(pattern) + ? splitPattern(pattern) + : pattern + ).forEach(this._addPattern, this) - if (options.files && options.extensions) { - return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); - } + // Some rules have just added to the ignore, + // making the behavior changed. + if (this._added) { + this._initCache() + } - if (options.files) { - return options.files.map(x => path.posix.join(directory, `**/${x}`)); - } + return this + } - if (options.extensions) { - return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; - } + // legacy + addPattern (pattern) { + return this.add(pattern) + } - return [path.posix.join(directory, '**')]; -}; + // | ignored : unignored + // negative | 0:0 | 0:1 | 1:0 | 1:1 + // -------- | ------- | ------- | ------- | -------- + // 0 | TEST | TEST | SKIP | X + // 1 | TESTIF | SKIP | TEST | X -module.exports = async (input, options) => { - options = { - cwd: process.cwd(), - ...options - }; + // - SKIP: always skip + // - TEST: always test + // - TESTIF: only test if checkUnignored + // - X: that never happen - if (typeof options.cwd !== 'string') { - throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); - } + // @param {boolean} whether should check if the path is unignored, + // setting `checkUnignored` to `false` could reduce additional + // path matching. - const globs = await Promise.all([].concat(input).map(async x => { - const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); - return isDirectory ? getGlob(x, options) : x; - })); + // @returns {TestResult} true if a file is ignored + _testOne (path, checkUnignored) { + let ignored = false + let unignored = false - return [].concat.apply([], globs); // eslint-disable-line prefer-spread -}; + this._rules.forEach(rule => { + const {negative} = rule + if ( + unignored === negative && ignored !== unignored + || negative && !ignored && !unignored && !checkUnignored + ) { + return + } -module.exports.sync = (input, options) => { - options = { - cwd: process.cwd(), - ...options - }; + const matched = rule.regex.test(path) - if (typeof options.cwd !== 'string') { - throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); - } + if (matched) { + ignored = !negative + unignored = negative + } + }) - const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); + return { + ignored, + unignored + } + } - return [].concat.apply([], globs); // eslint-disable-line prefer-spread -}; + // @returns {TestResult} + _test (originalPath, cache, checkUnignored, slices) { + const path = originalPath + // Supports nullable path + && checkPath.convert(originalPath) + checkPath(path, originalPath, throwError) -/***/ }), -/* 659 */ -/***/ (function(module, exports, __webpack_require__) { + return this._t(path, cache, checkUnignored, slices) + } -"use strict"; + _t (path, cache, checkUnignored, slices) { + if (path in cache) { + return cache[path] + } -const {promisify} = __webpack_require__(29); -const fs = __webpack_require__(23); + if (!slices) { + // path/to/a.js + // ['path', 'to', 'a.js'] + slices = path.split(SLASH) + } -async function isType(fsStatType, statsMethodName, filePath) { - if (typeof filePath !== 'string') { - throw new TypeError(`Expected a string, got ${typeof filePath}`); - } + slices.pop() - try { - const stats = await promisify(fs[fsStatType])(filePath); - return stats[statsMethodName](); - } catch (error) { - if (error.code === 'ENOENT') { - return false; - } + // If the path has no parent directory, just test it + if (!slices.length) { + return cache[path] = this._testOne(path, checkUnignored) + } - throw error; - } -} + const parent = this._t( + slices.join(SLASH) + SLASH, + cache, + checkUnignored, + slices + ) -function isTypeSync(fsStatType, statsMethodName, filePath) { - if (typeof filePath !== 'string') { - throw new TypeError(`Expected a string, got ${typeof filePath}`); - } + // If the path contains a parent directory, check the parent first + return cache[path] = parent.ignored + // > It is not possible to re-include a file if a parent directory of + // > that file is excluded. + ? parent + : this._testOne(path, checkUnignored) + } - try { - return fs[fsStatType](filePath)[statsMethodName](); - } catch (error) { - if (error.code === 'ENOENT') { - return false; - } + ignores (path) { + return this._test(path, this._ignoreCache, false).ignored + } - throw error; - } + createFilter () { + return path => !this.ignores(path) + } + + filter (paths) { + return makeArray(paths).filter(this.createFilter()) + } + + // @returns {TestResult} + test (path) { + return this._test(path, this._testCache, true) + } } -exports.isFile = isType.bind(null, 'stat', 'isFile'); -exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); -exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); -exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); -exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); -exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); +const factory = options => new Ignore(options) +const returnFalse = () => false -/***/ }), -/* 660 */ -/***/ (function(module, exports, __webpack_require__) { +const isPathValid = path => + checkPath(path && checkPath.convert(path), path, returnFalse) -"use strict"; +factory.isPathValid = isPathValid -const {promisify} = __webpack_require__(29); -const fs = __webpack_require__(23); -const path = __webpack_require__(16); -const fastGlob = __webpack_require__(592); -const gitIgnore = __webpack_require__(661); -const slash = __webpack_require__(662); +// Fixes typescript +factory.default = factory -const DEFAULT_IGNORE = [ - '**/node_modules/**', - '**/flow-typed/**', - '**/coverage/**', - '**/.git' -]; +module.exports = factory -const readFileP = promisify(fs.readFile); +// Windows +// -------------------------------------------------------------- +/* istanbul ignore if */ +if ( + // Detect `process` so that it can run in browsers. + typeof process !== 'undefined' + && ( + process.env && process.env.IGNORE_TEST_WIN32 + || process.platform === 'win32' + ) +) { + /* eslint no-control-regex: "off" */ + const makePosix = str => /^\\\\\?\\/.test(str) + || /["<>|\u0000-\u001F]+/u.test(str) + ? str + : str.replace(/\\/g, '/') -const mapGitIgnorePatternTo = base => ignore => { - if (ignore.startsWith('!')) { - return '!' + path.posix.join(base, ignore.slice(1)); - } + checkPath.convert = makePosix - return path.posix.join(base, ignore); -}; + // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' + // 'd:\\foo' + const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i + checkPath.isNotRelative = path => + REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) + || isNotRelative(path) +} -const parseGitIgnore = (content, options) => { - const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); - return content - .split(/\r?\n/) - .filter(Boolean) - .filter(line => !line.startsWith('#')) - .map(mapGitIgnorePatternTo(base)); -}; +/***/ }), +/* 667 */ +/***/ (function(module, exports, __webpack_require__) { -const reduceIgnore = files => { - return files.reduce((ignores, file) => { - ignores.add(parseGitIgnore(file.content, { - cwd: file.cwd, - fileName: file.filePath - })); - return ignores; - }, gitIgnore()); -}; +"use strict"; -const ensureAbsolutePathForCwd = (cwd, p) => { - if (path.isAbsolute(p)) { - if (p.startsWith(cwd)) { - return p; - } +module.exports = path => { + const isExtendedLengthPath = /^\\\\\?\\/.test(path); + const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex - throw new Error(`Path ${p} is not in cwd ${cwd}`); + if (isExtendedLengthPath || hasNonAscii) { + return path; } - return path.join(cwd, p); -}; - -const getIsIgnoredPredecate = (ignores, cwd) => { - return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); + return path.replace(/\\/g, '/'); }; -const getFile = async (file, cwd) => { - const filePath = path.join(cwd, file); - const content = await readFileP(filePath, 'utf8'); - - return { - cwd, - filePath, - content - }; -}; -const getFileSync = (file, cwd) => { - const filePath = path.join(cwd, file); - const content = fs.readFileSync(filePath, 'utf8'); +/***/ }), +/* 668 */ +/***/ (function(module, exports, __webpack_require__) { - return { - cwd, - filePath, - content - }; -}; +"use strict"; -const normalizeOptions = ({ - ignore = [], - cwd = process.cwd() -} = {}) => { - return {ignore, cwd}; -}; +const {Transform} = __webpack_require__(27); -module.exports = async options => { - options = normalizeOptions(options); +class ObjectTransform extends Transform { + constructor() { + super({ + objectMode: true + }); + } +} - const paths = await fastGlob('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }); +class FilterStream extends ObjectTransform { + constructor(filter) { + super(); + this._filter = filter; + } - const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); - const ignores = reduceIgnore(files); + _transform(data, encoding, callback) { + if (this._filter(data)) { + this.push(data); + } - return getIsIgnoredPredecate(ignores, options.cwd); -}; + callback(); + } +} -module.exports.sync = options => { - options = normalizeOptions(options); +class UniqueStream extends ObjectTransform { + constructor() { + super(); + this._pushed = new Set(); + } - const paths = fastGlob.sync('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }); + _transform(data, encoding, callback) { + if (!this._pushed.has(data)) { + this.push(data); + this._pushed.add(data); + } - const files = paths.map(file => getFileSync(file, options.cwd)); - const ignores = reduceIgnore(files); + callback(); + } +} - return getIsIgnoredPredecate(ignores, options.cwd); +module.exports = { + FilterStream, + UniqueStream }; /***/ }), -/* 661 */ -/***/ (function(module, exports) { +/* 669 */ +/***/ (function(module, exports, __webpack_require__) { -// A simple implementation of make-array -function makeArray (subject) { - return Array.isArray(subject) - ? subject - : [subject] -} +"use strict"; -const REGEX_TEST_BLANK_LINE = /^\s+$/ -const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ -const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ -const REGEX_SPLITALL_CRLF = /\r?\n/g -// /foo, -// ./foo, -// ../foo, -// . -// .. -const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ +const path = __webpack_require__(16); -const SLASH = '/' -const KEY_IGNORE = typeof Symbol !== 'undefined' - ? Symbol.for('node-ignore') - /* istanbul ignore next */ - : 'node-ignore' +module.exports = path_ => { + let cwd = process.cwd(); -const define = (object, key, value) => - Object.defineProperty(object, key, {value}) + path_ = path.resolve(path_); -const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + if (process.platform === 'win32') { + cwd = cwd.toLowerCase(); + path_ = path_.toLowerCase(); + } -// Sanitize the range of a regular expression -// The cases are complicated, see test cases for details -const sanitizeRange = range => range.replace( - REGEX_REGEXP_RANGE, - (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) - ? match - // Invalid range (out of order) which is ok for gitignore rules but - // fatal for JavaScript regular expression, so eliminate it. - : '' -) + return path_ === cwd; +}; -// > If the pattern ends with a slash, -// > it is removed for the purpose of the following description, -// > but it would only find a match with a directory. -// > In other words, foo/ will match a directory foo and paths underneath it, -// > but will not match a regular file or a symbolic link foo -// > (this is consistent with the way how pathspec works in general in Git). -// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' -// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call -// you could use option `mark: true` with `glob` -// '`foo/`' should not continue with the '`..`' -const DEFAULT_REPLACER_PREFIX = [ +/***/ }), +/* 670 */ +/***/ (function(module, exports, __webpack_require__) { - // > Trailing spaces are ignored unless they are quoted with backslash ("\") - [ - // (a\ ) -> (a ) - // (a ) -> (a) - // (a \ ) -> (a ) - /\\?\s+$/, - match => match.indexOf('\\') === 0 - ? ' ' - : '' - ], +"use strict"; - // replace (\ ) with ' ' - [ - /\\\s/g, - () => ' ' - ], +const path = __webpack_require__(16); - // Escape metacharacters - // which is written down by users but means special for regular expressions. +module.exports = (childPath, parentPath) => { + childPath = path.resolve(childPath); + parentPath = path.resolve(parentPath); - // > There are 12 characters with special meanings: - // > - the backslash \, - // > - the caret ^, - // > - the dollar sign $, - // > - the period or dot ., - // > - the vertical bar or pipe symbol |, - // > - the question mark ?, - // > - the asterisk or star *, - // > - the plus sign +, - // > - the opening parenthesis (, - // > - the closing parenthesis ), - // > - and the opening square bracket [, - // > - the opening curly brace {, - // > These special characters are often called "metacharacters". - [ - /[\\^$.|*+(){]/g, - match => `\\${match}` - ], + if (process.platform === 'win32') { + childPath = childPath.toLowerCase(); + parentPath = parentPath.toLowerCase(); + } - [ - // > [abc] matches any character inside the brackets - // > (in this case a, b, or c); - /\[([^\]/]*)($|\])/g, - (match, p1, p2) => p2 === ']' - ? `[${sanitizeRange(p1)}]` - : `\\${match}` - ], + if (childPath === parentPath) { + return false; + } - [ - // > a question mark (?) matches a single character - /(?!\\)\?/g, - () => '[^/]' - ], + childPath += path.sep; + parentPath += path.sep; - // leading slash - [ + return childPath.startsWith(parentPath); +}; - // > A leading slash matches the beginning of the pathname. - // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". - // A leading slash matches the beginning of the pathname - /^\//, - () => '^' - ], - // replace special metacharacter slash after the leading slash - [ - /\//g, - () => '\\/' - ], +/***/ }), +/* 671 */ +/***/ (function(module, exports, __webpack_require__) { - [ - // > A leading "**" followed by a slash means match in all directories. - // > For example, "**/foo" matches file or directory "foo" anywhere, - // > the same as pattern "foo". - // > "**/foo/bar" matches file or directory "bar" anywhere that is directly - // > under directory "foo". - // Notice that the '*'s have been replaced as '\\*' - /^\^*\\\*\\\*\\\//, +const assert = __webpack_require__(30) +const path = __webpack_require__(16) +const fs = __webpack_require__(23) +let glob = undefined +try { + glob = __webpack_require__(592) +} catch (_err) { + // treat glob as optional. +} - // '**/foo' <-> 'foo' - () => '^(?:.*\\/)?' - ] -] +const defaultGlobOpts = { + nosort: true, + silent: true +} -const DEFAULT_REPLACER_SUFFIX = [ - // starting - [ - // there will be no leading '/' - // (which has been replaced by section "leading slash") - // If starts with '**', adding a '^' to the regular expression also works - /^(?=[^^])/, - function startingReplacer () { - return !/\/(?!$)/.test(this) - // > If the pattern does not contain a slash /, - // > Git treats it as a shell glob pattern - // Actually, if there is only a trailing slash, - // git also treats it as a shell glob pattern - ? '(?:^|\\/)' +// for EMFILE handling +let timeout = 0 - // > Otherwise, Git treats the pattern as a shell glob suitable for - // > consumption by fnmatch(3) - : '^' - } - ], +const isWindows = (process.platform === "win32") - // two globstars - [ - // Use lookahead assertions so that we could match more than one `'/**'` - /\\\/\\\*\\\*(?=\\\/|$)/g, +const defaults = options => { + const methods = [ + 'unlink', + 'chmod', + 'stat', + 'lstat', + 'rmdir', + 'readdir' + ] + methods.forEach(m => { + options[m] = options[m] || fs[m] + m = m + 'Sync' + options[m] = options[m] || fs[m] + }) - // Zero, one or several directories - // should not use '*', or it will be replaced by the next replacer + options.maxBusyTries = options.maxBusyTries || 3 + options.emfileWait = options.emfileWait || 1000 + if (options.glob === false) { + options.disableGlob = true + } + if (options.disableGlob !== true && glob === undefined) { + throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + } + options.disableGlob = options.disableGlob || false + options.glob = options.glob || defaultGlobOpts +} - // Check if it is not the last `'/**'` - (_, index, str) => index + 6 < str.length +const rimraf = (p, options, cb) => { + if (typeof options === 'function') { + cb = options + options = {} + } - // case: /**/ - // > A slash followed by two consecutive asterisks then a slash matches - // > zero or more directories. - // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. - // '/**/' - ? '(?:\\/[^\\/]+)*' + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert.equal(typeof cb, 'function', 'rimraf: callback function required') + assert(options, 'rimraf: invalid options argument provided') + assert.equal(typeof options, 'object', 'rimraf: options should be object') - // case: /** - // > A trailing `"/**"` matches everything inside. + defaults(options) - // #21: everything inside but it should not include the current folder - : '\\/.+' - ], + let busyTries = 0 + let errState = null + let n = 0 - // intermediate wildcards - [ - // Never replace escaped '*' - // ignore rule '\*' will match the path '*' + const next = (er) => { + errState = errState || er + if (--n === 0) + cb(errState) + } - // 'abc.*/' -> go - // 'abc.*' -> skip this rule - /(^|[^\\]+)\\\*(?=.+)/g, + const afterGlob = (er, results) => { + if (er) + return cb(er) - // '*.js' matches '.js' - // '*.js' doesn't match 'abc' - (_, p1) => `${p1}[^\\/]*` - ], + n = results.length + if (n === 0) + return cb() - // trailing wildcard - [ - /(\^|\\\/)?\\\*$/, - (_, p1) => { - const prefix = p1 - // '\^': - // '/*' does not match '' - // '/*' does not match everything + results.forEach(p => { + const CB = (er) => { + if (er) { + if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && + busyTries < options.maxBusyTries) { + busyTries ++ + // try again, with the same exact callback as this one. + return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) + } - // '\\\/': - // 'abc/*' does not match 'abc/' - ? `${p1}[^/]+` + // this one won't happen if graceful-fs is used. + if (er.code === "EMFILE" && timeout < options.emfileWait) { + return setTimeout(() => rimraf_(p, options, CB), timeout ++) + } - // 'a*' matches 'a' - // 'a*' matches 'aa' - : '[^/]*' + // already gone + if (er.code === "ENOENT") er = null + } - return `${prefix}(?=$|\\/$)` - } - ], + timeout = 0 + next(er) + } + rimraf_(p, options, CB) + }) + } - [ - // unescape - /\\\\\\/g, - () => '\\' - ] -] + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) -const POSITIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, + options.lstat(p, (er, stat) => { + if (!er) + return afterGlob(null, [p]) - // 'f' - // matches - // - /f(end) - // - /f/ - // - (start)f(end) - // - (start)f/ - // doesn't match - // - oof - // - foo - // pseudo: - // -> (^|/)f(/|$) + glob(p, options.glob, afterGlob) + }) - // ending - [ - // 'js' will not match 'js.' - // 'ab' will not match 'abc' - /(?:[^*/])$/, +} - // 'js*' will not match 'a.js' - // 'js/' will not match 'a.js' - // 'js' will match 'a.js' and 'a.js/' - match => `${match}(?=$|\\/)` - ], +// Two possible strategies. +// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR +// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR +// +// Both result in an extra syscall when you guess wrong. However, there +// are likely far more normal files in the world than directories. This +// is based on the assumption that a the average number of files per +// directory is >= 1. +// +// If anyone ever complains about this, then I guess the strategy could +// be made configurable somehow. But until then, YAGNI. +const rimraf_ = (p, options, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') - ...DEFAULT_REPLACER_SUFFIX -] + // sunos lets the root user unlink directories, which is... weird. + // so we have to lstat here and make sure it's not a dir. + options.lstat(p, (er, st) => { + if (er && er.code === "ENOENT") + return cb(null) -const NEGATIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, + // Windows can EPERM on stat. Life is suffering. + if (er && er.code === "EPERM" && isWindows) + fixWinEPERM(p, options, er, cb) - // #24, #38 - // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) - // A negative pattern without a trailing wildcard should not - // re-include the things inside that directory. + if (st && st.isDirectory()) + return rmdir(p, options, er, cb) - // eg: - // ['node_modules/*', '!node_modules'] - // should ignore `node_modules/a.js` - [ - /(?:[^*])$/, - match => `${match}(?=$|\\/$)` - ], + options.unlink(p, er => { + if (er) { + if (er.code === "ENOENT") + return cb(null) + if (er.code === "EPERM") + return (isWindows) + ? fixWinEPERM(p, options, er, cb) + : rmdir(p, options, er, cb) + if (er.code === "EISDIR") + return rmdir(p, options, er, cb) + } + return cb(er) + }) + }) +} - ...DEFAULT_REPLACER_SUFFIX -] +const fixWinEPERM = (p, options, er, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') + if (er) + assert(er instanceof Error) -// A simple cache, because an ignore rule only has only one certain meaning -const regexCache = Object.create(null) + options.chmod(p, 0o666, er2 => { + if (er2) + cb(er2.code === "ENOENT" ? null : er) + else + options.stat(p, (er3, stats) => { + if (er3) + cb(er3.code === "ENOENT" ? null : er) + else if (stats.isDirectory()) + rmdir(p, options, er, cb) + else + options.unlink(p, cb) + }) + }) +} -// @param {pattern} -const makeRegex = (pattern, negative, ignorecase) => { - const r = regexCache[pattern] - if (r) { - return r +const fixWinEPERMSync = (p, options, er) => { + assert(p) + assert(options) + if (er) + assert(er instanceof Error) + + try { + options.chmodSync(p, 0o666) + } catch (er2) { + if (er2.code === "ENOENT") + return + else + throw er } - const replacers = negative - ? NEGATIVE_REPLACERS - : POSITIVE_REPLACERS + let stats + try { + stats = options.statSync(p) + } catch (er3) { + if (er3.code === "ENOENT") + return + else + throw er + } - const source = replacers.reduce( - (prev, current) => prev.replace(current[0], current[1].bind(pattern)), - pattern - ) + if (stats.isDirectory()) + rmdirSync(p, options, er) + else + options.unlinkSync(p) +} - return regexCache[pattern] = ignorecase - ? new RegExp(source, 'i') - : new RegExp(source) +const rmdir = (p, options, originalEr, cb) => { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + assert(typeof cb === 'function') + + // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) + // if we guessed wrong, and it's not a directory, then + // raise the original error. + options.rmdir(p, er => { + if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) + rmkids(p, options, cb) + else if (er && er.code === "ENOTDIR") + cb(originalEr) + else + cb(er) + }) } -const isString = subject => typeof subject === 'string' +const rmkids = (p, options, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') -// > A blank line matches no files, so it can serve as a separator for readability. -const checkPattern = pattern => pattern - && isString(pattern) - && !REGEX_TEST_BLANK_LINE.test(pattern) + options.readdir(p, (er, files) => { + if (er) + return cb(er) + let n = files.length + if (n === 0) + return options.rmdir(p, cb) + let errState + files.forEach(f => { + rimraf(path.join(p, f), options, er => { + if (errState) + return + if (er) + return cb(errState = er) + if (--n === 0) + options.rmdir(p, cb) + }) + }) + }) +} - // > A line starting with # serves as a comment. - && pattern.indexOf('#') !== 0 +// this looks simpler, and is strictly *faster*, but will +// tie up the JavaScript thread and fail on excessively +// deep directory trees. +const rimrafSync = (p, options) => { + options = options || {} + defaults(options) -const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert(options, 'rimraf: missing options') + assert.equal(typeof options, 'object', 'rimraf: options should be object') -class IgnoreRule { - constructor ( - origin, - pattern, - negative, - regex - ) { - this.origin = origin - this.pattern = pattern - this.negative = negative - this.regex = regex + let results + + if (options.disableGlob || !glob.hasMagic(p)) { + results = [p] + } else { + try { + options.lstatSync(p) + results = [p] + } catch (er) { + results = glob.sync(p, options.glob) + } } -} -const createRule = (pattern, ignorecase) => { - const origin = pattern - let negative = false + if (!results.length) + return - // > An optional prefix "!" which negates the pattern; - if (pattern.indexOf('!') === 0) { - negative = true - pattern = pattern.substr(1) - } + for (let i = 0; i < results.length; i++) { + const p = results[i] - pattern = pattern - // > Put a backslash ("\") in front of the first "!" for patterns that - // > begin with a literal "!", for example, `"\!important!.txt"`. - .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') - // > Put a backslash ("\") in front of the first hash for patterns that - // > begin with a hash. - .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') + let st + try { + st = options.lstatSync(p) + } catch (er) { + if (er.code === "ENOENT") + return - const regex = makeRegex(pattern, negative, ignorecase) + // Windows can EPERM on stat. Life is suffering. + if (er.code === "EPERM" && isWindows) + fixWinEPERMSync(p, options, er) + } - return new IgnoreRule( - origin, - pattern, - negative, - regex - ) -} + try { + // sunos lets the root user unlink directories, which is... weird. + if (st && st.isDirectory()) + rmdirSync(p, options, null) + else + options.unlinkSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "EPERM") + return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) + if (er.code !== "EISDIR") + throw er -const throwError = (message, Ctor) => { - throw new Ctor(message) + rmdirSync(p, options, er) + } + } } -const checkPath = (path, originalPath, doThrow) => { - if (!isString(path)) { - return doThrow( - `path must be a string, but got \`${originalPath}\``, - TypeError - ) - } +const rmdirSync = (p, options, originalEr) => { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) - // We don't know if we should ignore '', so throw - if (!path) { - return doThrow(`path must not be empty`, TypeError) + try { + options.rmdirSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "ENOTDIR") + throw originalEr + if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") + rmkidsSync(p, options) } +} - // Check if it is a relative path - if (checkPath.isNotRelative(path)) { - const r = '`path.relative()`d' - return doThrow( - `path should be a ${r} string, but got "${originalPath}"`, - RangeError - ) - } +const rmkidsSync = (p, options) => { + assert(p) + assert(options) + options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) - return true + // We only end up here once we got ENOTEMPTY at least once, and + // at this point, we are guaranteed to have removed all the kids. + // So, we know that it won't be ENOENT or ENOTDIR or anything else. + // try really hard to delete stuff on windows, because it has a + // PROFOUNDLY annoying habit of not closing handles promptly when + // files are deleted, resulting in spurious ENOTEMPTY errors. + const retries = isWindows ? 100 : 1 + let i = 0 + do { + let threw = true + try { + const ret = options.rmdirSync(p, options) + threw = false + return ret + } finally { + if (++i < retries && threw) + continue + } + } while (true) } -const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) +module.exports = rimraf +rimraf.sync = rimrafSync -checkPath.isNotRelative = isNotRelative -checkPath.convert = p => p -class Ignore { - constructor ({ - ignorecase = true - } = {}) { - this._rules = [] - this._ignorecase = ignorecase - define(this, KEY_IGNORE, true) - this._initCache() - } +/***/ }), +/* 672 */ +/***/ (function(module, exports, __webpack_require__) { - _initCache () { - this._ignoreCache = Object.create(null) - this._testCache = Object.create(null) - } +"use strict"; - _addPattern (pattern) { - // #32 - if (pattern && pattern[KEY_IGNORE]) { - this._rules = this._rules.concat(pattern._rules) - this._added = true - return - } +const AggregateError = __webpack_require__(673); - if (checkPattern(pattern)) { - const rule = createRule(pattern, this._ignorecase) - this._added = true - this._rules.push(rule) - } - } +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } - // @param {Array<string> | string | Ignore} pattern - add (pattern) { - this._added = false + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } - makeArray( - isString(pattern) - ? splitPattern(pattern) - : pattern - ).forEach(this._addPattern, this) + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; - // Some rules have just added to the ignore, - // making the behavior changed. - if (this._added) { - this._initCache() - } + const next = () => { + if (isRejected) { + return; + } - return this - } + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; - // legacy - addPattern (pattern) { - return this.add(pattern) - } + if (nextItem.done) { + isIterableDone = true; - // | ignored : unignored - // negative | 0:0 | 0:1 | 1:0 | 1:1 - // -------- | ------- | ------- | ------- | -------- - // 0 | TEST | TEST | SKIP | X - // 1 | TESTIF | SKIP | TEST | X + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } + } - // - SKIP: always skip - // - TEST: always test - // - TESTIF: only test if checkUnignored - // - X: that never happen + return; + } - // @param {boolean} whether should check if the path is unignored, - // setting `checkUnignored` to `false` could reduce additional - // path matching. + resolvingCount++; - // @returns {TestResult} true if a file is ignored - _testOne (path, checkUnignored) { - let ignored = false - let unignored = false + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); + resolvingCount--; + next(); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } + } + })(); + }; - this._rules.forEach(rule => { - const {negative} = rule - if ( - unignored === negative && ignored !== unignored - || negative && !ignored && !unignored && !checkUnignored - ) { - return - } + for (let i = 0; i < concurrency; i++) { + next(); - const matched = rule.regex.test(path) + if (isIterableDone) { + break; + } + } + }); +}; - if (matched) { - ignored = !negative - unignored = negative - } - }) - return { - ignored, - unignored - } - } +/***/ }), +/* 673 */ +/***/ (function(module, exports, __webpack_require__) { - // @returns {TestResult} - _test (originalPath, cache, checkUnignored, slices) { - const path = originalPath - // Supports nullable path - && checkPath.convert(originalPath) +"use strict"; - checkPath(path, originalPath, throwError) +const indentString = __webpack_require__(674); +const cleanStack = __webpack_require__(675); - return this._t(path, cache, checkUnignored, slices) - } +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); - _t (path, cache, checkUnignored, slices) { - if (path in cache) { - return cache[path] - } +class AggregateError extends Error { + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } - if (!slices) { - // path/to/a.js - // ['path', 'to', 'a.js'] - slices = path.split(SLASH) - } + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } - slices.pop() + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } - // If the path has no parent directory, just test it - if (!slices.length) { - return cache[path] = this._testOne(path, checkUnignored) - } + return new Error(error); + }); - const parent = this._t( - slices.join(SLASH) + SLASH, - cache, - checkUnignored, - slices - ) + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); - // If the path contains a parent directory, check the parent first - return cache[path] = parent.ignored - // > It is not possible to re-include a file if a parent directory of - // > that file is excluded. - ? parent - : this._testOne(path, checkUnignored) - } + this.name = 'AggregateError'; - ignores (path) { - return this._test(path, this._ignoreCache, false).ignored - } + Object.defineProperty(this, '_errors', {value: errors}); + } - createFilter () { - return path => !this.ignores(path) - } + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; + } + } +} - filter (paths) { - return makeArray(paths).filter(this.createFilter()) - } +module.exports = AggregateError; - // @returns {TestResult} - test (path) { - return this._test(path, this._testCache, true) - } -} -const factory = options => new Ignore(options) +/***/ }), +/* 674 */ +/***/ (function(module, exports, __webpack_require__) { -const returnFalse = () => false +"use strict"; -const isPathValid = path => - checkPath(path && checkPath.convert(path), path, returnFalse) -factory.isPathValid = isPathValid +module.exports = (string, count = 1, options) => { + options = { + indent: ' ', + includeEmptyLines: false, + ...options + }; -// Fixes typescript -factory.default = factory + if (typeof string !== 'string') { + throw new TypeError( + `Expected \`input\` to be a \`string\`, got \`${typeof string}\`` + ); + } -module.exports = factory + if (typeof count !== 'number') { + throw new TypeError( + `Expected \`count\` to be a \`number\`, got \`${typeof count}\`` + ); + } -// Windows -// -------------------------------------------------------------- -/* istanbul ignore if */ -if ( - // Detect `process` so that it can run in browsers. - typeof process !== 'undefined' - && ( - process.env && process.env.IGNORE_TEST_WIN32 - || process.platform === 'win32' - ) -) { - /* eslint no-control-regex: "off" */ - const makePosix = str => /^\\\\\?\\/.test(str) - || /["<>|\u0000-\u001F]+/u.test(str) - ? str - : str.replace(/\\/g, '/') + if (typeof options.indent !== 'string') { + throw new TypeError( + `Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\`` + ); + } - checkPath.convert = makePosix + if (count === 0) { + return string; + } - // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' - // 'd:\\foo' - const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i - checkPath.isNotRelative = path => - REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) - || isNotRelative(path) -} + const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + + return string.replace(regex, options.indent.repeat(count)); +}; /***/ }), -/* 662 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = path => { - const isExtendedLengthPath = /^\\\\\?\\/.test(path); - const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex +const os = __webpack_require__(11); - if (isExtendedLengthPath || hasNonAscii) { - return path; - } +const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; +const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; +const homeDir = typeof os.homedir === 'undefined' ? '' : os.homedir(); + +module.exports = (stack, options) => { + options = Object.assign({pretty: false}, options); + + return stack.replace(/\\/g, '/') + .split('\n') + .filter(line => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + + const match = pathMatches[1]; + + // Electron + if ( + match.includes('.app/Contents/Resources/electron.asar') || + match.includes('.app/Contents/Resources/default_app.asar') + ) { + return false; + } + + return !pathRegex.test(match); + }) + .filter(line => line.trim() !== '') + .map(line => { + if (options.pretty) { + return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); + } - return path.replace(/\\/g, '/'); + return line; + }) + .join('\n'); }; /***/ }), -/* 663 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {Transform} = __webpack_require__(27); +const chalk = __webpack_require__(677); +const cliCursor = __webpack_require__(681); +const cliSpinners = __webpack_require__(685); +const logSymbols = __webpack_require__(566); -class ObjectTransform extends Transform { - constructor() { - super({ - objectMode: true - }); - } -} +class Ora { + constructor(options) { + if (typeof options === 'string') { + options = { + text: options + }; + } -class FilterStream extends ObjectTransform { - constructor(filter) { - super(); - this._filter = filter; - } + this.options = Object.assign({ + text: '', + color: 'cyan', + stream: process.stderr + }, options); - _transform(data, encoding, callback) { - if (this._filter(data)) { - this.push(data); - } + const sp = this.options.spinner; + this.spinner = typeof sp === 'object' ? sp : (process.platform === 'win32' ? cliSpinners.line : (cliSpinners[sp] || cliSpinners.dots)); // eslint-disable-line no-nested-ternary - callback(); - } -} + if (this.spinner.frames === undefined) { + throw new Error('Spinner must define `frames`'); + } -class UniqueStream extends ObjectTransform { - constructor() { - super(); - this._pushed = new Set(); + this.text = this.options.text; + this.color = this.options.color; + this.interval = this.options.interval || this.spinner.interval || 100; + this.stream = this.options.stream; + this.id = null; + this.frameIndex = 0; + this.enabled = typeof this.options.enabled === 'boolean' ? this.options.enabled : ((this.stream && this.stream.isTTY) && !process.env.CI); } + frame() { + const frames = this.spinner.frames; + let frame = frames[this.frameIndex]; - _transform(data, encoding, callback) { - if (!this._pushed.has(data)) { - this.push(data); - this._pushed.add(data); + if (this.color) { + frame = chalk[this.color](frame); } - callback(); - } -} + this.frameIndex = ++this.frameIndex % frames.length; -module.exports = { - FilterStream, - UniqueStream -}; + return frame + ' ' + this.text; + } + clear() { + if (!this.enabled) { + return this; + } + this.stream.clearLine(); + this.stream.cursorTo(0); -/***/ }), -/* 664 */ -/***/ (function(module, exports, __webpack_require__) { + return this; + } + render() { + this.clear(); + this.stream.write(this.frame()); -var fs = __webpack_require__(23) -var polyfills = __webpack_require__(665) -var legacy = __webpack_require__(666) -var clone = __webpack_require__(667) + return this; + } + start(text) { + if (text) { + this.text = text; + } -var util = __webpack_require__(29) + if (!this.enabled || this.id) { + return this; + } -/* istanbul ignore next - node 0.x polyfill */ -var gracefulQueue -var previousSymbol + cliCursor.hide(this.stream); + this.render(); + this.id = setInterval(this.render.bind(this), this.interval); -/* istanbul ignore else - node 0.x polyfill */ -if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { - gracefulQueue = Symbol.for('graceful-fs.queue') - // This is used in testing by future versions - previousSymbol = Symbol.for('graceful-fs.previous') -} else { - gracefulQueue = '___graceful-fs.queue' - previousSymbol = '___graceful-fs.previous' -} + return this; + } + stop() { + if (!this.enabled) { + return this; + } -function noop () {} + clearInterval(this.id); + this.id = null; + this.frameIndex = 0; + this.clear(); + cliCursor.show(this.stream); -var debug = noop -if (util.debuglog) - debug = util.debuglog('gfs4') -else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) - debug = function() { - var m = util.format.apply(util, arguments) - m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') - console.error(m) - } + return this; + } + succeed(text) { + return this.stopAndPersist({symbol: logSymbols.success, text}); + } + fail(text) { + return this.stopAndPersist({symbol: logSymbols.error, text}); + } + warn(text) { + return this.stopAndPersist({symbol: logSymbols.warning, text}); + } + info(text) { + return this.stopAndPersist({symbol: logSymbols.info, text}); + } + stopAndPersist(options) { + if (!this.enabled) { + return this; + } -// Once time initialization -if (!global[gracefulQueue]) { - // This queue can be shared by multiple loaded instances - var queue = [] - Object.defineProperty(global, gracefulQueue, { - get: function() { - return queue - } - }) + // Legacy argument + // TODO: Deprecate sometime in the future + if (typeof options === 'string') { + options = { + symbol: options + }; + } - // Patch fs.close/closeSync to shared queue version, because we need - // to retry() whenever a close happens *anywhere* in the program. - // This is essential when multiple graceful-fs instances are - // in play at the same time. - fs.close = (function (fs$close) { - function close (fd, cb) { - return fs$close.call(fs, fd, function (err) { - // This function uses the graceful-fs shared queue - if (!err) { - retry() - } + options = options || {}; - if (typeof cb === 'function') - cb.apply(this, arguments) - }) - } + this.stop(); + this.stream.write(`${options.symbol || ' '} ${options.text || this.text}\n`); - Object.defineProperty(close, previousSymbol, { - value: fs$close - }) - return close - })(fs.close) + return this; + } +} - fs.closeSync = (function (fs$closeSync) { - function closeSync (fd) { - // This function uses the graceful-fs shared queue - fs$closeSync.apply(fs, arguments) - retry() - } +module.exports = function (opts) { + return new Ora(opts); +}; - Object.defineProperty(closeSync, previousSymbol, { - value: fs$closeSync - }) - return closeSync - })(fs.closeSync) +module.exports.promise = (action, options) => { + if (typeof action.then !== 'function') { + throw new TypeError('Parameter `action` must be a Promise'); + } - if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(global[gracefulQueue]) - __webpack_require__(30).equal(global[gracefulQueue].length, 0) - }) - } -} + const spinner = new Ora(options); + spinner.start(); -module.exports = patch(clone(fs)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { - module.exports = patch(fs) - fs.__patched = true; -} + action.then( + () => { + spinner.succeed(); + }, + () => { + spinner.fail(); + } + ); -function patch (fs) { - // Everything that references the open() function needs to be in here - polyfills(fs) - fs.gracefulify = patch + return spinner; +}; - fs.createReadStream = createReadStream - fs.createWriteStream = createWriteStream - var fs$readFile = fs.readFile - fs.readFile = readFile - function readFile (path, options, cb) { - if (typeof options === 'function') - cb = options, options = null - return go$readFile(path, options, cb) +/***/ }), +/* 677 */ +/***/ (function(module, exports, __webpack_require__) { - function go$readFile (path, options, cb) { - return fs$readFile(path, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readFile, [path, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +"use strict"; - var fs$writeFile = fs.writeFile - fs.writeFile = writeFile - function writeFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null +const escapeStringRegexp = __webpack_require__(3); +const ansiStyles = __webpack_require__(678); +const stdoutColor = __webpack_require__(679).stdout; - return go$writeFile(path, data, options, cb) +const template = __webpack_require__(680); - function go$writeFile (path, data, options, cb) { - return fs$writeFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$writeFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); - var fs$appendFile = fs.appendFile - if (fs$appendFile) - fs.appendFile = appendFile - function appendFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - return go$appendFile(path, data, options, cb) +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); - function go$appendFile (path, data, options, cb) { - return fs$appendFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$appendFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +const styles = Object.create(null); - var fs$readdir = fs.readdir - fs.readdir = readdir - function readdir (path, options, cb) { - var args = [path] - if (typeof options !== 'function') { - args.push(options) - } else { - cb = options - } - args.push(go$readdir$cb) +function applyOptions(obj, options) { + options = options || {}; - return go$readdir(args) + // Detect level if not set manually + const scLevel = stdoutColor ? stdoutColor.level : 0; + obj.level = options.level === undefined ? scLevel : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; +} - function go$readdir$cb (err, files) { - if (files && files.sort) - files.sort() +function Chalk(options) { + // We check for this.template here since calling `chalk.constructor()` + // by itself will have a `this` of a previously constructed chalk object + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readdir, [args]]) + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - } - } + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); - function go$readdir (args) { - return fs$readdir.apply(fs, args) - } + chalk.template.constructor = Chalk; - if (process.version.substr(0, 4) === 'v0.8') { - var legStreams = legacy(fs) - ReadStream = legStreams.ReadStream - WriteStream = legStreams.WriteStream - } + return chalk.template; + } - var fs$ReadStream = fs.ReadStream - if (fs$ReadStream) { - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open - } + applyOptions(this, options); +} - var fs$WriteStream = fs.WriteStream - if (fs$WriteStream) { - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open - } +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} - Object.defineProperty(fs, 'ReadStream', { - get: function () { - return ReadStream - }, - set: function (val) { - ReadStream = val - }, - enumerable: true, - configurable: true - }) - Object.defineProperty(fs, 'WriteStream', { - get: function () { - return WriteStream - }, - set: function (val) { - WriteStream = val - }, - enumerable: true, - configurable: true - }) +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - // legacy names - Object.defineProperty(fs, 'FileReadStream', { - get: function () { - return ReadStream - }, - set: function (val) { - ReadStream = val - }, - enumerable: true, - configurable: true - }) - Object.defineProperty(fs, 'FileWriteStream', { - get: function () { - return WriteStream - }, - set: function (val) { - WriteStream = val - }, - enumerable: true, - configurable: true - }) + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + } + }; +} - function ReadStream (path, options) { - if (this instanceof ReadStream) - return fs$ReadStream.apply(this, arguments), this - else - return ReadStream.apply(Object.create(ReadStream.prototype), arguments) - } +styles.visible = { + get() { + return build.call(this, this._styles || [], true, 'visible'); + } +}; - function ReadStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - if (that.autoClose) - that.destroy() +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - that.read() - } - }) - } + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} - function WriteStream (path, options) { - if (this instanceof WriteStream) - return fs$WriteStream.apply(this, arguments), this - else - return WriteStream.apply(Object.create(WriteStream.prototype), arguments) - } +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } - function WriteStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - that.destroy() - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - } - }) - } + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} - function createReadStream (path, options) { - return new fs.ReadStream(path, options) - } +const proto = Object.defineProperties(() => {}, styles); - function createWriteStream (path, options) { - return new fs.WriteStream(path, options) - } +function build(_styles, _empty, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; - var fs$open = fs.open - fs.open = open - function open (path, flags, mode, cb) { - if (typeof mode === 'function') - cb = mode, mode = null + builder._styles = _styles; + builder._empty = _empty; - return go$open(path, flags, mode, cb) + const self = this; - function go$open (path, flags, mode, cb) { - return fs$open(path, flags, mode, function (err, fd) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$open, [path, flags, mode, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); - return fs -} + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); -function enqueue (elem) { - debug('ENQUEUE', elem[0].name, elem[1]) - global[gracefulQueue].push(elem) -} + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; -function retry () { - var elem = global[gracefulQueue].shift() - if (elem) { - debug('RETRY', elem[0].name, elem[1]) - elem[0].apply(null, elem[1]) - } + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype + builder.__proto__ = proto; // eslint-disable-line no-proto + + return builder; } +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = String(arguments[0]); -/***/ }), -/* 665 */ -/***/ (function(module, exports, __webpack_require__) { + if (argsLen === 0) { + return ''; + } -var constants = __webpack_require__(25) + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } -var origCwd = process.cwd -var cwd = null + if (!this.enabled || this.level <= 0 || !str) { + return this._empty ? '' : str; + } -var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } -process.cwd = function() { - if (!cwd) - cwd = origCwd.call(process) - return cwd -} -try { - process.cwd() -} catch (er) {} + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; -var chdir = process.chdir -process.chdir = function(d) { - cwd = null - chdir.call(process, d) -} + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } -module.exports = patch + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; -function patch (fs) { - // (re-)implement some things that are known busted or missing. + return str; +} - // lchmod, broken prior to 0.6.2 - // back-port the fix here. - if (constants.hasOwnProperty('O_SYMLINK') && - process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { - patchLchmod(fs) - } +function chalkTag(chalk, strings) { + if (!Array.isArray(strings)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return [].slice.call(arguments, 1).join(' '); + } - // lutimes implementation, or no-op - if (!fs.lutimes) { - patchLutimes(fs) - } + const args = [].slice.call(arguments, 2); + const parts = [strings.raw[0]]; - // https://github.com/isaacs/node-graceful-fs/issues/4 - // Chown should not fail on einval or eperm if non-root. - // It should not fail on enosys ever, as this just indicates - // that a fs doesn't support the intended operation. + for (let i = 1; i < strings.length; i++) { + parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); + parts.push(String(strings.raw[i])); + } - fs.chown = chownFix(fs.chown) - fs.fchown = chownFix(fs.fchown) - fs.lchown = chownFix(fs.lchown) + return template(chalk, parts.join('')); +} - fs.chmod = chmodFix(fs.chmod) - fs.fchmod = chmodFix(fs.fchmod) - fs.lchmod = chmodFix(fs.lchmod) +Object.defineProperties(Chalk.prototype, styles); - fs.chownSync = chownFixSync(fs.chownSync) - fs.fchownSync = chownFixSync(fs.fchownSync) - fs.lchownSync = chownFixSync(fs.lchownSync) +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = stdoutColor; +module.exports.default = module.exports; // For TypeScript - fs.chmodSync = chmodFixSync(fs.chmodSync) - fs.fchmodSync = chmodFixSync(fs.fchmodSync) - fs.lchmodSync = chmodFixSync(fs.lchmodSync) - fs.stat = statFix(fs.stat) - fs.fstat = statFix(fs.fstat) - fs.lstat = statFix(fs.lstat) +/***/ }), +/* 678 */ +/***/ (function(module, exports, __webpack_require__) { - fs.statSync = statFixSync(fs.statSync) - fs.fstatSync = statFixSync(fs.fstatSync) - fs.lstatSync = statFixSync(fs.lstatSync) +"use strict"; +/* WEBPACK VAR INJECTION */(function(module) { +const colorConvert = __webpack_require__(6); - // if lchmod/lchown do not exist, then make them no-ops - if (!fs.lchmod) { - fs.lchmod = function (path, mode, cb) { - if (cb) process.nextTick(cb) - } - fs.lchmodSync = function () {} - } - if (!fs.lchown) { - fs.lchown = function (path, uid, gid, cb) { - if (cb) process.nextTick(cb) - } - fs.lchownSync = function () {} - } +const wrapAnsi16 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${code + offset}m`; +}; - // on Windows, A/V software can lock the directory, causing this - // to fail with an EACCES or EPERM if the directory contains newly - // created files. Try again on failure, for up to 60 seconds. +const wrapAnsi256 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};5;${code}m`; +}; - // Set the timeout this long because some Windows Anti-Virus, such as Parity - // bit9, may lock files for up to a minute, causing npm package install - // failures. Also, take care to yield the scheduler. Windows scheduling gives - // CPU to a busy looping process, which can cause the program causing the lock - // contention to be starved of CPU by node, so the contention doesn't resolve. - if (platform === "win32") { - fs.rename = (function (fs$rename) { return function (from, to, cb) { - var start = Date.now() - var backoff = 0; - fs$rename(from, to, function CB (er) { - if (er - && (er.code === "EACCES" || er.code === "EPERM") - && Date.now() - start < 60000) { - setTimeout(function() { - fs.stat(to, function (stater, st) { - if (stater && stater.code === "ENOENT") - fs$rename(from, to, CB); - else - cb(er) - }) - }, backoff) - if (backoff < 100) - backoff += 10; - return; - } - if (cb) cb(er) - }) - }})(fs.rename) - } +const wrapAnsi16m = (fn, offset) => function () { + const rgb = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; +}; - // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { - function read (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - callback_.apply(this, arguments) - } - } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } +function assembleStyles() { + const codes = new Map(); + const styles = { + modifier: { + reset: [0, 0], + // 21 isn't widely supported and 22 does the same thing + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29] + }, + color: { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + gray: [90, 39], - // This ensures `util.promisify` works as it does for native `fs.read`. - read.__proto__ = fs$read - return read - })(fs.read) + // Bright color + redBright: [91, 39], + greenBright: [92, 39], + yellowBright: [93, 39], + blueBright: [94, 39], + magentaBright: [95, 39], + cyanBright: [96, 39], + whiteBright: [97, 39] + }, + bgColor: { + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], - fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { - var eagCounter = 0 - while (true) { - try { - return fs$readSync.call(fs, fd, buffer, offset, length, position) - } catch (er) { - if (er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - continue - } - throw er - } - } - }})(fs.readSync) + // Bright color + bgBlackBright: [100, 49], + bgRedBright: [101, 49], + bgGreenBright: [102, 49], + bgYellowBright: [103, 49], + bgBlueBright: [104, 49], + bgMagentaBright: [105, 49], + bgCyanBright: [106, 49], + bgWhiteBright: [107, 49] + } + }; - function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } + // Fix humans + styles.color.grey = styles.color.gray; - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + for (const groupName of Object.keys(styles)) { + const group = styles[groupName]; - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } - } + for (const styleName of Object.keys(group)) { + const style = group[styleName]; - function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) - return - } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) - }) - }) - }) - } + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m` + }; - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret - var threw = true - try { - ret = fs.futimesSync(fd, at, mt) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } + group[styleName] = styles[styleName]; - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} - } - } + codes.set(style[0], style[1]); + } - function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } - } + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); - function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er - } - } - } + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false + }); + } + const ansi2ansi = n => n; + const rgb2rgb = (r, g, b) => [r, g, b]; - function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } - } + styles.color.close = '\u001B[39m'; + styles.bgColor.close = '\u001B[49m'; - function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er - } - } - } + styles.color.ansi = { + ansi: wrapAnsi16(ansi2ansi, 0) + }; + styles.color.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 0) + }; + styles.color.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 0) + }; - function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options, cb) { - if (typeof options === 'function') { - cb = options - options = null - } - function callback (er, stats) { - if (stats) { - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - } - if (cb) cb.apply(this, arguments) - } - return options ? orig.call(fs, target, options, callback) - : orig.call(fs, target, callback) - } - } + styles.bgColor.ansi = { + ansi: wrapAnsi16(ansi2ansi, 10) + }; + styles.bgColor.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 10) + }; + styles.bgColor.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 10) + }; - function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options) { - var stats = options ? orig.call(fs, target, options) - : orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } - } + for (let key of Object.keys(colorConvert)) { + if (typeof colorConvert[key] !== 'object') { + continue; + } - // ENOSYS means that the fs doesn't support the op. Just ignore - // that, because it doesn't matter. - // - // if there's no getuid, or if getuid() is something other - // than 0, and the error is EINVAL or EPERM, then just ignore - // it. - // - // This specific case is a silent failure in cp, install, tar, - // and most other unix tools that manage permissions. - // - // When running as root, or if other types of errors are - // encountered, then it's strict. - function chownErOk (er) { - if (!er) - return true + const suite = colorConvert[key]; - if (er.code === "ENOSYS") - return true + if (key === 'ansi16') { + key = 'ansi'; + } - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true - } + if ('ansi16' in suite) { + styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); + styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); + } - return false - } + if ('ansi256' in suite) { + styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); + styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); + } + + if ('rgb' in suite) { + styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); + styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); + } + } + + return styles; } +// Make the export immutable +Object.defineProperty(module, 'exports', { + enumerable: true, + get: assembleStyles +}); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 666 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(27).Stream - -module.exports = legacy - -function legacy (fs) { - return { - ReadStream: ReadStream, - WriteStream: WriteStream - } +"use strict"; - function ReadStream (path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); +const os = __webpack_require__(11); +const hasFlag = __webpack_require__(12); - Stream.call(this); +const env = process.env; - var self = this; +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + forceColor = false; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = true; +} +if ('FORCE_COLOR' in env) { + forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +} - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; +function translateLevel(level) { + if (level === 0) { + return false; + } - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} - options = options || {}; +function supportsColor(stream) { + if (forceColor === false) { + return 0; + } - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } - if (this.encoding) this.setEncoding(this.encoding); + if (hasFlag('color=256')) { + return 2; + } - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.end === undefined) { - this.end = Infinity; - } else if ('number' !== typeof this.end) { - throw TypeError('end must be a Number'); - } + if (stream && !stream.isTTY && forceColor !== true) { + // VS code debugger doesn't have isTTY set + if (env.VSCODE_PID) { + return 1; + } + return 0; + } - if (this.start > this.end) { - throw new Error('start must be <= end'); - } + const min = forceColor ? 1 : 0; - this.pos = this.start; - } + if (process.platform === 'win32') { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. Windows 10 build 14931 is the first release + // that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(process.versions.node.split('.')[0]) >= 8 && + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } + return 1; + } - fs.open(this.path, this.flags, this.mode, function (err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; - return; - } + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } - self.fd = fd; - self.emit('open', fd); - self._read(); - }) - } + return min; + } - function WriteStream (path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } - Stream.call(this); + if (env.COLORTERM === 'truecolor') { + return 3; + } - this.path = path; - this.fd = null; - this.writable = true; + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } - options = options || {}; + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } + if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.start < 0) { - throw new Error('start must be >= zero'); - } + if ('COLORTERM' in env) { + return 1; + } - this.pos = this.start; - } + if (env.TERM === 'dumb') { + return min; + } - this.busy = false; - this._queue = []; + return min; +} - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); - } - } +function getSupportLevel(stream) { + const level = supportsColor(stream); + return translateLevel(level); } +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; + /***/ }), -/* 667 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; +const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; +const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; -module.exports = clone +const ESCAPES = new Map([ + ['n', '\n'], + ['r', '\r'], + ['t', '\t'], + ['b', '\b'], + ['f', '\f'], + ['v', '\v'], + ['0', '\0'], + ['\\', '\\'], + ['e', '\u001B'], + ['a', '\u0007'] +]); -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj +function unescape(c) { + if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) + return ESCAPES.get(c) || c; +} - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) +function parseArguments(name, args) { + const results = []; + const chunks = args.trim().split(/\s*,\s*/g); + let matches; - return copy + for (const chunk of chunks) { + if (!isNaN(chunk)) { + results.push(Number(chunk)); + } else if ((matches = chunk.match(STRING_REGEX))) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + + return results; } +function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; -/***/ }), -/* 668 */ -/***/ (function(module, exports, __webpack_require__) { + const results = []; + let matches; -"use strict"; + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; -const path = __webpack_require__(16); + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } -module.exports = path_ => { - let cwd = process.cwd(); + return results; +} - path_ = path.resolve(path_); +function buildStyle(chalk, styles) { + const enabled = {}; - if (process.platform === 'win32') { - cwd = cwd.toLowerCase(); - path_ = path_.toLowerCase(); + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(1); + } } - return path_ === cwd; + let current = chalk; + for (const styleName of Object.keys(enabled)) { + if (Array.isArray(enabled[styleName])) { + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + if (enabled[styleName].length > 0) { + current = current[styleName].apply(current, enabled[styleName]); + } else { + current = current[styleName]; + } + } + } + + return current; +} + +module.exports = (chalk, tmp) => { + const styles = []; + const chunks = []; + let chunk = []; + + // eslint-disable-next-line max-params + tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { + if (escapeChar) { + chunk.push(unescape(escapeChar)); + } else if (style) { + const str = chunk.join(''); + chunk = []; + chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); + styles.push({inverse, styles: parseStyle(style)}); + } else if (close) { + if (styles.length === 0) { + throw new Error('Found extraneous } in Chalk template literal'); + } + + chunks.push(buildStyle(chalk, styles)(chunk.join(''))); + chunk = []; + styles.pop(); + } else { + chunk.push(chr); + } + }); + + chunks.push(chunk.join('')); + + if (styles.length > 0) { + const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; + throw new Error(errMsg); + } + + return chunks.join(''); }; /***/ }), -/* 669 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const path = __webpack_require__(16); +const restoreCursor = __webpack_require__(682); -module.exports = (childPath, parentPath) => { - childPath = path.resolve(childPath); - parentPath = path.resolve(parentPath); +let hidden = false; - if (process.platform === 'win32') { - childPath = childPath.toLowerCase(); - parentPath = parentPath.toLowerCase(); +exports.show = stream => { + const s = stream || process.stderr; + + if (!s.isTTY) { + return; } - if (childPath === parentPath) { - return false; + hidden = false; + s.write('\u001b[?25h'); +}; + +exports.hide = stream => { + const s = stream || process.stderr; + + if (!s.isTTY) { + return; } - childPath += path.sep; - parentPath += path.sep; + restoreCursor(); + hidden = true; + s.write('\u001b[?25l'); +}; - return childPath.startsWith(parentPath); +exports.toggle = (force, stream) => { + if (force !== undefined) { + hidden = force; + } + + if (hidden) { + exports.show(stream); + } else { + exports.hide(stream); + } }; /***/ }), -/* 670 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { -const assert = __webpack_require__(30) -const path = __webpack_require__(16) -const fs = __webpack_require__(23) -let glob = undefined -try { - glob = __webpack_require__(502) -} catch (_err) { - // treat glob as optional. -} +"use strict"; -const defaultGlobOpts = { - nosort: true, - silent: true -} +const onetime = __webpack_require__(683); +const signalExit = __webpack_require__(377); -// for EMFILE handling -let timeout = 0 +module.exports = onetime(() => { + signalExit(() => { + process.stderr.write('\u001b[?25h'); + }, {alwaysLast: true}); +}); -const isWindows = (process.platform === "win32") -const defaults = options => { - const methods = [ - 'unlink', - 'chmod', - 'stat', - 'lstat', - 'rmdir', - 'readdir' - ] - methods.forEach(m => { - options[m] = options[m] || fs[m] - m = m + 'Sync' - options[m] = options[m] || fs[m] - }) +/***/ }), +/* 683 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const mimicFn = __webpack_require__(684); + +module.exports = (fn, opts) => { + // TODO: Remove this in v3 + if (opts === true) { + throw new TypeError('The second argument is now an options object'); + } + + if (typeof fn !== 'function') { + throw new TypeError('Expected a function'); + } + + opts = opts || {}; + + let ret; + let called = false; + const fnName = fn.displayName || fn.name || '<anonymous>'; - options.maxBusyTries = options.maxBusyTries || 3 - options.emfileWait = options.emfileWait || 1000 - if (options.glob === false) { - options.disableGlob = true - } - if (options.disableGlob !== true && glob === undefined) { - throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') - } - options.disableGlob = options.disableGlob || false - options.glob = options.glob || defaultGlobOpts -} + const onetime = function () { + if (called) { + if (opts.throw === true) { + throw new Error(`Function \`${fnName}\` can only be called once`); + } -const rimraf = (p, options, cb) => { - if (typeof options === 'function') { - cb = options - options = {} - } + return ret; + } - assert(p, 'rimraf: missing path') - assert.equal(typeof p, 'string', 'rimraf: path should be a string') - assert.equal(typeof cb, 'function', 'rimraf: callback function required') - assert(options, 'rimraf: invalid options argument provided') - assert.equal(typeof options, 'object', 'rimraf: options should be object') + called = true; + ret = fn.apply(this, arguments); + fn = null; - defaults(options) + return ret; + }; - let busyTries = 0 - let errState = null - let n = 0 + mimicFn(onetime, fn); - const next = (er) => { - errState = errState || er - if (--n === 0) - cb(errState) - } + return onetime; +}; - const afterGlob = (er, results) => { - if (er) - return cb(er) - n = results.length - if (n === 0) - return cb() +/***/ }), +/* 684 */ +/***/ (function(module, exports, __webpack_require__) { - results.forEach(p => { - const CB = (er) => { - if (er) { - if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && - busyTries < options.maxBusyTries) { - busyTries ++ - // try again, with the same exact callback as this one. - return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) - } +"use strict"; - // this one won't happen if graceful-fs is used. - if (er.code === "EMFILE" && timeout < options.emfileWait) { - return setTimeout(() => rimraf_(p, options, CB), timeout ++) - } +module.exports = (to, from) => { + // TODO: use `Reflect.ownKeys()` when targeting Node.js 6 + for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } - // already gone - if (er.code === "ENOENT") er = null - } + return to; +}; - timeout = 0 - next(er) - } - rimraf_(p, options, CB) - }) - } - if (options.disableGlob || !glob.hasMagic(p)) - return afterGlob(null, [p]) +/***/ }), +/* 685 */ +/***/ (function(module, exports, __webpack_require__) { - options.lstat(p, (er, stat) => { - if (!er) - return afterGlob(null, [p]) +"use strict"; - glob(p, options.glob, afterGlob) - }) +module.exports = __webpack_require__(686); -} -// Two possible strategies. -// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR -// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR -// -// Both result in an extra syscall when you guess wrong. However, there -// are likely far more normal files in the world than directories. This -// is based on the assumption that a the average number of files per -// directory is >= 1. -// -// If anyone ever complains about this, then I guess the strategy could -// be made configurable somehow. But until then, YAGNI. -const rimraf_ = (p, options, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') +/***/ }), +/* 686 */ +/***/ (function(module) { - // sunos lets the root user unlink directories, which is... weird. - // so we have to lstat here and make sure it's not a dir. - options.lstat(p, (er, st) => { - if (er && er.code === "ENOENT") - return cb(null) +module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); - // Windows can EPERM on stat. Life is suffering. - if (er && er.code === "EPERM" && isWindows) - fixWinEPERM(p, options, er, cb) +/***/ }), +/* 687 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (st && st.isDirectory()) - return rmdir(p, options, er, cb) +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); +/* + * 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. + */ - options.unlink(p, er => { - if (er) { - if (er.code === "ENOENT") - return cb(null) - if (er.code === "EPERM") - return (isWindows) - ? fixWinEPERM(p, options, er, cb) - : rmdir(p, options, er, cb) - if (er.code === "EISDIR") - return rmdir(p, options, er, cb) - } - return cb(er) - }) - }) -} -const fixWinEPERM = (p, options, er, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') - if (er) - assert(er instanceof Error) - options.chmod(p, 0o666, er2 => { - if (er2) - cb(er2.code === "ENOENT" ? null : er) - else - options.stat(p, (er3, stats) => { - if (er3) - cb(er3.code === "ENOENT" ? null : er) - else if (stats.isDirectory()) - rmdir(p, options, er, cb) - else - options.unlink(p, cb) - }) - }) -} -const fixWinEPERMSync = (p, options, er) => { - assert(p) - assert(options) - if (er) - assert(er instanceof Error) +const RunCommand = { + description: 'Run script defined in package.json in each package that contains that script.', + name: 'run', - try { - options.chmodSync(p, 0o666) - } catch (er2) { - if (er2.code === "ENOENT") - return - else - throw er - } + async run(projects, projectGraph, { + extraArgs + }) { + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); - let stats - try { - stats = options.statSync(p) - } catch (er3) { - if (er3.code === "ENOENT") - return - else - throw er + if (extraArgs.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red.bold('\nNo script specified')); + process.exit(1); + } + + const scriptName = extraArgs[0]; + const scriptArgs = extraArgs.slice(1); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in batched topological order\n`)); + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { + if (pkg.hasScript(scriptName)) { + await pkg.runScriptStreaming(scriptName, scriptArgs); + } + }); } - if (stats.isDirectory()) - rmdirSync(p, options, er) - else - options.unlinkSync(p) -} +}; -const rmdir = (p, options, originalEr, cb) => { - assert(p) - assert(options) - if (originalEr) - assert(originalEr instanceof Error) - assert(typeof cb === 'function') +/***/ }), +/* 688 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) - // if we guessed wrong, and it's not a directory, then - // raise the original error. - options.rmdir(p, er => { - if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) - rmkids(p, options, cb) - else if (er && er.code === "ENOTDIR") - cb(originalEr) - else - cb(er) - }) -} +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(689); +/* + * 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. + */ -const rmkids = (p, options, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') - options.readdir(p, (er, files) => { - if (er) - return cb(er) - let n = files.length - if (n === 0) - return options.rmdir(p, cb) - let errState - files.forEach(f => { - rimraf(path.join(p, f), options, er => { - if (errState) - return - if (er) - return cb(errState = er) - if (--n === 0) - options.rmdir(p, cb) - }) - }) - }) -} -// this looks simpler, and is strictly *faster*, but will -// tie up the JavaScript thread and fail on excessively -// deep directory trees. -const rimrafSync = (p, options) => { - options = options || {} - defaults(options) - assert(p, 'rimraf: missing path') - assert.equal(typeof p, 'string', 'rimraf: path should be a string') - assert(options, 'rimraf: missing options') - assert.equal(typeof options, 'object', 'rimraf: options should be object') - let results - if (options.disableGlob || !glob.hasMagic(p)) { - results = [p] - } else { - try { - options.lstatSync(p) - results = [p] - } catch (er) { - results = glob.sync(p, options.glob) - } - } +/** + * Name of the script in the package/project package.json file to run during `kbn watch`. + */ +const watchScriptName = 'kbn:watch'; +/** + * Name of the Kibana project. + */ - if (!results.length) - return +const kibanaProjectName = 'kibana'; +/** + * Command that traverses through list of available projects/packages that have `kbn:watch` script in their + * package.json files, groups them into topology aware batches and then processes theses batches one by one + * running `kbn:watch` scripts in parallel within the same batch. + * + * Command internally relies on the fact that most of the build systems that are triggered by `kbn:watch` + * will emit special "marker" once build/watch process is ready that we can use as completion condition for + * the `kbn:watch` script and eventually for the entire batch. Currently we support completion "markers" for + * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. + */ - for (let i = 0; i < results.length; i++) { - const p = results[i] +const WatchCommand = { + description: 'Runs `kbn:watch` script for every project.', + name: 'watch', - let st - try { - st = options.lstatSync(p) - } catch (er) { - if (er.code === "ENOENT") - return + async run(projects, projectGraph) { + const projectsToWatch = new Map(); - // Windows can EPERM on stat. Life is suffering. - if (er.code === "EPERM" && isWindows) - fixWinEPERMSync(p, options, er) + for (const project of projects.values()) { + // We can't watch project that doesn't have `kbn:watch` script. + if (project.hasScript(watchScriptName)) { + projectsToWatch.set(project.name, project); + } } - try { - // sunos lets the root user unlink directories, which is... weird. - if (st && st.isDirectory()) - rmdirSync(p, options, null) - else - options.unlinkSync(p) - } catch (er) { - if (er.code === "ENOENT") - return - if (er.code === "EPERM") - return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) - if (er.code !== "EISDIR") - throw er - - rmdirSync(p, options, er) + if (projectsToWatch.size === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`\nThere are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.\n`)); + return; } - } -} - -const rmdirSync = (p, options, originalEr) => { - assert(p) - assert(options) - if (originalEr) - assert(originalEr instanceof Error) - try { - options.rmdirSync(p) - } catch (er) { - if (er.code === "ENOENT") - return - if (er.code === "ENOTDIR") - throw originalEr - if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") - rmkidsSync(p, options) - } -} + const projectNames = Array.from(projectsToWatch.keys()); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`))); // Kibana should always be run the last, so we don't rely on automatic + // topological batching and push it to the last one-entry batch manually. -const rmkidsSync = (p, options) => { - assert(p) - assert(options) - options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) + const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); - // We only end up here once we got ENOTEMPTY at least once, and - // at this point, we are guaranteed to have removed all the kids. - // So, we know that it won't be ENOENT or ENOTDIR or anything else. - // try really hard to delete stuff on windows, because it has a - // PROFOUNDLY annoying habit of not closing handles promptly when - // files are deleted, resulting in spurious ENOTEMPTY errors. - const retries = isWindows ? 100 : 1 - let i = 0 - do { - let threw = true - try { - const ret = options.rmdirSync(p, options) - threw = false - return ret - } finally { - if (++i < retries && threw) - continue + if (shouldWatchKibanaProject) { + batchedProjects.push([projects.get(kibanaProjectName)]); } - } while (true) -} -module.exports = rimraf -rimraf.sync = rimrafSync + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { + const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName).stdout); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`[${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(pkg.name)}] Initial build completed (${completionHint}).`)); + }); + } +}; /***/ }), -/* 671 */ -/***/ (function(module, exports, __webpack_require__) { +/* 689 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); +/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(392); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(169); +/* + * 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. + */ -const AggregateError = __webpack_require__(672); -module.exports = async ( - iterable, - mapper, - { - concurrency = Infinity, - stopOnError = true - } = {} -) => { - return new Promise((resolve, reject) => { - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } +/** + * Number of milliseconds we wait before we fall back to the default watch handler. + */ - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } +const defaultHandlerDelay = 3000; +/** + * If default watch handler is used, then it's the number of milliseconds we wait for + * any build output before we consider watch task ready. + */ - const ret = []; - const errors = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; +const defaultHandlerReadinessTimeout = 2000; +/** + * Describes configurable watch options. + */ + +function getWatchHandlers(buildOutput$, { + handlerDelay = defaultHandlerDelay, + handlerReadinessTimeout = defaultHandlerReadinessTimeout +}) { + const typescriptHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ tsc')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Compilation complete.')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('tsc')))); + const webpackHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ webpack')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Chunk Names')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('webpack')))); + const defaultHandler = rxjs__WEBPACK_IMPORTED_MODULE_0__["of"](undefined).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["delay"])(handlerReadinessTimeout), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["timeout"])(handlerDelay), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["catchError"])(() => rxjs__WEBPACK_IMPORTED_MODULE_0__["of"]('timeout'))))); + return [typescriptHandler, webpackHandler, defaultHandler]; +} - const next = () => { - if (isRejected) { - return; - } +function waitUntilWatchIsReady(stream, opts = {}) { + const buildOutput$ = new rxjs__WEBPACK_IMPORTED_MODULE_0__["Subject"](); - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + const onDataListener = data => buildOutput$.next(data.toString('utf-8')); - if (nextItem.done) { - isIterableDone = true; + const onEndListener = () => buildOutput$.complete(); - if (resolvingCount === 0) { - if (!stopOnError && errors.length !== 0) { - reject(new AggregateError(errors)); - } else { - resolve(ret); - } - } + const onErrorListener = e => buildOutput$.error(e); - return; - } + stream.once('end', onEndListener); + stream.once('error', onErrorListener); + stream.on('data', onDataListener); + return rxjs__WEBPACK_IMPORTED_MODULE_0__["race"](getWatchHandlers(buildOutput$, opts)).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mergeMap"])(whenReady => whenReady), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["finalize"])(() => { + stream.removeListener('data', onDataListener); + stream.removeListener('end', onEndListener); + stream.removeListener('error', onErrorListener); + buildOutput$.complete(); + })).toPromise(); +} - resolvingCount++; +/***/ }), +/* 690 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - (async () => { - try { - const element = await nextItem.value; - ret[i] = await mapper(element, i); - resolvingCount--; - next(); - } catch (error) { - if (stopOnError) { - isRejected = true; - reject(error); - } else { - errors.push(error); - resolvingCount--; - next(); - } - } - })(); - }; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(691); +/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(indent_string__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(692); +/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(wrap_ansi__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(515); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(34); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(501); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(699); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(700); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } - for (let i = 0; i < concurrency; i++) { - next(); +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - if (isIterableDone) { - break; - } - } - }); -}; +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +/* + * 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. + */ -/***/ }), -/* 672 */ -/***/ (function(module, exports, __webpack_require__) { -"use strict"; -const indentString = __webpack_require__(673); -const cleanStack = __webpack_require__(674); -const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); -class AggregateError extends Error { - constructor(errors) { - if (!Array.isArray(errors)) { - throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); - } - errors = [...errors].map(error => { - if (error instanceof Error) { - return error; - } - if (error !== null && typeof error === 'object') { - // Handle plain error objects with message property and/or possibly other metadata - return Object.assign(new Error(error.message), error); - } - return new Error(error); - }); +async function runCommand(command, config) { + try { + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Running [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(command.name)}] command from [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow(config.rootPath)}]:\n`)); + const kbn = await _utils_kibana__WEBPACK_IMPORTED_MODULE_7__["Kibana"].loadFrom(config.rootPath); + const projects = kbn.getFilteredProjects({ + skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), + ossOnly: Boolean(config.options.oss), + exclude: toArray(config.options.exclude), + include: toArray(config.options.include) + }); - let message = errors - .map(error => { - // The `stack` property is not standardized, so we can't assume it exists - return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); - }) - .join('\n'); - message = '\n' + indentString(message, 4); - super(message); + if (projects.size === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.\n`)); + return process.exit(1); + } - this.name = 'AggregateError'; + const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_5__["buildProjectGraph"])(projects); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Found [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(projects.size.toString())}] projects:\n`)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__["renderProjectsTree"])(config.rootPath, projects)); + await command.run(projects, projectGraph, _objectSpread({}, config, { + kbn + })); + } catch (e) { + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red(`\n[${command.name}] failed:\n`)); - Object.defineProperty(this, '_errors', {value: errors}); - } + if (e instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_3__["CliError"]) { + const msg = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`CliError: ${e.message}\n`); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default()(msg, 80)); + const keys = Object.keys(e.meta); - * [Symbol.iterator]() { - for (const error of this._errors) { - yield error; - } - } + if (keys.length > 0) { + const metaOutput = keys.map(key => { + const value = e.meta[key]; + return `${key}: ${value}`; + }); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write('Additional debugging info:\n'); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(indent_string__WEBPACK_IMPORTED_MODULE_1___default()(metaOutput.join('\n'), 3)); + } + } else { + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(e.stack); + } + + process.exit(1); + } } -module.exports = AggregateError; +function toArray(value) { + if (value == null) { + return []; + } + return Array.isArray(value) ? value : [value]; +} /***/ }), -/* 673 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77778,1043 +79919,754 @@ module.exports = (str, count, opts) => { /***/ }), -/* 674 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const os = __webpack_require__(11); - -const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; -const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; -const homeDir = os.homedir(); - -module.exports = (stack, options) => { - options = Object.assign({pretty: false}, options); - - return stack.replace(/\\/g, '/') - .split('\n') - .filter(line => { - const pathMatches = line.match(extractPathRegex); - if (pathMatches === null || !pathMatches[1]) { - return true; - } - - const match = pathMatches[1]; - - // Electron - if ( - match.includes('.app/Contents/Resources/electron.asar') || - match.includes('.app/Contents/Resources/default_app.asar') - ) { - return false; - } - - return !pathRegex.test(match); - }) - .filter(line => line.trim() !== '') - .map(line => { - if (options.pretty) { - return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); - } - - return line; - }) - .join('\n'); -}; - - -/***/ }), -/* 675 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(676); -const cliCursor = __webpack_require__(680); -const cliSpinners = __webpack_require__(684); -const logSymbols = __webpack_require__(566); - -class Ora { - constructor(options) { - if (typeof options === 'string') { - options = { - text: options - }; - } - - this.options = Object.assign({ - text: '', - color: 'cyan', - stream: process.stderr - }, options); - - const sp = this.options.spinner; - this.spinner = typeof sp === 'object' ? sp : (process.platform === 'win32' ? cliSpinners.line : (cliSpinners[sp] || cliSpinners.dots)); // eslint-disable-line no-nested-ternary - - if (this.spinner.frames === undefined) { - throw new Error('Spinner must define `frames`'); - } - - this.text = this.options.text; - this.color = this.options.color; - this.interval = this.options.interval || this.spinner.interval || 100; - this.stream = this.options.stream; - this.id = null; - this.frameIndex = 0; - this.enabled = typeof this.options.enabled === 'boolean' ? this.options.enabled : ((this.stream && this.stream.isTTY) && !process.env.CI); - } - frame() { - const frames = this.spinner.frames; - let frame = frames[this.frameIndex]; - - if (this.color) { - frame = chalk[this.color](frame); - } - - this.frameIndex = ++this.frameIndex % frames.length; - - return frame + ' ' + this.text; - } - clear() { - if (!this.enabled) { - return this; - } - - this.stream.clearLine(); - this.stream.cursorTo(0); - - return this; - } - render() { - this.clear(); - this.stream.write(this.frame()); - - return this; - } - start(text) { - if (text) { - this.text = text; - } - - if (!this.enabled || this.id) { - return this; - } - - cliCursor.hide(this.stream); - this.render(); - this.id = setInterval(this.render.bind(this), this.interval); - - return this; - } - stop() { - if (!this.enabled) { - return this; - } - - clearInterval(this.id); - this.id = null; - this.frameIndex = 0; - this.clear(); - cliCursor.show(this.stream); +const stringWidth = __webpack_require__(693); +const stripAnsi = __webpack_require__(697); - return this; - } - succeed(text) { - return this.stopAndPersist({symbol: logSymbols.success, text}); - } - fail(text) { - return this.stopAndPersist({symbol: logSymbols.error, text}); - } - warn(text) { - return this.stopAndPersist({symbol: logSymbols.warning, text}); - } - info(text) { - return this.stopAndPersist({symbol: logSymbols.info, text}); - } - stopAndPersist(options) { - if (!this.enabled) { - return this; - } +const ESCAPES = new Set([ + '\u001B', + '\u009B' +]); - // Legacy argument - // TODO: Deprecate sometime in the future - if (typeof options === 'string') { - options = { - symbol: options - }; - } +const END_CODE = 39; - options = options || {}; +const ESCAPE_CODES = new Map([ + [0, 0], + [1, 22], + [2, 22], + [3, 23], + [4, 24], + [7, 27], + [8, 28], + [9, 29], + [30, 39], + [31, 39], + [32, 39], + [33, 39], + [34, 39], + [35, 39], + [36, 39], + [37, 39], + [90, 39], + [40, 49], + [41, 49], + [42, 49], + [43, 49], + [44, 49], + [45, 49], + [46, 49], + [47, 49] +]); - this.stop(); - this.stream.write(`${options.symbol || ' '} ${options.text || this.text}\n`); +const wrapAnsi = code => `${ESCAPES.values().next().value}[${code}m`; - return this; - } -} +// Calculate the length of words split on ' ', ignoring +// the extra characters added by ansi escape codes +const wordLengths = str => str.split(' ').map(s => stringWidth(s)); -module.exports = function (opts) { - return new Ora(opts); -}; +// Wrap a long word across multiple rows +// Ansi escape codes do not count towards length +const wrapWord = (rows, word, cols) => { + const arr = Array.from(word); -module.exports.promise = (action, options) => { - if (typeof action.then !== 'function') { - throw new TypeError('Parameter `action` must be a Promise'); - } + let insideEscape = false; + let visible = stringWidth(stripAnsi(rows[rows.length - 1])); - const spinner = new Ora(options); - spinner.start(); + for (const item of arr.entries()) { + const i = item[0]; + const char = item[1]; + const charLength = stringWidth(char); - action.then( - () => { - spinner.succeed(); - }, - () => { - spinner.fail(); + if (visible + charLength <= cols) { + rows[rows.length - 1] += char; + } else { + rows.push(char); + visible = 0; } - ); - - return spinner; -}; - - -/***/ }), -/* 676 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(677); -const stdoutColor = __webpack_require__(678).stdout; - -const template = __webpack_require__(679); - -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); - -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; - -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); - -const styles = Object.create(null); - -function applyOptions(obj, options) { - options = options || {}; - - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; -} - -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; - - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - - chalk.template.constructor = Chalk; - - return chalk.template; - } - - applyOptions(this, options); -} - -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; -} - -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + if (ESCAPES.has(char)) { + insideEscape = true; + } else if (insideEscape && char === 'm') { + insideEscape = false; + continue; } - }; -} - -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; - -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } - - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; + + if (insideEscape) { + continue; } - }; -} -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } + visible += charLength; - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; + if (visible === cols && i < arr.length - 1) { + rows.push(''); + visible = 0; } - }; -} + } -const proto = Object.defineProperties(() => {}, styles); + // It's possible that the last row we copy over is only + // ansi escape characters, handle this edge-case + if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) { + rows[rows.length - 2] += rows.pop(); + } +}; -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; +// The wrap-ansi module can be invoked +// in either 'hard' or 'soft' wrap mode +// +// 'hard' will never allow a string to take up more +// than cols characters +// +// 'soft' allows long words to expand past the column length +const exec = (str, cols, opts) => { + const options = opts || {}; - builder._styles = _styles; - builder._empty = _empty; + if (str.trim() === '') { + return options.trim === false ? str : str.trim(); + } - const self = this; + let pre = ''; + let ret = ''; + let escapeCode; - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); + const lengths = wordLengths(str); + const words = str.split(' '); + const rows = ['']; - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); + for (const item of Array.from(words).entries()) { + const i = item[0]; + const word = item[1]; - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + rows[rows.length - 1] = options.trim === false ? rows[rows.length - 1] : rows[rows.length - 1].trim(); + let rowLength = stringWidth(rows[rows.length - 1]); - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto + if (rowLength || word === '') { + if (rowLength === cols && options.wordWrap === false) { + // If we start with a new word but the current row length equals the length of the columns, add a new row + rows.push(''); + rowLength = 0; + } - return builder; -} + rows[rows.length - 1] += ' '; + rowLength++; + } -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); + // In 'hard' wrap mode, the length of a line is + // never allowed to extend past 'cols' + if (lengths[i] > cols && options.hard) { + if (rowLength) { + rows.push(''); + } + wrapWord(rows, word, cols); + continue; + } - if (argsLen === 0) { - return ''; - } + if (rowLength + lengths[i] > cols && rowLength > 0) { + if (options.wordWrap === false && rowLength < cols) { + wrapWord(rows, word, cols); + continue; + } - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; + rows.push(''); } - } - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } + if (rowLength + lengths[i] > cols && options.wordWrap === false) { + wrapWord(rows, word, cols); + continue; + } - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; + rows[rows.length - 1] += word; } - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; - - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } + pre = rows.map(r => options.trim === false ? r : r.trim()).join('\n'); - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; + for (const item of Array.from(pre).entries()) { + const i = item[0]; + const char = item[1]; - return str; -} + ret += char; -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } + if (ESCAPES.has(char)) { + const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4))); + escapeCode = code === END_CODE ? null : code; + } - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; + const code = ESCAPE_CODES.get(Number(escapeCode)); - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); + if (escapeCode && code) { + if (pre[i + 1] === '\n') { + ret += wrapAnsi(code); + } else if (char === '\n') { + ret += wrapAnsi(escapeCode); + } + } } - return template(chalk, parts.join('')); -} - -Object.defineProperties(Chalk.prototype, styles); + return ret; +}; -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript +// For each newline, invoke the method separately +module.exports = (str, cols, opts) => { + return String(str) + .normalize() + .split('\n') + .map(line => exec(line, cols, opts)) + .join('\n'); +}; /***/ }), -/* 677 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(6); -const wrapAnsi16 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${code + offset}m`; -}; +const stripAnsi = __webpack_require__(694); +const isFullwidthCodePoint = __webpack_require__(696); -const wrapAnsi256 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};5;${code}m`; -}; +module.exports = str => { + if (typeof str !== 'string' || str.length === 0) { + return 0; + } -const wrapAnsi16m = (fn, offset) => function () { - const rgb = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; -}; + str = stripAnsi(str); -function assembleStyles() { - const codes = new Map(); - const styles = { - modifier: { - reset: [0, 0], - // 21 isn't widely supported and 22 does the same thing - bold: [1, 22], - dim: [2, 22], - italic: [3, 23], - underline: [4, 24], - inverse: [7, 27], - hidden: [8, 28], - strikethrough: [9, 29] - }, - color: { - black: [30, 39], - red: [31, 39], - green: [32, 39], - yellow: [33, 39], - blue: [34, 39], - magenta: [35, 39], - cyan: [36, 39], - white: [37, 39], - gray: [90, 39], + let width = 0; - // Bright color - redBright: [91, 39], - greenBright: [92, 39], - yellowBright: [93, 39], - blueBright: [94, 39], - magentaBright: [95, 39], - cyanBright: [96, 39], - whiteBright: [97, 39] - }, - bgColor: { - bgBlack: [40, 49], - bgRed: [41, 49], - bgGreen: [42, 49], - bgYellow: [43, 49], - bgBlue: [44, 49], - bgMagenta: [45, 49], - bgCyan: [46, 49], - bgWhite: [47, 49], + for (let i = 0; i < str.length; i++) { + const code = str.codePointAt(i); - // Bright color - bgBlackBright: [100, 49], - bgRedBright: [101, 49], - bgGreenBright: [102, 49], - bgYellowBright: [103, 49], - bgBlueBright: [104, 49], - bgMagentaBright: [105, 49], - bgCyanBright: [106, 49], - bgWhiteBright: [107, 49] + // Ignore control characters + if (code <= 0x1F || (code >= 0x7F && code <= 0x9F)) { + continue; } - }; - // Fix humans - styles.color.grey = styles.color.gray; + // Ignore combining characters + if (code >= 0x300 && code <= 0x36F) { + continue; + } - for (const groupName of Object.keys(styles)) { - const group = styles[groupName]; + // Surrogates + if (code > 0xFFFF) { + i++; + } - for (const styleName of Object.keys(group)) { - const style = group[styleName]; + width += isFullwidthCodePoint(code) ? 2 : 1; + } - styles[styleName] = { - open: `\u001B[${style[0]}m`, - close: `\u001B[${style[1]}m` - }; + return width; +}; - group[styleName] = styles[styleName]; - codes.set(style[0], style[1]); - } +/***/ }), +/* 694 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(styles, groupName, { - value: group, - enumerable: false - }); +"use strict"; - Object.defineProperty(styles, 'codes', { - value: codes, - enumerable: false - }); - } +const ansiRegex = __webpack_require__(695); - const ansi2ansi = n => n; - const rgb2rgb = (r, g, b) => [r, g, b]; +module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; - styles.color.close = '\u001B[39m'; - styles.bgColor.close = '\u001B[49m'; - styles.color.ansi = { - ansi: wrapAnsi16(ansi2ansi, 0) - }; - styles.color.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 0) - }; - styles.color.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 0) - }; +/***/ }), +/* 695 */ +/***/ (function(module, exports, __webpack_require__) { - styles.bgColor.ansi = { - ansi: wrapAnsi16(ansi2ansi, 10) - }; - styles.bgColor.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 10) - }; - styles.bgColor.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 10) - }; +"use strict"; - for (let key of Object.keys(colorConvert)) { - if (typeof colorConvert[key] !== 'object') { - continue; - } - const suite = colorConvert[key]; +module.exports = () => { + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' + ].join('|'); - if (key === 'ansi16') { - key = 'ansi'; - } + return new RegExp(pattern, 'g'); +}; - if ('ansi16' in suite) { - styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); - styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); - } - if ('ansi256' in suite) { - styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); - styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); - } +/***/ }), +/* 696 */ +/***/ (function(module, exports, __webpack_require__) { - if ('rgb' in suite) { - styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); - styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); - } +"use strict"; + +/* eslint-disable yoda */ +module.exports = x => { + if (Number.isNaN(x)) { + return false; } - return styles; -} + // code points are derived from: + // http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt + if ( + x >= 0x1100 && ( + x <= 0x115f || // Hangul Jamo + x === 0x2329 || // LEFT-POINTING ANGLE BRACKET + x === 0x232a || // RIGHT-POINTING ANGLE BRACKET + // CJK Radicals Supplement .. Enclosed CJK Letters and Months + (0x2e80 <= x && x <= 0x3247 && x !== 0x303f) || + // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A + (0x3250 <= x && x <= 0x4dbf) || + // CJK Unified Ideographs .. Yi Radicals + (0x4e00 <= x && x <= 0xa4c6) || + // Hangul Jamo Extended-A + (0xa960 <= x && x <= 0xa97c) || + // Hangul Syllables + (0xac00 <= x && x <= 0xd7a3) || + // CJK Compatibility Ideographs + (0xf900 <= x && x <= 0xfaff) || + // Vertical Forms + (0xfe10 <= x && x <= 0xfe19) || + // CJK Compatibility Forms .. Small Form Variants + (0xfe30 <= x && x <= 0xfe6b) || + // Halfwidth and Fullwidth Forms + (0xff01 <= x && x <= 0xff60) || + (0xffe0 <= x && x <= 0xffe6) || + // Kana Supplement + (0x1b000 <= x && x <= 0x1b001) || + // Enclosed Ideographic Supplement + (0x1f200 <= x && x <= 0x1f251) || + // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane + (0x20000 <= x && x <= 0x3fffd) + ) + ) { + return true; + } -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); + return false; +}; -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 678 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const os = __webpack_require__(11); -const hasFlag = __webpack_require__(12); +const ansiRegex = __webpack_require__(698); -const env = process.env; +module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; -let forceColor; -if (hasFlag('no-color') || - hasFlag('no-colors') || - hasFlag('color=false')) { - forceColor = false; -} else if (hasFlag('color') || - hasFlag('colors') || - hasFlag('color=true') || - hasFlag('color=always')) { - forceColor = true; -} -if ('FORCE_COLOR' in env) { - forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; -} -function translateLevel(level) { - if (level === 0) { - return false; - } +/***/ }), +/* 698 */ +/***/ (function(module, exports, __webpack_require__) { - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 - }; -} +"use strict"; -function supportsColor(stream) { - if (forceColor === false) { - return 0; - } - if (hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor')) { - return 3; - } +module.exports = () => { + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' + ].join('|'); - if (hasFlag('color=256')) { - return 2; - } + return new RegExp(pattern, 'g'); +}; + + +/***/ }), +/* 699 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "renderProjectsTree", function() { return renderProjectsTree; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* + * 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. + */ - if (stream && !stream.isTTY && forceColor !== true) { - // VS code debugger doesn't have isTTY set - if (env.VSCODE_PID) { - return 1; - } - return 0; - } - const min = forceColor ? 1 : 0; +const projectKey = Symbol('__project'); +function renderProjectsTree(rootPath, projects) { + const projectsTree = buildProjectsTree(rootPath, projects); + return treeToString(createTreeStructure(projectsTree)); +} - if (process.platform === 'win32') { - // Node.js 7.5.0 is the first version of Node.js to include a patch to - // libuv that enables 256 color output on Windows. Anything earlier and it - // won't work. However, here we target Node.js 8 at minimum as it is an LTS - // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows - // release that supports 256 colors. Windows 10 build 14931 is the first release - // that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if ( - Number(process.versions.node.split('.')[0]) >= 8 && - Number(osRelease[0]) >= 10 && - Number(osRelease[2]) >= 10586 - ) { - return Number(osRelease[2]) >= 14931 ? 3 : 2; - } +function treeToString(tree) { + return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n'); +} - return 1; - } +function childrenToStrings(tree, treePrefix) { + if (tree === undefined) { + return []; + } - if ('CI' in env) { - if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { - return 1; - } + let strings = []; + tree.forEach((node, index) => { + const isLastNode = tree.length - 1 === index; + const nodePrefix = isLastNode ? '└── ' : '├── '; + const childPrefix = isLastNode ? ' ' : '│ '; + const childrenPrefix = treePrefix + childPrefix; + strings.push(`${treePrefix}${nodePrefix}${node.name}`); + strings = strings.concat(childrenToStrings(node.children, childrenPrefix)); + }); + return strings; +} - return min; - } +function createTreeStructure(tree) { + let name; + const children = []; - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } + for (const [dir, project] of tree.entries()) { + // This is a leaf node (aka a project) + if (typeof project === 'string') { + name = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(project); + continue; + } // If there's only one project and the key indicates it's a leaf node, we + // know that we're at a package folder that contains a package.json, so we + // "inline it" so we don't get unnecessary levels, i.e. we'll just see + // `foo` instead of `foo -> foo`. - if (env.COLORTERM === 'truecolor') { - return 3; - } - if ('TERM_PROGRAM' in env) { - const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + if (project.size === 1 && project.has(projectKey)) { + const projectName = project.get(projectKey); + children.push({ + children: [], + name: dirOrProjectName(dir, projectName) + }); + continue; + } - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - return version >= 3 ? 3 : 2; - case 'Apple_Terminal': - return 2; - // No default - } - } + const subtree = createTreeStructure(project); // If the name is specified, we know there's a package at the "root" of the + // subtree itself. - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } + if (subtree.name !== undefined) { + const projectName = subtree.name; + children.push({ + children: subtree.children, + name: dirOrProjectName(dir, projectName) + }); + continue; + } // Special-case whenever we have one child, so we don't get unnecessary + // folders in the output. E.g. instead of `foo -> bar -> baz` we get + // `foo/bar/baz` instead. - if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { - return 1; - } - if ('COLORTERM' in env) { - return 1; - } + if (subtree.children && subtree.children.length === 1) { + const child = subtree.children[0]; + const newName = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(dir.toString(), child.name)); + children.push({ + children: child.children, + name: newName + }); + continue; + } - if (env.TERM === 'dumb') { - return min; - } + children.push({ + children: subtree.children, + name: chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(dir.toString()) + }); + } - return min; + return { + name, + children + }; } -function getSupportLevel(stream) { - const level = supportsColor(stream); - return translateLevel(level); +function dirOrProjectName(dir, projectName) { + return dir === projectName ? chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(dir) : chalk__WEBPACK_IMPORTED_MODULE_0___default.a`{dim ${dir.toString()} ({reset.green ${projectName}})}`; } -module.exports = { - supportsColor: getSupportLevel, - stdout: getSupportLevel(process.stdout), - stderr: getSupportLevel(process.stderr) -}; - - -/***/ }), -/* 679 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; -const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; -const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; -const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; - -const ESCAPES = new Map([ - ['n', '\n'], - ['r', '\r'], - ['t', '\t'], - ['b', '\b'], - ['f', '\f'], - ['v', '\v'], - ['0', '\0'], - ['\\', '\\'], - ['e', '\u001B'], - ['a', '\u0007'] -]); +function buildProjectsTree(rootPath, projects) { + const tree = new Map(); -function unescape(c) { - if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { - return String.fromCharCode(parseInt(c.slice(1), 16)); - } + for (const project of projects.values()) { + if (rootPath === project.path) { + tree.set(projectKey, project.name); + } else { + const relativeProjectPath = path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(rootPath, project.path); + addProjectToTree(tree, relativeProjectPath.split(path__WEBPACK_IMPORTED_MODULE_1___default.a.sep), project); + } + } - return ESCAPES.get(c) || c; + return tree; } -function parseArguments(name, args) { - const results = []; - const chunks = args.trim().split(/\s*,\s*/g); - let matches; +function addProjectToTree(tree, pathParts, project) { + if (pathParts.length === 0) { + tree.set(projectKey, project.name); + } else { + const [currentDir, ...rest] = pathParts; - for (const chunk of chunks) { - if (!isNaN(chunk)) { - results.push(Number(chunk)); - } else if ((matches = chunk.match(STRING_REGEX))) { - results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); - } else { - throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); - } - } + if (!tree.has(currentDir)) { + tree.set(currentDir, new Map()); + } - return results; + const subtree = tree.get(currentDir); + addProjectToTree(subtree, rest, project); + } } -function parseStyle(style) { - STYLE_REGEX.lastIndex = 0; +/***/ }), +/* 700 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const results = []; - let matches; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(701); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(501); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } - while ((matches = STYLE_REGEX.exec(style)) !== null) { - const name = matches[1]; +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - if (matches[2]) { - const args = parseArguments(name, matches[2]); - results.push([name].concat(args)); - } else { - results.push([name]); - } - } +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - return results; -} +/* + * 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. + */ -function buildStyle(chalk, styles) { - const enabled = {}; - for (const layer of styles) { - for (const style of layer.styles) { - enabled[style[0]] = layer.inverse ? null : style.slice(1); - } - } - let current = chalk; - for (const styleName of Object.keys(enabled)) { - if (Array.isArray(enabled[styleName])) { - if (!(styleName in current)) { - throw new Error(`Unknown Chalk style: ${styleName}`); - } - if (enabled[styleName].length > 0) { - current = current[styleName].apply(current, enabled[styleName]); - } else { - current = current[styleName]; - } - } - } +/** + * Helper class for dealing with a set of projects as children of + * the Kibana project. The kbn/pm is currently implemented to be + * more generic, where everything is an operation of generic projects, + * but that leads to exceptions where we need the kibana project and + * do things like `project.get('kibana')!`. + * + * Using this helper we can restructre the generic list of projects + * as a Kibana object which encapulates all the projects in the + * workspace and knows about the root Kibana project. + */ - return current; -} +class Kibana { + static async loadFrom(rootPath) { + return new Kibana((await Object(_projects__WEBPACK_IMPORTED_MODULE_2__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ + rootPath + })))); + } -module.exports = (chalk, tmp) => { - const styles = []; - const chunks = []; - let chunk = []; + constructor(allWorkspaceProjects) { + this.allWorkspaceProjects = allWorkspaceProjects; - // eslint-disable-next-line max-params - tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { - if (escapeChar) { - chunk.push(unescape(escapeChar)); - } else if (style) { - const str = chunk.join(''); - chunk = []; - chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); - styles.push({inverse, styles: parseStyle(style)}); - } else if (close) { - if (styles.length === 0) { - throw new Error('Found extraneous } in Chalk template literal'); - } + _defineProperty(this, "kibanaProject", void 0); - chunks.push(buildStyle(chalk, styles)(chunk.join(''))); - chunk = []; - styles.pop(); - } else { - chunk.push(chr); - } - }); + const kibanaProject = allWorkspaceProjects.get('kibana'); - chunks.push(chunk.join('')); + if (!kibanaProject) { + throw new TypeError('Unable to create Kibana object without all projects, including the Kibana project.'); + } - if (styles.length > 0) { - const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; - throw new Error(errMsg); - } + this.kibanaProject = kibanaProject; + } + /** make an absolute path by resolving subPath relative to the kibana repo */ - return chunks.join(''); -}; + getAbsolute(...subPath) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(this.kibanaProject.path, ...subPath); + } + /** convert an absolute path to a relative path, relative to the kibana repo */ -/***/ }), -/* 680 */ -/***/ (function(module, exports, __webpack_require__) { -"use strict"; + getRelative(absolute) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.relative(this.kibanaProject.path, absolute); + } + /** get a copy of the map of all projects in the kibana workspace */ -const restoreCursor = __webpack_require__(681); -let hidden = false; + getAllProjects() { + return new Map(this.allWorkspaceProjects); + } + /** determine if a project with the given name exists */ -exports.show = stream => { - const s = stream || process.stderr; - if (!s.isTTY) { - return; - } + hasProject(name) { + return this.allWorkspaceProjects.has(name); + } + /** get a specific project, throws if the name is not known (use hasProject() first) */ - hidden = false; - s.write('\u001b[?25h'); -}; -exports.hide = stream => { - const s = stream || process.stderr; + getProject(name) { + const project = this.allWorkspaceProjects.get(name); - if (!s.isTTY) { - return; - } + if (!project) { + throw new Error(`No package with name "${name}" in the workspace`); + } - restoreCursor(); - hidden = true; - s.write('\u001b[?25l'); -}; + return project; + } + /** get a project and all of the projects it depends on in a ProjectMap */ -exports.toggle = (force, stream) => { - if (force !== undefined) { - hidden = force; - } - if (hidden) { - exports.show(stream); - } else { - exports.hide(stream); - } -}; + getProjectAndDeps(name) { + const project = this.getProject(name); + return Object(_projects__WEBPACK_IMPORTED_MODULE_2__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); + } + /** filter the projects to just those matching certain paths/include/exclude tags */ -/***/ }), -/* 681 */ -/***/ (function(module, exports, __webpack_require__) { + getFilteredProjects(options) { + const allProjects = this.getAllProjects(); + const filteredProjects = new Map(); + const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); + const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(_objectSpread({}, options, { + rootPath: this.kibanaProject.path + })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); + const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_1___default()(pkgJsonPaths, filteredPkgJsonGlobs); -"use strict"; + for (const project of allProjects.values()) { + const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); + const notExcluded = !options.exclude.includes(project.name); + const isIncluded = !options.include.length || options.include.includes(project.name); -const onetime = __webpack_require__(682); -const signalExit = __webpack_require__(377); + if (pathMatches && notExcluded && isIncluded) { + filteredProjects.set(project.name, project); + } + } -module.exports = onetime(() => { - signalExit(() => { - process.stderr.write('\u001b[?25h'); - }, {alwaysLast: true}); -}); + return filteredProjects; + } +} /***/ }), -/* 682 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const mimicFn = __webpack_require__(683); +const minimatch = __webpack_require__(505); +const arrayUnion = __webpack_require__(702); +const arrayDiffer = __webpack_require__(703); +const arrify = __webpack_require__(704); -module.exports = (fn, opts) => { - // TODO: Remove this in v3 - if (opts === true) { - throw new TypeError('The second argument is now an options object'); - } +module.exports = (list, patterns, options = {}) => { + list = arrify(list); + patterns = arrify(patterns); - if (typeof fn !== 'function') { - throw new TypeError('Expected a function'); + if (list.length === 0 || patterns.length === 0) { + return []; } - opts = opts || {}; + return patterns.reduce((result, pattern) => { + let process = arrayUnion; - let ret; - let called = false; - const fnName = fn.displayName || fn.name || '<anonymous>'; + if (pattern[0] === '!') { + pattern = pattern.slice(1); + process = arrayDiffer; + } - const onetime = function () { - if (called) { - if (opts.throw === true) { - throw new Error(`Function \`${fnName}\` can only be called once`); - } + return process(result, minimatch.match(list, pattern, options)); + }, []); +}; - return ret; - } - called = true; - ret = fn.apply(this, arguments); - fn = null; +/***/ }), +/* 702 */ +/***/ (function(module, exports, __webpack_require__) { - return ret; - }; +"use strict"; - mimicFn(onetime, fn); - return onetime; +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; }; /***/ }), -/* 683 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = (to, from) => { - // TODO: use `Reflect.ownKeys()` when targeting Node.js 6 - for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { - Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); - } - return to; +const arrayDiffer = (array, ...values) => { + const rest = new Set([].concat(...values)); + return array.filter(element => !rest.has(element)); }; +module.exports = arrayDiffer; + /***/ }), -/* 684 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -module.exports = __webpack_require__(685); +const arrify = value => { + if (value === null || value === undefined) { + return []; + } -/***/ }), -/* 685 */ -/***/ (function(module) { + if (Array.isArray(value)) { + return value; + } + + if (typeof value === 'string') { + return [value]; + } + + if (typeof value[Symbol.iterator] === 'function') { + return [...value]; + } + + return [value]; +}; + +module.exports = arrify; -module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 686 */ +/* 705 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(706); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); + +/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(923); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -78836,46 +80688,24 @@ __webpack_require__.r(__webpack_exports__); - -const RunCommand = { - description: 'Run script defined in package.json in each package that contains that script.', - name: 'run', - - async run(projects, projectGraph, { - extraArgs - }) { - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); - - if (extraArgs.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red.bold('\nNo script specified')); - process.exit(1); - } - - const scriptName = extraArgs[0]; - const scriptArgs = extraArgs.slice(1); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in batched topological order\n`)); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - if (pkg.hasScript(scriptName)) { - await pkg.runScriptStreaming(scriptName, scriptArgs); - } - }); - } - -}; - /***/ }), -/* 687 */ +/* 706 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(688); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(707); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(588); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); +/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(517); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(501); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -78900,1532 +80730,2066 @@ __webpack_require__.r(__webpack_exports__); + + +async function buildProductionProjects({ + kibanaRoot, + buildRoot, + onlyOSS +}) { + const projects = await getProductionProjects(kibanaRoot, onlyOSS); + const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["buildProjectGraph"])(projects); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["topologicallyBatchProjects"])(projects, projectGraph); + const projectNames = [...projects.values()].map(project => project.name); + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(`Preparing production build for [${projectNames.join(', ')}]`); + + for (const batch of batchedProjects) { + for (const project of batch) { + await deleteTarget(project); + await buildProject(project); + await copyToBuild(project, kibanaRoot, buildRoot); + } + } +} /** - * Name of the script in the package/project package.json file to run during `kbn watch`. - */ -const watchScriptName = 'kbn:watch'; -/** - * Name of the Kibana project. + * Returns the subset of projects that should be built into the production + * bundle. As we copy these into Kibana's `node_modules` during the build step, + * and let Kibana's build process be responsible for installing dependencies, + * we only include Kibana's transitive _production_ dependencies. If onlyOSS + * is supplied, we omit projects with build.oss in their package.json set to false. */ -const kibanaProjectName = 'kibana'; +async function getProductionProjects(rootPath, onlyOSS) { + const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ + rootPath + }); + const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["getProjects"])(rootPath, projectPaths); + const projectsSubset = [projects.get('kibana')]; + + if (projects.has('x-pack')) { + projectsSubset.push(projects.get('x-pack')); + } + + const productionProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["includeTransitiveProjects"])(projectsSubset, projects, { + onlyProductionDependencies: true + }); // We remove Kibana, as we're already building Kibana + + productionProjects.delete('kibana'); + + if (onlyOSS) { + productionProjects.forEach(project => { + if (project.getBuildConfig().oss === false) { + productionProjects.delete(project.json.name); + } + }); + } + + return productionProjects; +} + +async function deleteTarget(project) { + const targetDir = project.targetLocation; + + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(targetDir)) { + await del__WEBPACK_IMPORTED_MODULE_1___default()(targetDir, { + force: true + }); + } +} + +async function buildProject(project) { + if (project.hasScript('build')) { + await project.runScript('build'); + } +} /** - * Command that traverses through list of available projects/packages that have `kbn:watch` script in their - * package.json files, groups them into topology aware batches and then processes theses batches one by one - * running `kbn:watch` scripts in parallel within the same batch. + * Copy all the project's files from its "intermediate build directory" and + * into the build. The intermediate directory can either be the root of the + * project or some other location defined in the project's `package.json`. * - * Command internally relies on the fact that most of the build systems that are triggered by `kbn:watch` - * will emit special "marker" once build/watch process is ready that we can use as completion condition for - * the `kbn:watch` script and eventually for the entire batch. Currently we support completion "markers" for - * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. + * When copying all the files into the build, we exclude `node_modules` because + * we want the Kibana build to be responsible for actually installing all + * dependencies. The primary reason for allowing the Kibana build process to + * manage dependencies is that it will "dedupe" them, so we don't include + * unnecessary copies of dependencies. */ -const WatchCommand = { - description: 'Runs `kbn:watch` script for every project.', - name: 'watch', - async run(projects, projectGraph) { - const projectsToWatch = new Map(); +async function copyToBuild(project, kibanaRoot, buildRoot) { + // We want the package to have the same relative location within the build + const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); + const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); + await cpy__WEBPACK_IMPORTED_MODULE_0___default()(['**/*', '!node_modules/**'], buildProjectPath, { + cwd: project.getIntermediateBuildDirectory(), + dot: true, + nodir: true, + parents: true + }); // If a project is using an intermediate build directory, we special-case our + // handling of `package.json`, as the project build process might have copied + // (a potentially modified) `package.json` into the intermediate build + // directory already. If so, we want to use that `package.json` as the basis + // for creating the production-ready `package.json`. If it's not present in + // the intermediate build, we fall back to using the project's already defined + // `package.json`. - for (const project of projects.values()) { - // We can't watch project that doesn't have `kbn:watch` script. - if (project.hasScript(watchScriptName)) { - projectsToWatch.set(project.name, project); - } - } + const packageJson = (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isFile"])(Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(buildProjectPath, 'package.json'))) ? await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_6__["readPackageJson"])(buildProjectPath) : project.json; + await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_6__["writePackageJson"])(buildProjectPath, packageJson); +} - if (projectsToWatch.size === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`\nThere are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.\n`)); - return; - } +/***/ }), +/* 707 */ +/***/ (function(module, exports, __webpack_require__) { - const projectNames = Array.from(projectsToWatch.keys()); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`))); // Kibana should always be run the last, so we don't rely on automatic - // topological batching and push it to the last one-entry batch manually. +"use strict"; - const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); +const EventEmitter = __webpack_require__(379); +const path = __webpack_require__(16); +const arrify = __webpack_require__(708); +const globby = __webpack_require__(709); +const cpFile = __webpack_require__(912); +const CpyError = __webpack_require__(921); - if (shouldWatchKibanaProject) { - batchedProjects.push([projects.get(kibanaProjectName)]); - } +const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName).stdout); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`[${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(pkg.name)}] Initial build completed (${completionHint}).`)); - }); - } +const preprocessDestPath = (srcPath, dest, options) => { + let basename = path.basename(srcPath); + const dirname = path.dirname(srcPath); + + if (typeof options.rename === 'string') { + basename = options.rename; + } else if (typeof options.rename === 'function') { + basename = options.rename(basename); + } + + if (options.cwd) { + dest = path.resolve(options.cwd, dest); + } + + if (options.parents) { + return path.join(dest, dirname, basename); + } + + return path.join(dest, basename); +}; + +const cpy = (src, dest, options = {}) => { + src = arrify(src); + + const progressEmitter = new EventEmitter(); + + if (src.length === 0 || !dest) { + const promise = Promise.reject(new CpyError('`files` and `destination` required')); + promise.on = (...args) => { + progressEmitter.on(...args); + return promise; + }; + + return promise; + } + + const copyStatus = new Map(); + let completedFiles = 0; + let completedSize = 0; + + const promise = globby(src, options) + .catch(error => { + throw new CpyError(`Cannot glob \`${src}\`: ${error.message}`, error); + }) + .then(files => { + if (files.length === 0) { + progressEmitter.emit('progress', { + totalFiles: 0, + percent: 1, + completedFiles: 0, + completedSize: 0 + }); + } + + return Promise.all(files.map(srcPath => { + const from = preprocessSrcPath(srcPath, options); + const to = preprocessDestPath(srcPath, dest, options); + + return cpFile(from, to, options) + .on('progress', event => { + const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; + + if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { + completedSize -= fileStatus.written; + completedSize += event.written; + + if (event.percent === 1 && fileStatus.percent !== 1) { + completedFiles++; + } + + copyStatus.set(event.src, {written: event.written, percent: event.percent}); + + progressEmitter.emit('progress', { + totalFiles: files.length, + percent: completedFiles / files.length, + completedFiles, + completedSize + }); + } + }) + .then(() => to) + .catch(error => { + throw new CpyError(`Cannot copy from \`${from}\` to \`${to}\`: ${error.message}`, error); + }); + })); + }); + + promise.on = (...args) => { + progressEmitter.on(...args); + return promise; + }; + return promise; }; +module.exports = cpy; +// TODO: Remove this for the next major release +module.exports.default = cpy; + + /***/ }), -/* 688 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 708 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(392); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(169); -/* - * 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. - */ +module.exports = function (val) { + if (val === null || val === undefined) { + return []; + } -/** - * Number of milliseconds we wait before we fall back to the default watch handler. - */ + return Array.isArray(val) ? val : [val]; +}; -const defaultHandlerDelay = 3000; -/** - * If default watch handler is used, then it's the number of milliseconds we wait for - * any build output before we consider watch task ready. - */ -const defaultHandlerReadinessTimeout = 2000; -/** - * Describes configurable watch options. - */ +/***/ }), +/* 709 */ +/***/ (function(module, exports, __webpack_require__) { -function getWatchHandlers(buildOutput$, { - handlerDelay = defaultHandlerDelay, - handlerReadinessTimeout = defaultHandlerReadinessTimeout -}) { - const typescriptHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ tsc')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Compilation complete.')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('tsc')))); - const webpackHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ webpack')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Chunk Names')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('webpack')))); - const defaultHandler = rxjs__WEBPACK_IMPORTED_MODULE_0__["of"](undefined).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["delay"])(handlerReadinessTimeout), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["timeout"])(handlerDelay), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["catchError"])(() => rxjs__WEBPACK_IMPORTED_MODULE_0__["of"]('timeout'))))); - return [typescriptHandler, webpackHandler, defaultHandler]; -} +"use strict"; -function waitUntilWatchIsReady(stream, opts = {}) { - const buildOutput$ = new rxjs__WEBPACK_IMPORTED_MODULE_0__["Subject"](); +const fs = __webpack_require__(23); +const arrayUnion = __webpack_require__(710); +const glob = __webpack_require__(712); +const fastGlob = __webpack_require__(717); +const dirGlob = __webpack_require__(905); +const gitignore = __webpack_require__(908); - const onDataListener = data => buildOutput$.next(data.toString('utf-8')); +const DEFAULT_FILTER = () => false; - const onEndListener = () => buildOutput$.complete(); +const isNegative = pattern => pattern[0] === '!'; - const onErrorListener = e => buildOutput$.error(e); +const assertPatternsInput = patterns => { + if (!patterns.every(x => typeof x === 'string')) { + throw new TypeError('Patterns must be a string or an array of strings'); + } +}; + +const checkCwdOption = options => { + if (options && options.cwd && !fs.statSync(options.cwd).isDirectory()) { + throw new Error('The `cwd` option must be a path to a directory'); + } +}; + +const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([].concat(patterns)); + assertPatternsInput(patterns); + checkCwdOption(taskOptions); + + const globTasks = []; + + taskOptions = Object.assign({ + ignore: [], + expandDirectories: true + }, taskOptions); + + patterns.forEach((pattern, i) => { + if (isNegative(pattern)) { + return; + } + + const ignore = patterns + .slice(i) + .filter(isNegative) + .map(pattern => pattern.slice(1)); + + const options = Object.assign({}, taskOptions, { + ignore: taskOptions.ignore.concat(ignore) + }); + + globTasks.push({pattern, options}); + }); + + return globTasks; +}; + +const globDirs = (task, fn) => { + let options = {}; + if (task.options.cwd) { + options.cwd = task.options.cwd; + } + + if (Array.isArray(task.options.expandDirectories)) { + options = Object.assign(options, {files: task.options.expandDirectories}); + } else if (typeof task.options.expandDirectories === 'object') { + options = Object.assign(options, task.options.expandDirectories); + } + + return fn(task.pattern, options); +}; + +const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; + +const globToTask = task => glob => { + const {options} = task; + if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { + options.ignore = dirGlob.sync(options.ignore); + } + + return { + pattern: glob, + options + }; +}; + +const globby = (patterns, options) => { + let globTasks; + + try { + globTasks = generateGlobTasks(patterns, options); + } catch (error) { + return Promise.reject(error); + } + + const getTasks = Promise.all(globTasks.map(task => Promise.resolve(getPattern(task, dirGlob)) + .then(globs => Promise.all(globs.map(globToTask(task)))) + )) + .then(tasks => arrayUnion(...tasks)); + + const getFilter = () => { + return Promise.resolve( + options && options.gitignore ? + gitignore({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER + ); + }; + + return getFilter() + .then(filter => { + return getTasks + .then(tasks => Promise.all(tasks.map(task => fastGlob(task.pattern, task.options)))) + .then(paths => arrayUnion(...paths)) + .then(paths => paths.filter(p => !filter(p))); + }); +}; + +module.exports = globby; +// TODO: Remove this for the next major release +module.exports.default = globby; + +module.exports.sync = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const getFilter = () => { + return options && options.gitignore ? + gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; + }; + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilter(); + return tasks.reduce( + (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), + [] + ).filter(p => !filter(p)); +}; + +module.exports.generateGlobTasks = generateGlobTasks; + +module.exports.hasMagic = (patterns, options) => [] + .concat(patterns) + .some(pattern => glob.hasMagic(pattern, options)); + +module.exports.gitignore = gitignore; - stream.once('end', onEndListener); - stream.once('error', onErrorListener); - stream.on('data', onDataListener); - return rxjs__WEBPACK_IMPORTED_MODULE_0__["race"](getWatchHandlers(buildOutput$, opts)).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mergeMap"])(whenReady => whenReady), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["finalize"])(() => { - stream.removeListener('data', onDataListener); - stream.removeListener('end', onEndListener); - stream.removeListener('error', onErrorListener); - buildOutput$.complete(); - })).toPromise(); -} /***/ }), -/* 689 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 710 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(673); -/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(indent_string__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(690); -/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(wrap_ansi__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(515); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(34); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(501); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(697); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(698); -function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } - -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } +var arrayUniq = __webpack_require__(711); -/* - * 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. - */ +module.exports = function () { + return arrayUniq([].concat.apply([], arguments)); +}; +/***/ }), +/* 711 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +// there's 3 implementations written in increasing order of efficiency +// 1 - no Set type is defined +function uniqNoSet(arr) { + var ret = []; + for (var i = 0; i < arr.length; i++) { + if (ret.indexOf(arr[i]) === -1) { + ret.push(arr[i]); + } + } -async function runCommand(command, config) { - try { - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Running [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(command.name)}] command from [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow(config.rootPath)}]:\n`)); - const kbn = await _utils_kibana__WEBPACK_IMPORTED_MODULE_7__["Kibana"].loadFrom(config.rootPath); - const projects = kbn.getFilteredProjects({ - skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), - ossOnly: Boolean(config.options.oss), - exclude: toArray(config.options.exclude), - include: toArray(config.options.include) - }); + return ret; +} - if (projects.size === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.\n`)); - return process.exit(1); - } +// 2 - a simple Set type is defined +function uniqSet(arr) { + var seen = new Set(); + return arr.filter(function (el) { + if (!seen.has(el)) { + seen.add(el); + return true; + } - const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_5__["buildProjectGraph"])(projects); - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Found [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(projects.size.toString())}] projects:\n`)); - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__["renderProjectsTree"])(config.rootPath, projects)); - await command.run(projects, projectGraph, _objectSpread({}, config, { - kbn - })); - } catch (e) { - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red(`\n[${command.name}] failed:\n`)); + return false; + }); +} - if (e instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_3__["CliError"]) { - const msg = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`CliError: ${e.message}\n`); - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default()(msg, 80)); - const keys = Object.keys(e.meta); +// 3 - a standard Set type is defined and it has a forEach method +function uniqSetWithForEach(arr) { + var ret = []; - if (keys.length > 0) { - const metaOutput = keys.map(key => { - const value = e.meta[key]; - return `${key}: ${value}`; - }); - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write('Additional debugging info:\n'); - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(indent_string__WEBPACK_IMPORTED_MODULE_1___default()(metaOutput.join('\n'), 3)); - } - } else { - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(e.stack); - } + (new Set(arr)).forEach(function (el) { + ret.push(el); + }); - process.exit(1); - } + return ret; } -function toArray(value) { - if (value == null) { - return []; - } +// V8 currently has a broken implementation +// https://github.com/joyent/node/issues/8449 +function doesForEachActuallyWork() { + var ret = false; - return Array.isArray(value) ? value : [value]; + (new Set([true])).forEach(function (el) { + ret = el; + }); + + return ret === true; +} + +if ('Set' in global) { + if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { + module.exports = uniqSetWithForEach; + } else { + module.exports = uniqSet; + } +} else { + module.exports = uniqNoSet; } + /***/ }), -/* 690 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. -const stringWidth = __webpack_require__(691); -const stripAnsi = __webpack_require__(695); +module.exports = glob -const ESCAPES = new Set([ - '\u001B', - '\u009B' -]); +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var inherits = __webpack_require__(713) +var EE = __webpack_require__(379).EventEmitter +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var globSync = __webpack_require__(715) +var common = __webpack_require__(716) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = __webpack_require__(514) +var util = __webpack_require__(29) +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored -const END_CODE = 39; +var once = __webpack_require__(385) -const ESCAPE_CODES = new Map([ - [0, 0], - [1, 22], - [2, 22], - [3, 23], - [4, 24], - [7, 27], - [8, 28], - [9, 29], - [30, 39], - [31, 39], - [32, 39], - [33, 39], - [34, 39], - [35, 39], - [36, 39], - [37, 39], - [90, 39], - [40, 49], - [41, 49], - [42, 49], - [43, 49], - [44, 49], - [45, 49], - [46, 49], - [47, 49] -]); +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} -const wrapAnsi = code => `${ESCAPES.values().next().value}[${code}m`; + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } -// Calculate the length of words split on ' ', ignoring -// the extra characters added by ansi escape codes -const wordLengths = str => str.split(' ').map(s => stringWidth(s)); + return new Glob(pattern, options, cb) +} -// Wrap a long word across multiple rows -// Ansi escape codes do not count towards length -const wrapWord = (rows, word, cols) => { - const arr = Array.from(word); +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync - let insideEscape = false; - let visible = stringWidth(stripAnsi(rows[rows.length - 1])); +// old api surface +glob.glob = glob - for (const item of arr.entries()) { - const i = item[0]; - const char = item[1]; - const charLength = stringWidth(char); +function extend (origin, add) { + if (add === null || typeof add !== 'object') { + return origin + } - if (visible + charLength <= cols) { - rows[rows.length - 1] += char; - } else { - rows.push(char); - visible = 0; - } + var keys = Object.keys(add) + var i = keys.length + while (i--) { + origin[keys[i]] = add[keys[i]] + } + return origin +} - if (ESCAPES.has(char)) { - insideEscape = true; - } else if (insideEscape && char === 'm') { - insideEscape = false; - continue; - } +glob.hasMagic = function (pattern, options_) { + var options = extend({}, options_) + options.noprocess = true - if (insideEscape) { - continue; - } + var g = new Glob(pattern, options) + var set = g.minimatch.set - visible += charLength; + if (!pattern) + return false - if (visible === cols && i < arr.length - 1) { - rows.push(''); - visible = 0; - } - } + if (set.length > 1) + return true - // It's possible that the last row we copy over is only - // ansi escape characters, handle this edge-case - if (!visible && rows[rows.length - 1].length > 0 && rows.length > 1) { - rows[rows.length - 2] += rows.pop(); - } -}; + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } -// The wrap-ansi module can be invoked -// in either 'hard' or 'soft' wrap mode -// -// 'hard' will never allow a string to take up more -// than cols characters -// -// 'soft' allows long words to expand past the column length -const exec = (str, cols, opts) => { - const options = opts || {}; + return false +} - if (str.trim() === '') { - return options.trim === false ? str : str.trim(); - } +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } - let pre = ''; - let ret = ''; - let escapeCode; + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } - const lengths = wordLengths(str); - const words = str.split(' '); - const rows = ['']; + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) - for (const item of Array.from(words).entries()) { - const i = item[0]; - const word = item[1]; + setopts(this, pattern, options) + this._didRealPath = false - rows[rows.length - 1] = options.trim === false ? rows[rows.length - 1] : rows[rows.length - 1].trim(); - let rowLength = stringWidth(rows[rows.length - 1]); + // process each pattern in the minimatch set + var n = this.minimatch.set.length - if (rowLength || word === '') { - if (rowLength === cols && options.wordWrap === false) { - // If we start with a new word but the current row length equals the length of the columns, add a new row - rows.push(''); - rowLength = 0; - } + // The matches are stored as {<filename>: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) - rows[rows.length - 1] += ' '; - rowLength++; - } + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } - // In 'hard' wrap mode, the length of a line is - // never allowed to extend past 'cols' - if (lengths[i] > cols && options.hard) { - if (rowLength) { - rows.push(''); - } - wrapWord(rows, word, cols); - continue; - } + var self = this + this._processing = 0 - if (rowLength + lengths[i] > cols && rowLength > 0) { - if (options.wordWrap === false && rowLength < cols) { - wrapWord(rows, word, cols); - continue; - } + this._emitQueue = [] + this._processQueue = [] + this.paused = false - rows.push(''); - } + if (this.noprocess) + return this - if (rowLength + lengths[i] > cols && options.wordWrap === false) { - wrapWord(rows, word, cols); - continue; - } + if (n === 0) + return done() - rows[rows.length - 1] += word; - } + var sync = true + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + sync = false - pre = rows.map(r => options.trim === false ? r : r.trim()).join('\n'); + function done () { + --self._processing + if (self._processing <= 0) { + if (sync) { + process.nextTick(function () { + self._finish() + }) + } else { + self._finish() + } + } + } +} - for (const item of Array.from(pre).entries()) { - const i = item[0]; - const char = item[1]; +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return - ret += char; + if (this.realpath && !this._didRealpath) + return this._realpath() - if (ESCAPES.has(char)) { - const code = parseFloat(/\d[^m]*/.exec(pre.slice(i, i + 4))); - escapeCode = code === END_CODE ? null : code; - } + common.finish(this) + this.emit('end', this.found) +} - const code = ESCAPE_CODES.get(Number(escapeCode)); +Glob.prototype._realpath = function () { + if (this._didRealpath) + return - if (escapeCode && code) { - if (pre[i + 1] === '\n') { - ret += wrapAnsi(code); - } else if (char === '\n') { - ret += wrapAnsi(escapeCode); - } - } - } + this._didRealpath = true - return ret; -}; + var n = this.matches.length + if (n === 0) + return this._finish() -// For each newline, invoke the method separately -module.exports = (str, cols, opts) => { - return String(str) - .normalize() - .split('\n') - .map(line => exec(line, cols, opts)) - .join('\n'); -}; + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() -/***/ }), -/* 691 */ -/***/ (function(module, exports, __webpack_require__) { + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + rp.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} -"use strict"; +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} -const stripAnsi = __webpack_require__(692); -const isFullwidthCodePoint = __webpack_require__(694); +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} -module.exports = str => { - if (typeof str !== 'string' || str.length === 0) { - return 0; - } +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} - str = stripAnsi(str); +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} - let width = 0; +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') - for (let i = 0; i < str.length; i++) { - const code = str.codePointAt(i); + if (this.aborted) + return - // Ignore control characters - if (code <= 0x1F || (code >= 0x7F && code <= 0x9F)) { - continue; - } + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } - // Ignore combining characters - if (code >= 0x300 && code <= 0x36F) { - continue; - } + //console.error('PROCESS %d', this._processing, pattern) - // Surrogates - if (code > 0xFFFF) { - i++; - } + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. - width += isFullwidthCodePoint(code) ? 2 : 1; - } + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return - return width; -}; + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } -/***/ }), -/* 692 */ -/***/ (function(module, exports, __webpack_require__) { + var remain = pattern.slice(n) -"use strict"; + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix -const ansiRegex = __webpack_require__(693); + var abs = this._makeAbs(read) -module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} -/***/ }), -/* 693 */ -/***/ (function(module, exports, __webpack_require__) { +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} -"use strict"; +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() -module.exports = () => { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' - ].join('|'); + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' - return new RegExp(pattern, 'g'); -}; + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) -/***/ }), -/* 694 */ -/***/ (function(module, exports, __webpack_require__) { + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() -"use strict"; + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. -/* eslint-disable yoda */ -module.exports = x => { - if (Number.isNaN(x)) { - return false; - } + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) - // code points are derived from: - // http://www.unix.org/Public/UNIDATA/EastAsianWidth.txt - if ( - x >= 0x1100 && ( - x <= 0x115f || // Hangul Jamo - x === 0x2329 || // LEFT-POINTING ANGLE BRACKET - x === 0x232a || // RIGHT-POINTING ANGLE BRACKET - // CJK Radicals Supplement .. Enclosed CJK Letters and Months - (0x2e80 <= x && x <= 0x3247 && x !== 0x303f) || - // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A - (0x3250 <= x && x <= 0x4dbf) || - // CJK Unified Ideographs .. Yi Radicals - (0x4e00 <= x && x <= 0xa4c6) || - // Hangul Jamo Extended-A - (0xa960 <= x && x <= 0xa97c) || - // Hangul Syllables - (0xac00 <= x && x <= 0xd7a3) || - // CJK Compatibility Ideographs - (0xf900 <= x && x <= 0xfaff) || - // Vertical Forms - (0xfe10 <= x && x <= 0xfe19) || - // CJK Compatibility Forms .. Small Form Variants - (0xfe30 <= x && x <= 0xfe6b) || - // Halfwidth and Fullwidth Forms - (0xff01 <= x && x <= 0xff60) || - (0xffe0 <= x && x <= 0xffe6) || - // Kana Supplement - (0x1b000 <= x && x <= 0x1b001) || - // Enclosed Ideographic Supplement - (0x1f200 <= x && x <= 0x1f251) || - // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane - (0x20000 <= x && x <= 0x3fffd) - ) - ) { - return true; - } + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } - return false; -}; + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} -/***/ }), -/* 695 */ -/***/ (function(module, exports, __webpack_require__) { +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return -"use strict"; + if (isIgnored(this, e)) + return -const ansiRegex = __webpack_require__(696); + if (this.paused) { + this._emitQueue.push([index, e]) + return + } -module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; + var abs = isAbsolute(e) ? e : this._makeAbs(e) + if (this.mark) + e = this._mark(e) -/***/ }), -/* 696 */ -/***/ (function(module, exports, __webpack_require__) { + if (this.absolute) + e = abs -"use strict"; + if (this.matches[index][e]) + return + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } -module.exports = () => { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))' - ].join('|'); + this.matches[index][e] = true - return new RegExp(pattern, 'g'); -}; + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + this.emit('match', e) +} -/***/ }), -/* 697 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "renderProjectsTree", function() { return renderProjectsTree; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* - * 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. - */ + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) -const projectKey = Symbol('__project'); -function renderProjectsTree(rootPath, projects) { - const projectsTree = buildProjectsTree(rootPath, projects); - return treeToString(createTreeStructure(projectsTree)); -} + if (lstatcb) + fs.lstat(abs, lstatcb) -function treeToString(tree) { - return [tree.name].concat(childrenToStrings(tree.children, '')).join('\n'); -} + function lstatcb_ (er, lstat) { + if (er && er.code === 'ENOENT') + return cb() -function childrenToStrings(tree, treePrefix) { - if (tree === undefined) { - return []; - } + var isSym = lstat && lstat.isSymbolicLink() + self.symlinks[abs] = isSym - let strings = []; - tree.forEach((node, index) => { - const isLastNode = tree.length - 1 === index; - const nodePrefix = isLastNode ? '└── ' : '├── '; - const childPrefix = isLastNode ? ' ' : '│ '; - const childrenPrefix = treePrefix + childPrefix; - strings.push(`${treePrefix}${nodePrefix}${node.name}`); - strings = strings.concat(childrenToStrings(node.children, childrenPrefix)); - }); - return strings; + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } } -function createTreeStructure(tree) { - let name; - const children = []; +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return - for (const [dir, project] of tree.entries()) { - // This is a leaf node (aka a project) - if (typeof project === 'string') { - name = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(project); - continue; - } // If there's only one project and the key indicates it's a leaf node, we - // know that we're at a package folder that contains a package.json, so we - // "inline it" so we don't get unnecessary levels, i.e. we'll just see - // `foo` instead of `foo -> foo`. + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) - if (project.size === 1 && project.has(projectKey)) { - const projectName = project.get(projectKey); - children.push({ - children: [], - name: dirOrProjectName(dir, projectName) - }); - continue; - } + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() - const subtree = createTreeStructure(project); // If the name is specified, we know there's a package at the "root" of the - // subtree itself. + if (Array.isArray(c)) + return cb(null, c) + } - if (subtree.name !== undefined) { - const projectName = subtree.name; - children.push({ - children: subtree.children, - name: dirOrProjectName(dir, projectName) - }); - continue; - } // Special-case whenever we have one child, so we don't get unnecessary - // folders in the output. E.g. instead of `foo -> bar -> baz` we get - // `foo/bar/baz` instead. + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} - if (subtree.children && subtree.children.length === 1) { - const child = subtree.children[0]; - const newName = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(dir.toString(), child.name)); - children.push({ - children: child.children, - name: newName - }); - continue; - } +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return - children.push({ - children: subtree.children, - name: chalk__WEBPACK_IMPORTED_MODULE_0___default.a.dim(dir.toString()) - }); + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } } - return { - name, - children - }; + this.cache[abs] = entries + return cb(null, entries) } -function dirOrProjectName(dir, projectName) { - return dir === projectName ? chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(dir) : chalk__WEBPACK_IMPORTED_MODULE_0___default.a`{dim ${dir.toString()} ({reset.green ${projectName}})}`; -} +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return -function buildProjectsTree(rootPath, projects) { - const tree = new Map(); + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + this.emit('error', error) + this.abort() + } + break - for (const project of projects.values()) { - if (rootPath === project.path) { - tree.set(projectKey, project.name); - } else { - const relativeProjectPath = path__WEBPACK_IMPORTED_MODULE_1___default.a.relative(rootPath, project.path); - addProjectToTree(tree, relativeProjectPath.split(path__WEBPACK_IMPORTED_MODULE_1___default.a.sep), project); - } + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break } - return tree; + return cb() } -function addProjectToTree(tree, pathParts, project) { - if (pathParts.length === 0) { - tree.set(projectKey, project.name); - } else { - const [currentDir, ...rest] = pathParts; - - if (!tree.has(currentDir)) { - tree.set(currentDir, new Map()); - } - - const subtree = tree.get(currentDir); - addProjectToTree(subtree, rest, project); - } +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) } -/***/ }), -/* 698 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(699); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(501); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); -function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) -/* - * 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. - */ + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + var isSym = this.symlinks[abs] + var len = entries.length + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue -/** - * Helper class for dealing with a set of projects as children of - * the Kibana project. The kbn/pm is currently implemented to be - * more generic, where everything is an operation of generic projects, - * but that leads to exceptions where we need the kibana project and - * do things like `project.get('kibana')!`. - * - * Using this helper we can restructre the generic list of projects - * as a Kibana object which encapulates all the projects in the - * workspace and knows about the root Kibana project. - */ + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) -class Kibana { - static async loadFrom(rootPath) { - return new Kibana((await Object(_projects__WEBPACK_IMPORTED_MODULE_2__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ - rootPath - })))); + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) } - constructor(allWorkspaceProjects) { - this.allWorkspaceProjects = allWorkspaceProjects; - - _defineProperty(this, "kibanaProject", void 0); + cb() +} - const kibanaProject = allWorkspaceProjects.get('kibana'); +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { - if (!kibanaProject) { - throw new TypeError('Unable to create Kibana object without all projects, including the Kibana project.'); - } + //console.error('ps2', prefix, exists) - this.kibanaProject = kibanaProject; - } - /** make an absolute path by resolving subPath relative to the kibana repo */ + if (!this.matches[index]) + this.matches[index] = Object.create(null) + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() - getAbsolute(...subPath) { - return path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(this.kibanaProject.path, ...subPath); + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } } - /** convert an absolute path to a relative path, relative to the kibana repo */ + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') - getRelative(absolute) { - return path__WEBPACK_IMPORTED_MODULE_0___default.a.relative(this.kibanaProject.path, absolute); - } - /** get a copy of the map of all projects in the kibana workspace */ - + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} - getAllProjects() { - return new Map(this.allWorkspaceProjects); - } - /** determine if a project with the given name exists */ +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + if (f.length > this.maxLength) + return cb() - hasProject(name) { - return this.allWorkspaceProjects.has(name); - } - /** get a specific project, throws if the name is not known (use hasProject() first) */ + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (Array.isArray(c)) + c = 'DIR' - getProject(name) { - const project = this.allWorkspaceProjects.get(name); + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) - if (!project) { - throw new Error(`No package with name "${name}" in the workspace`); - } + if (needDir && c === 'FILE') + return cb() - return project; + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. } - /** get a project and all of the projects it depends on in a ProjectMap */ - - getProjectAndDeps(name) { - const project = this.getProject(name); - return Object(_projects__WEBPACK_IMPORTED_MODULE_2__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } } - /** filter the projects to just those matching certain paths/include/exclude tags */ - - - getFilteredProjects(options) { - const allProjects = this.getAllProjects(); - const filteredProjects = new Map(); - const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); - const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(_objectSpread({}, options, { - rootPath: this.kibanaProject.path - })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); - const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_1___default()(pkgJsonPaths, filteredPkgJsonGlobs); - for (const project of allProjects.values()) { - const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); - const notExcluded = !options.exclude.includes(project.name); - const isIncluded = !options.include.length || options.include.includes(project.name); + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) - if (pathMatches && notExcluded && isIncluded) { - filteredProjects.set(project.name, project); - } + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) } - - return filteredProjects; } - } -/***/ }), -/* 699 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const minimatch = __webpack_require__(505); -const arrayUnion = __webpack_require__(700); -const arrayDiffer = __webpack_require__(701); -const arrify = __webpack_require__(702); +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return cb() + } -module.exports = (list, patterns, options = {}) => { - list = arrify(list); - patterns = arrify(patterns); + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat - if (list.length === 0 || patterns.length === 0) { - return []; - } + if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) + return cb(null, false, stat) - return patterns.reduce((result, pattern) => { - let process = arrayUnion; + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c - if (pattern[0] === '!') { - pattern = pattern.slice(1); - process = arrayDiffer; - } + if (needDir && c === 'FILE') + return cb() - return process(result, minimatch.match(list, pattern, options)); - }, []); -}; + return cb(null, c, stat) +} /***/ }), -/* 700 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +try { + var util = __webpack_require__(29); + /* istanbul ignore next */ + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + /* istanbul ignore next */ + module.exports = __webpack_require__(714); +} -module.exports = (...arguments_) => { - return [...new Set([].concat(...arguments_))]; -}; +/***/ }), +/* 714 */ +/***/ (function(module, exports) { + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }) + } + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } + } +} /***/ }), -/* 701 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +module.exports = globSync +globSync.GlobSync = GlobSync +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var Glob = __webpack_require__(712).Glob +var util = __webpack_require__(29) +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var common = __webpack_require__(716) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored -const arrayDiffer = (array, ...values) => { - const rest = new Set([].concat(...values)); - return array.filter(element => !rest.has(element)); -}; +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -module.exports = arrayDiffer; + return new GlobSync(pattern, options).found +} +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') -/***/ }), -/* 702 */ -/***/ (function(module, exports, __webpack_require__) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -"use strict"; + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) + setopts(this, pattern, options) -const arrify = value => { - if (value === null || value === undefined) { - return []; - } + if (this.noprocess) + return this - if (Array.isArray(value)) { - return value; - } + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} - if (typeof value === 'string') { - return [value]; - } +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = rp.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} - if (typeof value[Symbol.iterator] === 'function') { - return [...value]; - } - return [value]; -}; +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) -module.exports = arrify; + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return -/***/ }), -/* 703 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(704); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } -/* harmony import */ var _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(914); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _prepare_project_dependencies__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); + var remain = pattern.slice(n) -/* - * 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. - */ + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + var abs = this._makeAbs(read) + //if ignored, skip processing + if (childrenIgnored(this, read)) + return -/***/ }), -/* 704 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(705); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(588); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(517); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(501); -/* - * 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. - */ +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) -async function buildProductionProjects({ - kibanaRoot, - buildRoot, - onlyOSS -}) { - const projects = await getProductionProjects(kibanaRoot, onlyOSS); - const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["buildProjectGraph"])(projects); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["topologicallyBatchProjects"])(projects, projectGraph); - const projectNames = [...projects.values()].map(project => project.name); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(`Preparing production build for [${projectNames.join(', ')}]`); + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } - for (const batch of batchedProjects) { - for (const project of batch) { - await deleteTarget(project); - await buildProject(project); - await copyToBuild(project, kibanaRoot, buildRoot); + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) } + // This was the last one, and no stats were needed + return + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) } } -/** - * Returns the subset of projects that should be built into the production - * bundle. As we copy these into Kibana's `node_modules` during the build step, - * and let Kibana's build process be responsible for installing dependencies, - * we only include Kibana's transitive _production_ dependencies. If onlyOSS - * is supplied, we omit projects with build.oss in their package.json set to false. - */ -async function getProductionProjects(rootPath, onlyOSS) { - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ - rootPath - }); - const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["getProjects"])(rootPath, projectPaths); - const projectsSubset = [projects.get('kibana')]; - if (projects.has('x-pack')) { - projectsSubset.push(projects.get('x-pack')); - } +GlobSync.prototype._emitMatch = function (index, e) { + if (isIgnored(this, e)) + return - const productionProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["includeTransitiveProjects"])(projectsSubset, projects, { - onlyProductionDependencies: true - }); // We remove Kibana, as we're already building Kibana + var abs = this._makeAbs(e) - productionProjects.delete('kibana'); + if (this.mark) + e = this._mark(e) - if (onlyOSS) { - productionProjects.forEach(project => { - if (project.getBuildConfig().oss === false) { - productionProjects.delete(project.json.name); - } - }); + if (this.absolute) { + e = abs } - return productionProjects; -} - -async function deleteTarget(project) { - const targetDir = project.targetLocation; + if (this.matches[index][e]) + return - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(targetDir)) { - await del__WEBPACK_IMPORTED_MODULE_1___default()(targetDir, { - force: true - }); + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return } -} -async function buildProject(project) { - if (project.hasScript('build')) { - await project.runScript('build'); - } + this.matches[index][e] = true + + if (this.stat) + this._stat(e) } -/** - * Copy all the project's files from its "intermediate build directory" and - * into the build. The intermediate directory can either be the root of the - * project or some other location defined in the project's `package.json`. - * - * When copying all the files into the build, we exclude `node_modules` because - * we want the Kibana build to be responsible for actually installing all - * dependencies. The primary reason for allowing the Kibana build process to - * manage dependencies is that it will "dedupe" them, so we don't include - * unnecessary copies of dependencies. - */ -async function copyToBuild(project, kibanaRoot, buildRoot) { - // We want the package to have the same relative location within the build - const relativeProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(kibanaRoot, project.path); - const buildProjectPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(buildRoot, relativeProjectPath); - await cpy__WEBPACK_IMPORTED_MODULE_0___default()(['**/*', '!node_modules/**'], buildProjectPath, { - cwd: project.getIntermediateBuildDirectory(), - dot: true, - nodir: true, - parents: true - }); // If a project is using an intermediate build directory, we special-case our - // handling of `package.json`, as the project build process might have copied - // (a potentially modified) `package.json` into the intermediate build - // directory already. If so, we want to use that `package.json` as the basis - // for creating the production-ready `package.json`. If it's not present in - // the intermediate build, we fall back to using the project's already defined - // `package.json`. +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) - const packageJson = (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isFile"])(Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(buildProjectPath, 'package.json'))) ? await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_6__["readPackageJson"])(buildProjectPath) : project.json; - await Object(_utils_package_json__WEBPACK_IMPORTED_MODULE_6__["writePackageJson"])(buildProjectPath, packageJson); -} + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er.code === 'ENOENT') { + // lstat failed, doesn't exist + return null + } + } -/***/ }), -/* 705 */ -/***/ (function(module, exports, __webpack_require__) { + var isSym = lstat && lstat.isSymbolicLink() + this.symlinks[abs] = isSym -"use strict"; + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) -const EventEmitter = __webpack_require__(379); -const path = __webpack_require__(16); -const arrify = __webpack_require__(706); -const globby = __webpack_require__(707); -const cpFile = __webpack_require__(905); -const CpyError = __webpack_require__(912); + return entries +} -const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries -const preprocessDestPath = (srcPath, dest, options) => { - let basename = path.basename(srcPath); - const dirname = path.dirname(srcPath); + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) - if (typeof options.rename === 'string') { - basename = options.rename; - } else if (typeof options.rename === 'function') { - basename = options.rename(basename); - } + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null - if (options.cwd) { - dest = path.resolve(options.cwd, dest); - } + if (Array.isArray(c)) + return c + } - if (options.parents) { - return path.join(dest, dirname, basename); - } + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } +} - return path.join(dest, basename); -}; +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } -const cpy = (src, dest, options = {}) => { - src = arrify(src); + this.cache[abs] = entries - const progressEmitter = new EventEmitter(); + // mark and cache dir-ness + return entries +} - if (src.length === 0 || !dest) { - const promise = Promise.reject(new CpyError('`files` and `destination` required')); - promise.on = (...args) => { - progressEmitter.on(...args); - return promise; - }; +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + throw error + } + break - return promise; - } + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break - const copyStatus = new Map(); - let completedFiles = 0; - let completedSize = 0; + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } +} - const promise = globby(src, options) - .catch(error => { - throw new CpyError(`Cannot glob \`${src}\`: ${error.message}`, error); - }) - .then(files => { - if (files.length === 0) { - progressEmitter.emit('progress', { - totalFiles: 0, - percent: 1, - completedFiles: 0, - completedSize: 0 - }); - } +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { - return Promise.all(files.map(srcPath => { - const from = preprocessSrcPath(srcPath, options); - const to = preprocessDestPath(srcPath, dest, options); + var entries = this._readdir(abs, inGlobStar) - return cpFile(from, to, options) - .on('progress', event => { - const fileStatus = copyStatus.get(event.src) || {written: 0, percent: 0}; + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return - if (fileStatus.written !== event.written || fileStatus.percent !== event.percent) { - completedSize -= fileStatus.written; - completedSize += event.written; + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) - if (event.percent === 1 && fileStatus.percent !== 1) { - completedFiles++; - } + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) - copyStatus.set(event.src, {written: event.written, percent: event.percent}); + var len = entries.length + var isSym = this.symlinks[abs] - progressEmitter.emit('progress', { - totalFiles: files.length, - percent: completedFiles / files.length, - completedFiles, - completedSize - }); - } - }) - .then(() => to) - .catch(error => { - throw new CpyError(`Cannot copy from \`${from}\` to \`${to}\`: ${error.message}`, error); - }); - })); - }); + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return - promise.on = (...args) => { - progressEmitter.on(...args); - return promise; - }; + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue - return promise; -}; + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) -module.exports = cpy; -// TODO: Remove this for the next major release -module.exports.default = cpy; + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) -/***/ }), -/* 706 */ -/***/ (function(module, exports, __webpack_require__) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) -"use strict"; + // If it doesn't exist, then just mark the lack of results + if (!exists) + return -module.exports = function (val) { - if (val === null || val === undefined) { - return []; - } + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } - return Array.isArray(val) ? val : [val]; -}; + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + // Mark this as a match + this._emitMatch(index, prefix) +} -/***/ }), -/* 707 */ -/***/ (function(module, exports, __webpack_require__) { +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' -"use strict"; + if (f.length > this.maxLength) + return false -const fs = __webpack_require__(23); -const arrayUnion = __webpack_require__(708); -const glob = __webpack_require__(502); -const fastGlob = __webpack_require__(710); -const dirGlob = __webpack_require__(898); -const gitignore = __webpack_require__(901); + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] -const DEFAULT_FILTER = () => false; + if (Array.isArray(c)) + c = 'DIR' -const isNegative = pattern => pattern[0] === '!'; + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c -const assertPatternsInput = patterns => { - if (!patterns.every(x => typeof x === 'string')) { - throw new TypeError('Patterns must be a string or an array of strings'); - } -}; + if (needDir && c === 'FILE') + return false -const checkCwdOption = options => { - if (options && options.cwd && !fs.statSync(options.cwd).isDirectory()) { - throw new Error('The `cwd` option must be a path to a directory'); - } -}; + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } -const generateGlobTasks = (patterns, taskOptions) => { - patterns = arrayUnion([].concat(patterns)); - assertPatternsInput(patterns); - checkCwdOption(taskOptions); + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return false + } + } - const globTasks = []; + if (lstat && lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } - taskOptions = Object.assign({ - ignore: [], - expandDirectories: true - }, taskOptions); + this.statCache[abs] = stat - patterns.forEach((pattern, i) => { - if (isNegative(pattern)) { - return; - } + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' - const ignore = patterns - .slice(i) - .filter(isNegative) - .map(pattern => pattern.slice(1)); + this.cache[abs] = this.cache[abs] || c - const options = Object.assign({}, taskOptions, { - ignore: taskOptions.ignore.concat(ignore) - }); + if (needDir && c === 'FILE') + return false - globTasks.push({pattern, options}); - }); + return c +} - return globTasks; -}; +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} -const globDirs = (task, fn) => { - let options = {}; - if (task.options.cwd) { - options.cwd = task.options.cwd; - } +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} - if (Array.isArray(task.options.expandDirectories)) { - options = Object.assign(options, {files: task.options.expandDirectories}); - } else if (typeof task.options.expandDirectories === 'object') { - options = Object.assign(options, task.options.expandDirectories); - } - return fn(task.pattern, options); -}; +/***/ }), +/* 716 */ +/***/ (function(module, exports, __webpack_require__) { -const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored -const globToTask = task => glob => { - const {options} = task; - if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { - options.ignore = dirGlob.sync(options.ignore); - } +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} - return { - pattern: glob, - options - }; -}; +var path = __webpack_require__(16) +var minimatch = __webpack_require__(505) +var isAbsolute = __webpack_require__(511) +var Minimatch = minimatch.Minimatch -const globby = (patterns, options) => { - let globTasks; +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} - try { - globTasks = generateGlobTasks(patterns, options); - } catch (error) { - return Promise.reject(error); - } +function alphasort (a, b) { + return a.localeCompare(b) +} - const getTasks = Promise.all(globTasks.map(task => Promise.resolve(getPattern(task, dirGlob)) - .then(globs => Promise.all(globs.map(globToTask(task)))) - )) - .then(tasks => arrayUnion(...tasks)); +function setupIgnores (self, options) { + self.ignore = options.ignore || [] - const getFilter = () => { - return Promise.resolve( - options && options.gitignore ? - gitignore({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER - ); - }; + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] - return getFilter() - .then(filter => { - return getTasks - .then(tasks => Promise.all(tasks.map(task => fastGlob(task.pattern, task.options)))) - .then(paths => arrayUnion(...paths)) - .then(paths => paths.filter(p => !filter(p))); - }); -}; + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} -module.exports = globby; -// TODO: Remove this for the next major release -module.exports.default = globby; +// ignore patterns are always in dot:true mode. +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern, { dot: true }) + } -module.exports.sync = (patterns, options) => { - const globTasks = generateGlobTasks(patterns, options); + return { + matcher: new Minimatch(pattern, { dot: true }), + gmatcher: gmatcher + } +} - const getFilter = () => { - return options && options.gitignore ? - gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; - }; +function setopts (self, pattern, options) { + if (!options) + options = {} - const tasks = globTasks.reduce((tasks, task) => { - const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); - return tasks.concat(newTask); - }, []); + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } - const filter = getFilter(); - return tasks.reduce( - (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), - [] - ).filter(p => !filter(p)); -}; + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + self.absolute = !!options.absolute -module.exports.generateGlobTasks = generateGlobTasks; + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) -module.exports.hasMagic = (patterns, options) => [] - .concat(patterns) - .some(pattern => glob.hasMagic(pattern, options)); + setupIgnores(self, options) -module.exports.gitignore = gitignore; + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = path.resolve(options.cwd) + self.changedCwd = self.cwd !== cwd + } + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") -/***/ }), -/* 708 */ -/***/ (function(module, exports, __webpack_require__) { + // TODO: is an absolute `cwd` supposed to be resolved against `root`? + // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') + self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd) + if (process.platform === "win32") + self.cwdAbs = self.cwdAbs.replace(/\\/g, "/") + self.nomount = !!options.nomount -"use strict"; + // disable comments and negation in Minimatch. + // Note that they are not supported in Glob itself anyway. + options.nonegate = true + options.nocomment = true -var arrayUniq = __webpack_require__(709); + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} -module.exports = function () { - return arrayUniq([].concat.apply([], arguments)); -}; +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } -/***/ }), -/* 709 */ -/***/ (function(module, exports, __webpack_require__) { + if (!nou) + all = Object.keys(all) -"use strict"; + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + var notDir = !(/\/$/.test(e)) + var c = self.cache[e] || self.cache[makeAbs(self, e)] + if (notDir && c) + notDir = c !== 'DIR' && !Array.isArray(c) + return notDir + }) + } + } -// there's 3 implementations written in increasing order of efficiency + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) -// 1 - no Set type is defined -function uniqNoSet(arr) { - var ret = []; + self.found = all +} - for (var i = 0; i < arr.length; i++) { - if (ret.indexOf(arr[i]) === -1) { - ret.push(arr[i]); - } - } +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' - return ret; -} + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) -// 2 - a simple Set type is defined -function uniqSet(arr) { - var seen = new Set(); - return arr.filter(function (el) { - if (!seen.has(el)) { - seen.add(el); - return true; - } + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } - return false; - }); + return m } -// 3 - a standard Set type is defined and it has a forEach method -function uniqSetWithForEach(arr) { - var ret = []; +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } - (new Set(arr)).forEach(function (el) { - ret.push(el); - }); + if (process.platform === 'win32') + abs = abs.replace(/\\/g, '/') - return ret; + return abs } -// V8 currently has a broken implementation -// https://github.com/joyent/node/issues/8449 -function doesForEachActuallyWork() { - var ret = false; - (new Set([true])).forEach(function (el) { - ret = el; - }); +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false - return ret === true; + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) } -if ('Set' in global) { - if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { - module.exports = uniqSetWithForEach; - } else { - module.exports = uniqSet; - } -} else { - module.exports = uniqNoSet; +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false + + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) } /***/ }), -/* 710 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(711); +const pkg = __webpack_require__(718); module.exports = pkg.async; module.exports.default = pkg.async; @@ -80438,19 +82802,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 711 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(712); -var taskManager = __webpack_require__(713); -var reader_async_1 = __webpack_require__(869); -var reader_stream_1 = __webpack_require__(893); -var reader_sync_1 = __webpack_require__(894); -var arrayUtils = __webpack_require__(896); -var streamUtils = __webpack_require__(897); +var optionsManager = __webpack_require__(719); +var taskManager = __webpack_require__(720); +var reader_async_1 = __webpack_require__(876); +var reader_stream_1 = __webpack_require__(900); +var reader_sync_1 = __webpack_require__(901); +var arrayUtils = __webpack_require__(903); +var streamUtils = __webpack_require__(904); /** * Synchronous API. */ @@ -80516,7 +82880,7 @@ function isString(source) { /***/ }), -/* 712 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80554,13 +82918,13 @@ exports.prepare = prepare; /***/ }), -/* 713 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(714); +var patternUtils = __webpack_require__(721); /** * Generate tasks based on parent directory of each pattern. */ @@ -80651,16 +83015,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 714 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var globParent = __webpack_require__(715); -var isGlob = __webpack_require__(718); -var micromatch = __webpack_require__(719); +var globParent = __webpack_require__(722); +var isGlob = __webpack_require__(725); +var micromatch = __webpack_require__(726); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -80806,15 +83170,15 @@ exports.matchAny = matchAny; /***/ }), -/* 715 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(16); -var isglob = __webpack_require__(716); -var pathDirname = __webpack_require__(717); +var isglob = __webpack_require__(723); +var pathDirname = __webpack_require__(724); var isWin32 = __webpack_require__(11).platform() === 'win32'; module.exports = function globParent(str) { @@ -80837,7 +83201,7 @@ module.exports = function globParent(str) { /***/ }), -/* 716 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -80847,7 +83211,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(602); +var isExtglob = __webpack_require__(607); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -80868,7 +83232,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 717 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81018,7 +83382,7 @@ module.exports.win32 = win32; /***/ }), -/* 718 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -81028,7 +83392,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(602); +var isExtglob = __webpack_require__(607); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -81070,7 +83434,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 719 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81081,18 +83445,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(29); -var braces = __webpack_require__(720); -var toRegex = __webpack_require__(822); -var extend = __webpack_require__(830); +var braces = __webpack_require__(727); +var toRegex = __webpack_require__(829); +var extend = __webpack_require__(837); /** * Local dependencies */ -var compilers = __webpack_require__(833); -var parsers = __webpack_require__(865); -var cache = __webpack_require__(866); -var utils = __webpack_require__(867); +var compilers = __webpack_require__(840); +var parsers = __webpack_require__(872); +var cache = __webpack_require__(873); +var utils = __webpack_require__(874); var MAX_LENGTH = 1024 * 64; /** @@ -81954,7 +84318,7 @@ module.exports = micromatch; /***/ }), -/* 720 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81964,18 +84328,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(721); -var unique = __webpack_require__(733); -var extend = __webpack_require__(730); +var toRegex = __webpack_require__(728); +var unique = __webpack_require__(740); +var extend = __webpack_require__(737); /** * Local dependencies */ -var compilers = __webpack_require__(734); -var parsers = __webpack_require__(749); -var Braces = __webpack_require__(759); -var utils = __webpack_require__(735); +var compilers = __webpack_require__(741); +var parsers = __webpack_require__(756); +var Braces = __webpack_require__(766); +var utils = __webpack_require__(742); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -82279,15 +84643,15 @@ module.exports = braces; /***/ }), -/* 721 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(722); -var extend = __webpack_require__(730); -var not = __webpack_require__(732); +var define = __webpack_require__(729); +var extend = __webpack_require__(737); +var not = __webpack_require__(739); var MAX_LENGTH = 1024 * 64; /** @@ -82434,7 +84798,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 722 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82447,7 +84811,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(723); +var isDescriptor = __webpack_require__(730); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -82472,7 +84836,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 723 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82485,9 +84849,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(724); -var isAccessor = __webpack_require__(725); -var isData = __webpack_require__(728); +var typeOf = __webpack_require__(731); +var isAccessor = __webpack_require__(732); +var isData = __webpack_require__(735); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -82501,7 +84865,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 724 */ +/* 731 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -82654,7 +85018,7 @@ function isBuffer(val) { /***/ }), -/* 725 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82667,7 +85031,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(726); +var typeOf = __webpack_require__(733); // accessor descriptor properties var accessor = { @@ -82730,10 +85094,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 726 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(727); +var isBuffer = __webpack_require__(734); var toString = Object.prototype.toString; /** @@ -82852,7 +85216,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 727 */ +/* 734 */ /***/ (function(module, exports) { /*! @@ -82879,7 +85243,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 728 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82892,7 +85256,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(729); +var typeOf = __webpack_require__(736); // data descriptor properties var data = { @@ -82941,10 +85305,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 729 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(727); +var isBuffer = __webpack_require__(734); var toString = Object.prototype.toString; /** @@ -83063,13 +85427,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 730 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(731); +var isObject = __webpack_require__(738); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -83103,7 +85467,7 @@ function hasOwn(obj, key) { /***/ }), -/* 731 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83123,13 +85487,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 732 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(730); +var extend = __webpack_require__(737); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -83196,7 +85560,7 @@ module.exports = toRegex; /***/ }), -/* 733 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83246,13 +85610,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 734 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(735); +var utils = __webpack_require__(742); module.exports = function(braces, options) { braces.compiler @@ -83535,25 +85899,25 @@ function hasQueue(node) { /***/ }), -/* 735 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(736); +var splitString = __webpack_require__(743); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(730); -utils.flatten = __webpack_require__(742); -utils.isObject = __webpack_require__(740); -utils.fillRange = __webpack_require__(743); -utils.repeat = __webpack_require__(748); -utils.unique = __webpack_require__(733); +utils.extend = __webpack_require__(737); +utils.flatten = __webpack_require__(749); +utils.isObject = __webpack_require__(747); +utils.fillRange = __webpack_require__(750); +utils.repeat = __webpack_require__(755); +utils.unique = __webpack_require__(740); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -83885,7 +86249,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 736 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83898,7 +86262,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(737); +var extend = __webpack_require__(744); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -84063,14 +86427,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 737 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(738); -var assignSymbols = __webpack_require__(741); +var isExtendable = __webpack_require__(745); +var assignSymbols = __webpack_require__(748); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -84130,7 +86494,7 @@ function isEnum(obj, key) { /***/ }), -/* 738 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84143,7 +86507,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(739); +var isPlainObject = __webpack_require__(746); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -84151,7 +86515,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 739 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84164,7 +86528,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(740); +var isObject = __webpack_require__(747); function isObjectObject(o) { return isObject(o) === true @@ -84195,7 +86559,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 740 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84214,7 +86578,7 @@ module.exports = function isObject(val) { /***/ }), -/* 741 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84261,7 +86625,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 742 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84290,7 +86654,7 @@ function flat(arr, res) { /***/ }), -/* 743 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84304,10 +86668,10 @@ function flat(arr, res) { var util = __webpack_require__(29); -var isNumber = __webpack_require__(744); -var extend = __webpack_require__(730); -var repeat = __webpack_require__(746); -var toRegex = __webpack_require__(747); +var isNumber = __webpack_require__(751); +var extend = __webpack_require__(737); +var repeat = __webpack_require__(753); +var toRegex = __webpack_require__(754); /** * Return a range of numbers or letters. @@ -84505,7 +86869,7 @@ module.exports = fillRange; /***/ }), -/* 744 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84518,7 +86882,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(745); +var typeOf = __webpack_require__(752); module.exports = function isNumber(num) { var type = typeOf(num); @@ -84534,10 +86898,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 745 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(727); +var isBuffer = __webpack_require__(734); var toString = Object.prototype.toString; /** @@ -84656,7 +87020,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 746 */ +/* 753 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84733,7 +87097,7 @@ function repeat(str, num) { /***/ }), -/* 747 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84746,8 +87110,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(746); -var isNumber = __webpack_require__(744); +var repeat = __webpack_require__(753); +var isNumber = __webpack_require__(751); var cache = {}; function toRegexRange(min, max, options) { @@ -85034,7 +87398,7 @@ module.exports = toRegexRange; /***/ }), -/* 748 */ +/* 755 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85059,14 +87423,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 749 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(750); -var utils = __webpack_require__(735); +var Node = __webpack_require__(757); +var utils = __webpack_require__(742); /** * Braces parsers @@ -85426,15 +87790,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 750 */ +/* 757 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(740); -var define = __webpack_require__(751); -var utils = __webpack_require__(758); +var isObject = __webpack_require__(747); +var define = __webpack_require__(758); +var utils = __webpack_require__(765); var ownNames; /** @@ -85925,7 +88289,7 @@ exports = module.exports = Node; /***/ }), -/* 751 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85938,7 +88302,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(752); +var isDescriptor = __webpack_require__(759); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -85963,7 +88327,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 752 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -85976,9 +88340,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(753); -var isAccessor = __webpack_require__(754); -var isData = __webpack_require__(756); +var typeOf = __webpack_require__(760); +var isAccessor = __webpack_require__(761); +var isData = __webpack_require__(763); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -85992,7 +88356,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 753 */ +/* 760 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -86127,7 +88491,7 @@ function isBuffer(val) { /***/ }), -/* 754 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86140,7 +88504,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(755); +var typeOf = __webpack_require__(762); // accessor descriptor properties var accessor = { @@ -86203,7 +88567,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 755 */ +/* 762 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -86338,7 +88702,7 @@ function isBuffer(val) { /***/ }), -/* 756 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86351,7 +88715,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(757); +var typeOf = __webpack_require__(764); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -86394,7 +88758,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 757 */ +/* 764 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -86529,13 +88893,13 @@ function isBuffer(val) { /***/ }), -/* 758 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(745); +var typeOf = __webpack_require__(752); var utils = module.exports; /** @@ -87555,17 +89919,17 @@ function assert(val, message) { /***/ }), -/* 759 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(730); -var Snapdragon = __webpack_require__(760); -var compilers = __webpack_require__(734); -var parsers = __webpack_require__(749); -var utils = __webpack_require__(735); +var extend = __webpack_require__(737); +var Snapdragon = __webpack_require__(767); +var compilers = __webpack_require__(741); +var parsers = __webpack_require__(756); +var utils = __webpack_require__(742); /** * Customize Snapdragon parser and renderer @@ -87666,17 +90030,17 @@ module.exports = Braces; /***/ }), -/* 760 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(761); -var define = __webpack_require__(722); -var Compiler = __webpack_require__(790); -var Parser = __webpack_require__(819); -var utils = __webpack_require__(799); +var Base = __webpack_require__(768); +var define = __webpack_require__(729); +var Compiler = __webpack_require__(797); +var Parser = __webpack_require__(826); +var utils = __webpack_require__(806); var regexCache = {}; var cache = {}; @@ -87847,20 +90211,20 @@ module.exports.Parser = Parser; /***/ }), -/* 761 */ +/* 768 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var define = __webpack_require__(762); -var CacheBase = __webpack_require__(763); -var Emitter = __webpack_require__(764); -var isObject = __webpack_require__(740); -var merge = __webpack_require__(781); -var pascal = __webpack_require__(784); -var cu = __webpack_require__(785); +var define = __webpack_require__(769); +var CacheBase = __webpack_require__(770); +var Emitter = __webpack_require__(771); +var isObject = __webpack_require__(747); +var merge = __webpack_require__(788); +var pascal = __webpack_require__(791); +var cu = __webpack_require__(792); /** * Optionally define a custom `cache` namespace to use. @@ -88289,7 +90653,7 @@ module.exports.namespace = namespace; /***/ }), -/* 762 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88302,7 +90666,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(752); +var isDescriptor = __webpack_require__(759); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -88327,21 +90691,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 763 */ +/* 770 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(740); -var Emitter = __webpack_require__(764); -var visit = __webpack_require__(765); -var toPath = __webpack_require__(768); -var union = __webpack_require__(769); -var del = __webpack_require__(773); -var get = __webpack_require__(771); -var has = __webpack_require__(778); -var set = __webpack_require__(772); +var isObject = __webpack_require__(747); +var Emitter = __webpack_require__(771); +var visit = __webpack_require__(772); +var toPath = __webpack_require__(775); +var union = __webpack_require__(776); +var del = __webpack_require__(780); +var get = __webpack_require__(778); +var has = __webpack_require__(785); +var set = __webpack_require__(779); /** * Create a `Cache` constructor that when instantiated will @@ -88595,7 +90959,7 @@ module.exports.namespace = namespace; /***/ }), -/* 764 */ +/* 771 */ /***/ (function(module, exports, __webpack_require__) { @@ -88764,7 +91128,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 765 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88777,8 +91141,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(766); -var mapVisit = __webpack_require__(767); +var visit = __webpack_require__(773); +var mapVisit = __webpack_require__(774); module.exports = function(collection, method, val) { var result; @@ -88801,7 +91165,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 766 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88814,7 +91178,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(740); +var isObject = __webpack_require__(747); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -88841,14 +91205,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 767 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var visit = __webpack_require__(766); +var visit = __webpack_require__(773); /** * Map `visit` over an array of objects. @@ -88885,7 +91249,7 @@ function isObject(val) { /***/ }), -/* 768 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88898,7 +91262,7 @@ function isObject(val) { -var typeOf = __webpack_require__(745); +var typeOf = __webpack_require__(752); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -88925,16 +91289,16 @@ function filter(arr) { /***/ }), -/* 769 */ +/* 776 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(731); -var union = __webpack_require__(770); -var get = __webpack_require__(771); -var set = __webpack_require__(772); +var isObject = __webpack_require__(738); +var union = __webpack_require__(777); +var get = __webpack_require__(778); +var set = __webpack_require__(779); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -88962,7 +91326,7 @@ function arrayify(val) { /***/ }), -/* 770 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -88998,7 +91362,7 @@ module.exports = function union(init) { /***/ }), -/* 771 */ +/* 778 */ /***/ (function(module, exports) { /*! @@ -89054,7 +91418,7 @@ function toString(val) { /***/ }), -/* 772 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89067,10 +91431,10 @@ function toString(val) { -var split = __webpack_require__(736); -var extend = __webpack_require__(730); -var isPlainObject = __webpack_require__(739); -var isObject = __webpack_require__(731); +var split = __webpack_require__(743); +var extend = __webpack_require__(737); +var isPlainObject = __webpack_require__(746); +var isObject = __webpack_require__(738); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -89116,7 +91480,7 @@ function isValidKey(key) { /***/ }), -/* 773 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89129,8 +91493,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(740); -var has = __webpack_require__(774); +var isObject = __webpack_require__(747); +var has = __webpack_require__(781); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -89155,7 +91519,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 774 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89168,9 +91532,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(775); -var hasValues = __webpack_require__(777); -var get = __webpack_require__(771); +var isObject = __webpack_require__(782); +var hasValues = __webpack_require__(784); +var get = __webpack_require__(778); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -89181,7 +91545,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 775 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89194,7 +91558,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(776); +var isArray = __webpack_require__(783); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -89202,7 +91566,7 @@ module.exports = function isObject(val) { /***/ }), -/* 776 */ +/* 783 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -89213,7 +91577,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 777 */ +/* 784 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89256,7 +91620,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 778 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89269,9 +91633,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(740); -var hasValues = __webpack_require__(779); -var get = __webpack_require__(771); +var isObject = __webpack_require__(747); +var hasValues = __webpack_require__(786); +var get = __webpack_require__(778); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -89279,7 +91643,7 @@ module.exports = function(val, prop) { /***/ }), -/* 779 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89292,8 +91656,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(780); -var isNumber = __webpack_require__(744); +var typeOf = __webpack_require__(787); +var isNumber = __webpack_require__(751); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -89346,10 +91710,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 780 */ +/* 787 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(727); +var isBuffer = __webpack_require__(734); var toString = Object.prototype.toString; /** @@ -89471,14 +91835,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 781 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(782); -var forIn = __webpack_require__(783); +var isExtendable = __webpack_require__(789); +var forIn = __webpack_require__(790); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -89542,7 +91906,7 @@ module.exports = mixinDeep; /***/ }), -/* 782 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89555,7 +91919,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(739); +var isPlainObject = __webpack_require__(746); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -89563,7 +91927,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 783 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89586,7 +91950,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 784 */ +/* 791 */ /***/ (function(module, exports) { /*! @@ -89613,14 +91977,14 @@ module.exports = pascalcase; /***/ }), -/* 785 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(786); +var utils = __webpack_require__(793); /** * Expose class utils @@ -89985,7 +92349,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 786 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -89999,10 +92363,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(770); -utils.define = __webpack_require__(722); -utils.isObj = __webpack_require__(740); -utils.staticExtend = __webpack_require__(787); +utils.union = __webpack_require__(777); +utils.define = __webpack_require__(729); +utils.isObj = __webpack_require__(747); +utils.staticExtend = __webpack_require__(794); /** @@ -90013,7 +92377,7 @@ module.exports = utils; /***/ }), -/* 787 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90026,8 +92390,8 @@ module.exports = utils; -var copy = __webpack_require__(788); -var define = __webpack_require__(722); +var copy = __webpack_require__(795); +var define = __webpack_require__(729); var util = __webpack_require__(29); /** @@ -90110,15 +92474,15 @@ module.exports = extend; /***/ }), -/* 788 */ +/* 795 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(745); -var copyDescriptor = __webpack_require__(789); -var define = __webpack_require__(722); +var typeOf = __webpack_require__(752); +var copyDescriptor = __webpack_require__(796); +var define = __webpack_require__(729); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -90291,7 +92655,7 @@ module.exports.has = has; /***/ }), -/* 789 */ +/* 796 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90379,16 +92743,16 @@ function isObject(val) { /***/ }), -/* 790 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(791); -var define = __webpack_require__(722); -var debug = __webpack_require__(793)('snapdragon:compiler'); -var utils = __webpack_require__(799); +var use = __webpack_require__(798); +var define = __webpack_require__(729); +var debug = __webpack_require__(800)('snapdragon:compiler'); +var utils = __webpack_require__(806); /** * Create a new `Compiler` with the given `options`. @@ -90542,7 +92906,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(818); + var sourcemaps = __webpack_require__(825); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -90563,7 +92927,7 @@ module.exports = Compiler; /***/ }), -/* 791 */ +/* 798 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90576,7 +92940,7 @@ module.exports = Compiler; -var utils = __webpack_require__(792); +var utils = __webpack_require__(799); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -90691,7 +93055,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 792 */ +/* 799 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -90705,8 +93069,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(722); -utils.isObject = __webpack_require__(740); +utils.define = __webpack_require__(729); +utils.isObject = __webpack_require__(747); utils.isString = function(val) { @@ -90721,7 +93085,7 @@ module.exports = utils; /***/ }), -/* 793 */ +/* 800 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -90730,14 +93094,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(794); + module.exports = __webpack_require__(801); } else { - module.exports = __webpack_require__(797); + module.exports = __webpack_require__(804); } /***/ }), -/* 794 */ +/* 801 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -90746,7 +93110,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(795); +exports = module.exports = __webpack_require__(802); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -90928,7 +93292,7 @@ function localstorage() { /***/ }), -/* 795 */ +/* 802 */ /***/ (function(module, exports, __webpack_require__) { @@ -90944,7 +93308,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(796); +exports.humanize = __webpack_require__(803); /** * The currently active debug mode names, and names to skip. @@ -91136,7 +93500,7 @@ function coerce(val) { /***/ }), -/* 796 */ +/* 803 */ /***/ (function(module, exports) { /** @@ -91294,7 +93658,7 @@ function plural(ms, n, name) { /***/ }), -/* 797 */ +/* 804 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -91310,7 +93674,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(795); +exports = module.exports = __webpack_require__(802); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -91489,7 +93853,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(798); + var net = __webpack_require__(805); stream = new net.Socket({ fd: fd, readable: false, @@ -91548,13 +93912,13 @@ exports.enable(load()); /***/ }), -/* 798 */ +/* 805 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 799 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -91564,9 +93928,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(730); -exports.SourceMap = __webpack_require__(800); -exports.sourceMapResolve = __webpack_require__(811); +exports.extend = __webpack_require__(737); +exports.SourceMap = __webpack_require__(807); +exports.sourceMapResolve = __webpack_require__(818); /** * Convert backslash in the given string to forward slashes @@ -91609,7 +93973,7 @@ exports.last = function(arr, n) { /***/ }), -/* 800 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -91617,13 +93981,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(801).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(807).SourceMapConsumer; -exports.SourceNode = __webpack_require__(810).SourceNode; +exports.SourceMapGenerator = __webpack_require__(808).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(814).SourceMapConsumer; +exports.SourceNode = __webpack_require__(817).SourceNode; /***/ }), -/* 801 */ +/* 808 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -91633,10 +93997,10 @@ exports.SourceNode = __webpack_require__(810).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(802); -var util = __webpack_require__(804); -var ArraySet = __webpack_require__(805).ArraySet; -var MappingList = __webpack_require__(806).MappingList; +var base64VLQ = __webpack_require__(809); +var util = __webpack_require__(811); +var ArraySet = __webpack_require__(812).ArraySet; +var MappingList = __webpack_require__(813).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -92045,7 +94409,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 802 */ +/* 809 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92085,7 +94449,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(803); +var base64 = __webpack_require__(810); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -92191,7 +94555,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 803 */ +/* 810 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92264,7 +94628,7 @@ exports.decode = function (charCode) { /***/ }), -/* 804 */ +/* 811 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92687,7 +95051,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 805 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92697,7 +95061,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(804); +var util = __webpack_require__(811); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -92814,7 +95178,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 806 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92824,7 +95188,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(804); +var util = __webpack_require__(811); /** * Determine whether mappingB is after mappingA with respect to generated @@ -92899,7 +95263,7 @@ exports.MappingList = MappingList; /***/ }), -/* 807 */ +/* 814 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -92909,11 +95273,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(804); -var binarySearch = __webpack_require__(808); -var ArraySet = __webpack_require__(805).ArraySet; -var base64VLQ = __webpack_require__(802); -var quickSort = __webpack_require__(809).quickSort; +var util = __webpack_require__(811); +var binarySearch = __webpack_require__(815); +var ArraySet = __webpack_require__(812).ArraySet; +var base64VLQ = __webpack_require__(809); +var quickSort = __webpack_require__(816).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -93987,7 +96351,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 808 */ +/* 815 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -94104,7 +96468,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 809 */ +/* 816 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -94224,7 +96588,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 810 */ +/* 817 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -94234,8 +96598,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(801).SourceMapGenerator; -var util = __webpack_require__(804); +var SourceMapGenerator = __webpack_require__(808).SourceMapGenerator; +var util = __webpack_require__(811); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -94643,17 +97007,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 811 */ +/* 818 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(812) -var resolveUrl = __webpack_require__(813) -var decodeUriComponent = __webpack_require__(814) -var urix = __webpack_require__(816) -var atob = __webpack_require__(817) +var sourceMappingURL = __webpack_require__(819) +var resolveUrl = __webpack_require__(820) +var decodeUriComponent = __webpack_require__(821) +var urix = __webpack_require__(823) +var atob = __webpack_require__(824) @@ -94951,7 +97315,7 @@ module.exports = { /***/ }), -/* 812 */ +/* 819 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -95014,7 +97378,7 @@ void (function(root, factory) { /***/ }), -/* 813 */ +/* 820 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -95032,13 +97396,13 @@ module.exports = resolveUrl /***/ }), -/* 814 */ +/* 821 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(815) +var decodeUriComponent = __webpack_require__(822) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -95049,7 +97413,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 815 */ +/* 822 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -95150,7 +97514,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 816 */ +/* 823 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -95173,7 +97537,7 @@ module.exports = urix /***/ }), -/* 817 */ +/* 824 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -95187,7 +97551,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 818 */ +/* 825 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -95195,8 +97559,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); -var define = __webpack_require__(722); -var utils = __webpack_require__(799); +var define = __webpack_require__(729); +var utils = __webpack_require__(806); /** * Expose `mixin()`. @@ -95339,19 +97703,19 @@ exports.comment = function(node) { /***/ }), -/* 819 */ +/* 826 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(791); +var use = __webpack_require__(798); var util = __webpack_require__(29); -var Cache = __webpack_require__(820); -var define = __webpack_require__(722); -var debug = __webpack_require__(793)('snapdragon:parser'); -var Position = __webpack_require__(821); -var utils = __webpack_require__(799); +var Cache = __webpack_require__(827); +var define = __webpack_require__(729); +var debug = __webpack_require__(800)('snapdragon:parser'); +var Position = __webpack_require__(828); +var utils = __webpack_require__(806); /** * Create a new `Parser` with the given `input` and `options`. @@ -95879,7 +98243,7 @@ module.exports = Parser; /***/ }), -/* 820 */ +/* 827 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -95986,13 +98350,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 821 */ +/* 828 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(722); +var define = __webpack_require__(729); /** * Store position for a node @@ -96007,16 +98371,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 822 */ +/* 829 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(823); -var define = __webpack_require__(829); -var extend = __webpack_require__(830); -var not = __webpack_require__(832); +var safe = __webpack_require__(830); +var define = __webpack_require__(836); +var extend = __webpack_require__(837); +var not = __webpack_require__(839); var MAX_LENGTH = 1024 * 64; /** @@ -96169,10 +98533,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 823 */ +/* 830 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(824); +var parse = __webpack_require__(831); var types = parse.types; module.exports = function (re, opts) { @@ -96218,13 +98582,13 @@ function isRegExp (x) { /***/ }), -/* 824 */ +/* 831 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(825); -var types = __webpack_require__(826); -var sets = __webpack_require__(827); -var positions = __webpack_require__(828); +var util = __webpack_require__(832); +var types = __webpack_require__(833); +var sets = __webpack_require__(834); +var positions = __webpack_require__(835); module.exports = function(regexpStr) { @@ -96506,11 +98870,11 @@ module.exports.types = types; /***/ }), -/* 825 */ +/* 832 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(826); -var sets = __webpack_require__(827); +var types = __webpack_require__(833); +var sets = __webpack_require__(834); // All of these are private and only used by randexp. @@ -96623,7 +98987,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 826 */ +/* 833 */ /***/ (function(module, exports) { module.exports = { @@ -96639,10 +99003,10 @@ module.exports = { /***/ }), -/* 827 */ +/* 834 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(826); +var types = __webpack_require__(833); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -96727,10 +99091,10 @@ exports.anyChar = function() { /***/ }), -/* 828 */ +/* 835 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(826); +var types = __webpack_require__(833); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -96750,7 +99114,7 @@ exports.end = function() { /***/ }), -/* 829 */ +/* 836 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96763,8 +99127,8 @@ exports.end = function() { -var isobject = __webpack_require__(740); -var isDescriptor = __webpack_require__(752); +var isobject = __webpack_require__(747); +var isDescriptor = __webpack_require__(759); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -96795,14 +99159,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 830 */ +/* 837 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(831); -var assignSymbols = __webpack_require__(741); +var isExtendable = __webpack_require__(838); +var assignSymbols = __webpack_require__(748); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -96862,7 +99226,7 @@ function isEnum(obj, key) { /***/ }), -/* 831 */ +/* 838 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -96875,7 +99239,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(739); +var isPlainObject = __webpack_require__(746); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -96883,14 +99247,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 832 */ +/* 839 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(830); -var safe = __webpack_require__(823); +var extend = __webpack_require__(837); +var safe = __webpack_require__(830); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -96962,14 +99326,14 @@ module.exports = toRegex; /***/ }), -/* 833 */ +/* 840 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(834); -var extglob = __webpack_require__(849); +var nanomatch = __webpack_require__(841); +var extglob = __webpack_require__(856); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -97046,7 +99410,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 834 */ +/* 841 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -97057,17 +99421,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(29); -var toRegex = __webpack_require__(721); -var extend = __webpack_require__(835); +var toRegex = __webpack_require__(728); +var extend = __webpack_require__(842); /** * Local dependencies */ -var compilers = __webpack_require__(837); -var parsers = __webpack_require__(838); -var cache = __webpack_require__(841); -var utils = __webpack_require__(843); +var compilers = __webpack_require__(844); +var parsers = __webpack_require__(845); +var cache = __webpack_require__(848); +var utils = __webpack_require__(850); var MAX_LENGTH = 1024 * 64; /** @@ -97891,14 +100255,14 @@ module.exports = nanomatch; /***/ }), -/* 835 */ +/* 842 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(836); -var assignSymbols = __webpack_require__(741); +var isExtendable = __webpack_require__(843); +var assignSymbols = __webpack_require__(748); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -97958,7 +100322,7 @@ function isEnum(obj, key) { /***/ }), -/* 836 */ +/* 843 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -97971,7 +100335,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(739); +var isPlainObject = __webpack_require__(746); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -97979,7 +100343,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 837 */ +/* 844 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98325,15 +100689,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 838 */ +/* 845 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(732); -var toRegex = __webpack_require__(721); -var isOdd = __webpack_require__(839); +var regexNot = __webpack_require__(739); +var toRegex = __webpack_require__(728); +var isOdd = __webpack_require__(846); /** * Characters to use in negation regex (we want to "not" match @@ -98719,7 +101083,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 839 */ +/* 846 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98732,7 +101096,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(840); +var isNumber = __webpack_require__(847); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -98746,7 +101110,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 840 */ +/* 847 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98774,14 +101138,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 841 */ +/* 848 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(842))(); +module.exports = new (__webpack_require__(849))(); /***/ }), -/* 842 */ +/* 849 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98794,7 +101158,7 @@ module.exports = new (__webpack_require__(842))(); -var MapCache = __webpack_require__(820); +var MapCache = __webpack_require__(827); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -98916,7 +101280,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 843 */ +/* 850 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -98929,14 +101293,14 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(844)(); -var Snapdragon = __webpack_require__(760); -utils.define = __webpack_require__(845); -utils.diff = __webpack_require__(846); -utils.extend = __webpack_require__(835); -utils.pick = __webpack_require__(847); -utils.typeOf = __webpack_require__(848); -utils.unique = __webpack_require__(733); +var isWindows = __webpack_require__(851)(); +var Snapdragon = __webpack_require__(767); +utils.define = __webpack_require__(852); +utils.diff = __webpack_require__(853); +utils.extend = __webpack_require__(842); +utils.pick = __webpack_require__(854); +utils.typeOf = __webpack_require__(855); +utils.unique = __webpack_require__(740); /** * Returns true if the given value is effectively an empty string @@ -99302,7 +101666,7 @@ utils.unixify = function(options) { /***/ }), -/* 844 */ +/* 851 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -99330,7 +101694,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 845 */ +/* 852 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99343,8 +101707,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(740); -var isDescriptor = __webpack_require__(752); +var isobject = __webpack_require__(747); +var isDescriptor = __webpack_require__(759); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -99375,7 +101739,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 846 */ +/* 853 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99429,7 +101793,7 @@ function diffArray(one, two) { /***/ }), -/* 847 */ +/* 854 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99442,7 +101806,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(740); +var isObject = __webpack_require__(747); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -99471,7 +101835,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 848 */ +/* 855 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -99606,7 +101970,7 @@ function isBuffer(val) { /***/ }), -/* 849 */ +/* 856 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -99616,18 +101980,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(730); -var unique = __webpack_require__(733); -var toRegex = __webpack_require__(721); +var extend = __webpack_require__(737); +var unique = __webpack_require__(740); +var toRegex = __webpack_require__(728); /** * Local dependencies */ -var compilers = __webpack_require__(850); -var parsers = __webpack_require__(861); -var Extglob = __webpack_require__(864); -var utils = __webpack_require__(863); +var compilers = __webpack_require__(857); +var parsers = __webpack_require__(868); +var Extglob = __webpack_require__(871); +var utils = __webpack_require__(870); var MAX_LENGTH = 1024 * 64; /** @@ -99944,13 +102308,13 @@ module.exports = extglob; /***/ }), -/* 850 */ +/* 857 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(851); +var brackets = __webpack_require__(858); /** * Extglob compilers @@ -100120,7 +102484,7 @@ module.exports = function(extglob) { /***/ }), -/* 851 */ +/* 858 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100130,17 +102494,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(852); -var parsers = __webpack_require__(854); +var compilers = __webpack_require__(859); +var parsers = __webpack_require__(861); /** * Module dependencies */ -var debug = __webpack_require__(856)('expand-brackets'); -var extend = __webpack_require__(730); -var Snapdragon = __webpack_require__(760); -var toRegex = __webpack_require__(721); +var debug = __webpack_require__(863)('expand-brackets'); +var extend = __webpack_require__(737); +var Snapdragon = __webpack_require__(767); +var toRegex = __webpack_require__(728); /** * Parses the given POSIX character class `pattern` and returns a @@ -100338,13 +102702,13 @@ module.exports = brackets; /***/ }), -/* 852 */ +/* 859 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(853); +var posix = __webpack_require__(860); module.exports = function(brackets) { brackets.compiler @@ -100432,7 +102796,7 @@ module.exports = function(brackets) { /***/ }), -/* 853 */ +/* 860 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -100461,14 +102825,14 @@ module.exports = { /***/ }), -/* 854 */ +/* 861 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(855); -var define = __webpack_require__(722); +var utils = __webpack_require__(862); +var define = __webpack_require__(729); /** * Text regex @@ -100687,14 +103051,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 855 */ +/* 862 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(721); -var regexNot = __webpack_require__(732); +var toRegex = __webpack_require__(728); +var regexNot = __webpack_require__(739); var cached; /** @@ -100728,7 +103092,7 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 856 */ +/* 863 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -100737,14 +103101,14 @@ exports.createRegex = function(pattern, include) { */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(857); + module.exports = __webpack_require__(864); } else { - module.exports = __webpack_require__(860); + module.exports = __webpack_require__(867); } /***/ }), -/* 857 */ +/* 864 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -100753,7 +103117,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(858); +exports = module.exports = __webpack_require__(865); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -100935,7 +103299,7 @@ function localstorage() { /***/ }), -/* 858 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { @@ -100951,7 +103315,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(859); +exports.humanize = __webpack_require__(866); /** * The currently active debug mode names, and names to skip. @@ -101143,7 +103507,7 @@ function coerce(val) { /***/ }), -/* 859 */ +/* 866 */ /***/ (function(module, exports) { /** @@ -101301,7 +103665,7 @@ function plural(ms, n, name) { /***/ }), -/* 860 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -101317,7 +103681,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(858); +exports = module.exports = __webpack_require__(865); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -101496,7 +103860,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(798); + var net = __webpack_require__(805); stream = new net.Socket({ fd: fd, readable: false, @@ -101555,15 +103919,15 @@ exports.enable(load()); /***/ }), -/* 861 */ +/* 868 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(851); -var define = __webpack_require__(862); -var utils = __webpack_require__(863); +var brackets = __webpack_require__(858); +var define = __webpack_require__(869); +var utils = __webpack_require__(870); /** * Characters to use in text regex (we want to "not" match @@ -101718,7 +104082,7 @@ module.exports = parsers; /***/ }), -/* 862 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101731,7 +104095,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(752); +var isDescriptor = __webpack_require__(759); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -101756,14 +104120,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 863 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(732); -var Cache = __webpack_require__(842); +var regex = __webpack_require__(739); +var Cache = __webpack_require__(849); /** * Utils @@ -101832,7 +104196,7 @@ utils.createRegex = function(str) { /***/ }), -/* 864 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -101842,16 +104206,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(760); -var define = __webpack_require__(862); -var extend = __webpack_require__(730); +var Snapdragon = __webpack_require__(767); +var define = __webpack_require__(869); +var extend = __webpack_require__(737); /** * Local dependencies */ -var compilers = __webpack_require__(850); -var parsers = __webpack_require__(861); +var compilers = __webpack_require__(857); +var parsers = __webpack_require__(868); /** * Customize Snapdragon parser and renderer @@ -101917,16 +104281,16 @@ module.exports = Extglob; /***/ }), -/* 865 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(849); -var nanomatch = __webpack_require__(834); -var regexNot = __webpack_require__(732); -var toRegex = __webpack_require__(822); +var extglob = __webpack_require__(856); +var nanomatch = __webpack_require__(841); +var regexNot = __webpack_require__(739); +var toRegex = __webpack_require__(829); var not; /** @@ -102007,14 +104371,14 @@ function textRegex(pattern) { /***/ }), -/* 866 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(842))(); +module.exports = new (__webpack_require__(849))(); /***/ }), -/* 867 */ +/* 874 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102027,13 +104391,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var Snapdragon = __webpack_require__(760); -utils.define = __webpack_require__(829); -utils.diff = __webpack_require__(846); -utils.extend = __webpack_require__(830); -utils.pick = __webpack_require__(847); -utils.typeOf = __webpack_require__(868); -utils.unique = __webpack_require__(733); +var Snapdragon = __webpack_require__(767); +utils.define = __webpack_require__(836); +utils.diff = __webpack_require__(853); +utils.extend = __webpack_require__(837); +utils.pick = __webpack_require__(854); +utils.typeOf = __webpack_require__(875); +utils.unique = __webpack_require__(740); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -102330,7 +104694,7 @@ utils.unixify = function(options) { /***/ }), -/* 868 */ +/* 875 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -102465,7 +104829,7 @@ function isBuffer(val) { /***/ }), -/* 869 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102484,9 +104848,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(870); -var reader_1 = __webpack_require__(883); -var fs_stream_1 = __webpack_require__(887); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -102547,15 +104911,15 @@ exports.default = ReaderAsync; /***/ }), -/* 870 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(871); -const readdirAsync = __webpack_require__(879); -const readdirStream = __webpack_require__(882); +const readdirSync = __webpack_require__(878); +const readdirAsync = __webpack_require__(886); +const readdirStream = __webpack_require__(889); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -102639,7 +105003,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 871 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102647,11 +105011,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(872); +const DirectoryReader = __webpack_require__(879); let syncFacade = { - fs: __webpack_require__(877), - forEach: __webpack_require__(878), + fs: __webpack_require__(884), + forEach: __webpack_require__(885), sync: true }; @@ -102680,7 +105044,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 872 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -102689,9 +105053,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(27).Readable; const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(873); -const stat = __webpack_require__(875); -const call = __webpack_require__(876); +const normalizeOptions = __webpack_require__(880); +const stat = __webpack_require__(882); +const call = __webpack_require__(883); /** * Asynchronously reads the contents of a directory and streams the results @@ -103067,14 +105431,14 @@ module.exports = DirectoryReader; /***/ }), -/* 873 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(874); +const globToRegExp = __webpack_require__(881); module.exports = normalizeOptions; @@ -103251,7 +105615,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 874 */ +/* 881 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -103388,13 +105752,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 875 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(876); +const call = __webpack_require__(883); module.exports = stat; @@ -103469,7 +105833,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 876 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103530,14 +105894,14 @@ function callOnce (fn) { /***/ }), -/* 877 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(876); +const call = __webpack_require__(883); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -103601,7 +105965,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 878 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103630,7 +105994,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 879 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103638,12 +106002,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(880); -const DirectoryReader = __webpack_require__(872); +const maybe = __webpack_require__(887); +const DirectoryReader = __webpack_require__(879); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(881), + forEach: __webpack_require__(888), async: true }; @@ -103685,7 +106049,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 880 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103712,7 +106076,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 881 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103748,7 +106112,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 882 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103756,11 +106120,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(872); +const DirectoryReader = __webpack_require__(879); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(881), + forEach: __webpack_require__(888), async: true }; @@ -103780,16 +106144,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 883 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(884); -var entry_1 = __webpack_require__(886); -var pathUtil = __webpack_require__(885); +var deep_1 = __webpack_require__(891); +var entry_1 = __webpack_require__(893); +var pathUtil = __webpack_require__(892); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -103855,14 +106219,14 @@ exports.default = Reader; /***/ }), -/* 884 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(885); -var patternUtils = __webpack_require__(714); +var pathUtils = __webpack_require__(892); +var patternUtils = __webpack_require__(721); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -103945,7 +106309,7 @@ exports.default = DeepFilter; /***/ }), -/* 885 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -103976,14 +106340,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 886 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(885); -var patternUtils = __webpack_require__(714); +var pathUtils = __webpack_require__(892); +var patternUtils = __webpack_require__(721); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -104068,7 +106432,7 @@ exports.default = EntryFilter; /***/ }), -/* 887 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104088,8 +106452,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var fsStat = __webpack_require__(888); -var fs_1 = __webpack_require__(892); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -104139,14 +106503,14 @@ exports.default = FileSystemStream; /***/ }), -/* 888 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(889); -const statProvider = __webpack_require__(891); +const optionsManager = __webpack_require__(896); +const statProvider = __webpack_require__(898); /** * Asynchronous API. */ @@ -104177,13 +106541,13 @@ exports.statSync = statSync; /***/ }), -/* 889 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(890); +const fsAdapter = __webpack_require__(897); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -104196,7 +106560,7 @@ exports.prepare = prepare; /***/ }), -/* 890 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104219,7 +106583,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 891 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104271,7 +106635,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 892 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104302,7 +106666,7 @@ exports.default = FileSystem; /***/ }), -/* 893 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104322,9 +106686,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(27); -var readdir = __webpack_require__(870); -var reader_1 = __webpack_require__(883); -var fs_stream_1 = __webpack_require__(887); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_stream_1 = __webpack_require__(894); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -104392,7 +106756,7 @@ exports.default = ReaderStream; /***/ }), -/* 894 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104411,9 +106775,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(870); -var reader_1 = __webpack_require__(883); -var fs_sync_1 = __webpack_require__(895); +var readdir = __webpack_require__(877); +var reader_1 = __webpack_require__(890); +var fs_sync_1 = __webpack_require__(902); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -104473,7 +106837,7 @@ exports.default = ReaderSync; /***/ }), -/* 895 */ +/* 902 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104492,8 +106856,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(888); -var fs_1 = __webpack_require__(892); +var fsStat = __webpack_require__(895); +var fs_1 = __webpack_require__(899); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -104539,7 +106903,7 @@ exports.default = FileSystemSync; /***/ }), -/* 896 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104555,7 +106919,7 @@ exports.flatten = flatten; /***/ }), -/* 897 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104576,13 +106940,13 @@ exports.merge = merge; /***/ }), -/* 898 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(899); +const pathType = __webpack_require__(906); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -104648,13 +107012,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 899 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(900); +const pify = __webpack_require__(907); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -104697,7 +107061,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 900 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -104788,17 +107152,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 901 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); const path = __webpack_require__(16); -const fastGlob = __webpack_require__(710); -const gitIgnore = __webpack_require__(902); -const pify = __webpack_require__(903); -const slash = __webpack_require__(904); +const fastGlob = __webpack_require__(717); +const gitIgnore = __webpack_require__(909); +const pify = __webpack_require__(910); +const slash = __webpack_require__(911); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -104896,7 +107260,7 @@ module.exports.sync = options => { /***/ }), -/* 902 */ +/* 909 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -105365,7 +107729,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 903 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105440,7 +107804,7 @@ module.exports = (input, options) => { /***/ }), -/* 904 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105458,17 +107822,17 @@ module.exports = input => { /***/ }), -/* 905 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(906); -const CpFileError = __webpack_require__(907); -const fs = __webpack_require__(909); -const ProgressEmitter = __webpack_require__(911); +const {Buffer} = __webpack_require__(913); +const CpFileError = __webpack_require__(914); +const fs = __webpack_require__(918); +const ProgressEmitter = __webpack_require__(920); const cpFile = (source, destination, options) => { if (!source || !destination) { @@ -105622,7 +107986,7 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 906 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ @@ -105690,12 +108054,12 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 907 */ +/* 914 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(908); +const NestedError = __webpack_require__(915); class CpFileError extends NestedError { constructor(message, nested) { @@ -105709,10 +108073,10 @@ module.exports = CpFileError; /***/ }), -/* 908 */ +/* 915 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(509); +var inherits = __webpack_require__(916); var NestedError = function (message, nested) { this.nested = nested; @@ -105763,15 +108127,57 @@ module.exports = NestedError; /***/ }), -/* 909 */ +/* 916 */ +/***/ (function(module, exports, __webpack_require__) { + +try { + var util = __webpack_require__(29); + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + module.exports = __webpack_require__(917); +} + + +/***/ }), +/* 917 */ +/***/ (function(module, exports) { + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + + +/***/ }), +/* 918 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); const makeDir = __webpack_require__(559); -const pify = __webpack_require__(910); -const CpFileError = __webpack_require__(907); +const pify = __webpack_require__(919); +const CpFileError = __webpack_require__(914); const fsP = pify(fs); @@ -105916,7 +108322,7 @@ if (fs.copyFileSync) { /***/ }), -/* 910 */ +/* 919 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -105991,7 +108397,7 @@ module.exports = (input, options) => { /***/ }), -/* 911 */ +/* 920 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -106032,12 +108438,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 912 */ +/* 921 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(913); +const NestedError = __webpack_require__(922); class CpyError extends NestedError { constructor(message, nested) { @@ -106051,7 +108457,7 @@ module.exports = CpyError; /***/ }), -/* 913 */ +/* 922 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -106107,7 +108513,7 @@ module.exports = NestedError; /***/ }), -/* 914 */ +/* 923 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; diff --git a/yarn.lock b/yarn.lock index caa5587a0bbd0..a36c3e1501695 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5860,12 +5860,12 @@ aggregate-error@^1.0.0: indent-string "^3.0.0" aggregate-error@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.0.tgz#5b5a3c95e9095f311c9ab16c19fb4f3527cd3f79" - integrity sha512-yKD9kEoJIR+2IFqhMwayIBgheLYbB3PS2OBhWae1L/ODTd/JF/30cW0bc9TqzRL3k4U41Dieu3BF4I29p8xesA== + version "3.0.1" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" + integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== dependencies: clean-stack "^2.0.0" - indent-string "^3.2.0" + indent-string "^4.0.0" "airbnb-js-shims@^1 || ^2": version "2.1.1" @@ -5952,7 +5952,7 @@ ajv@^6.1.0, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.10.0, ajv@^6.10.2: +ajv@^6.10.0: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== @@ -5962,6 +5962,16 @@ ajv@^6.10.0, ajv@^6.10.2: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.10.2: + version "6.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" + integrity sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^6.9.1: version "6.9.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.2.tgz#4927adb83e7f48e5a32b45729744c71ec39c9c7b" @@ -8341,7 +8351,7 @@ cacache@^12.0.2: unique-filename "^1.1.1" y18n "^4.0.0" -cacache@^13.0.0: +cacache@^13.0.1: version "13.0.1" resolved "https://registry.yarnpkg.com/cacache/-/cacache-13.0.1.tgz#a8000c21697089082f85287a1aec6e382024a71c" integrity sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w== @@ -9021,9 +9031,9 @@ clean-stack@^1.0.0, clean-stack@^1.3.0: integrity sha1-noIVAa6XmYbEax1m0tQy2y/UrjE= clean-stack@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.1.0.tgz#9e7fec7f3f8340a2ab4f127c80273085e8fbbdd0" - integrity sha512-uQWrpRm+iZZUCAp7ZZJQbd4Za9I3AjR/3YTjmcnAtkauaIm/T5CT6U8zVI6e60T6OANqBFAzuR9/HB3NzuZCRA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== cli-boxes@^1.0.0: version "1.0.0" @@ -9461,12 +9471,12 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, commander@^2.20.0: +commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.7.1: +commander@^2.20.0, commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -13363,9 +13373,9 @@ fast-glob@^3.0.3: micromatch "^4.0.2" fast-json-stable-stringify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" - integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: version "2.0.6" @@ -13724,10 +13734,10 @@ find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.0.0.tgz#cd4b7dd97b7185b7e17dbfe2d6e4115ee3eeb8fc" - integrity sha512-t7ulV1fmbxh5G9l/492O1p5+EBbr3uwpt6odhFTMc+nWyhmbloe+ja9BZ8pIBtqFWhOmCWVjx+pTW4zDkFoclw== +find-cache-dir@^3.0.0, find-cache-dir@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874" + integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg== dependencies: commondir "^1.0.1" make-dir "^3.0.0" @@ -14204,9 +14214,9 @@ fs-minipass@^1.2.5: minipass "^2.2.1" fs-minipass@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.0.0.tgz#a6415edab02fae4b9e9230bc87ee2e4472003cd1" - integrity sha512-40Qz+LFXmd9tzYVnnBmZvFfvAADfUA14TXPK1s7IfElJTIZ97rA8w4Kin7Wt5JBrC3ShnnFJO/5vPjPEeJIq9A== + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== dependencies: minipass "^3.0.0" @@ -14675,7 +14685,7 @@ glob-watcher@5.0.3, glob-watcher@^5.0.3: just-debounce "^1.0.0" object.defaults "^1.1.0" -glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: +glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -14687,7 +14697,7 @@ glob@7.1.3, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.4, glob@^7.1.4, glob@~7.1.4: +glob@7.1.4, glob@~7.1.4: version "7.1.4" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== @@ -14721,7 +14731,7 @@ glob@^6.0.1, glob@^6.0.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.6: +glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -15090,12 +15100,12 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.2: version "4.2.3" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -graceful-fs@^4.2.0, graceful-fs@^4.2.2: +graceful-fs@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== @@ -16551,16 +16561,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= +inherits@2, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= +inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + ini@^1.2.0, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" @@ -18203,6 +18218,14 @@ jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" +jest-worker@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" + integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== + dependencies: + merge-stream "^2.0.0" + supports-color "^7.0.0" + jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest/-/jest-24.9.0.tgz#987d290c05a08b52c56188c1002e368edb007171" @@ -20602,10 +20625,10 @@ minipass@^2.8.6: safe-buffer "^5.1.2" yallist "^3.0.0" -minipass@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.0.1.tgz#b4fec73bd61e8a40f0b374ddd04260ade2c8ec20" - integrity sha512-2y5okJ4uBsjoD2vAbLKL9EUQPPkC0YMIp+2mZOXG3nBba++pdfJWRxx2Ewirc0pwAJYu4XtWg2EkVo1nRXuO/w== +minipass@^3.0.0, minipass@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" + integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== dependencies: yallist "^4.0.0" @@ -22215,10 +22238,10 @@ p-limit@^2.0.0: dependencies: p-try "^2.0.0" -p-limit@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" - integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== +p-limit@^2.2.0, p-limit@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" + integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== dependencies: p-try "^2.0.0" @@ -22299,9 +22322,9 @@ p-try@^1.0.0: integrity sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M= p-try@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" - integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== package-hash@^3.0.0: version "3.0.0" @@ -23292,7 +23315,7 @@ private@^0.1.6, private@^0.1.8, private@~0.1.5: resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== -process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: +process-nextick-args@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== @@ -23302,6 +23325,11 @@ process-nextick-args@~1.0.6: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" @@ -24931,10 +24959,10 @@ read-pkg@^5.1.1, read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== +"readable-stream@1 || 2": + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -24963,6 +24991,19 @@ readable-stream@1.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.3, readable-stream@~2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + readable-stream@~1.1.0: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -25993,7 +26034,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: +rimraf@2, rimraf@^2.2.0, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== @@ -26014,7 +26055,7 @@ rimraf@3.0.0, rimraf@^3.0.0: dependencies: glob "^7.1.3" -rimraf@^2.7.1: +rimraf@^2.5.4, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== @@ -26397,10 +26438,10 @@ schema-utils@^2.0.0, schema-utils@^2.0.1: ajv "^6.1.0" ajv-keywords "^3.1.0" -schema-utils@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.4.1.tgz#e89ade5d056dc8bcaca377574bb4a9c4e1b8be56" - integrity sha512-RqYLpkPZX5Oc3fw/kHHHyP56fg5Y+XBpIpV8nCg0znIALfq3OH+Ea9Hfeac9BAMwG5IICltiZ0vxFvJQONfA5w== +schema-utils@^2.4.1, schema-utils@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53" + integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ== dependencies: ajv "^6.10.2" ajv-keywords "^3.4.1" @@ -26532,21 +26573,16 @@ semver@^5.5.1: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw== -semver@^6.0.0: - version "6.1.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.2.tgz#079960381376a3db62eb2edc8a3bfb10c7cfe318" - integrity sha512-z4PqiCpomGtWj8633oeAdXm1Kn1W++3T8epkZYnwiVgIYIJ0QHszhInYSJTYxebByQH7KVCEAn8R9duzZW2PhQ== +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== semver@^6.1.0: version "6.1.1" resolved "https://registry.yarnpkg.com/semver/-/semver-6.1.1.tgz#53f53da9b30b2103cd4f15eab3a18ecbcb210c9b" integrity sha512-rWYq2e5iYW+fFe/oPPtYJxYgjBm8sC4rmoGdUOgBB7VnwKt6HrL793l2voH1UlsyYZpJ4g0wfjnTEO1s1NP2eQ== -semver@^6.1.1, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -26598,7 +26634,7 @@ sentence-case@^2.1.0: no-case "^2.2.0" upper-case-first "^1.1.2" -serialize-javascript@^1.7.0, serialize-javascript@^2.1.0, serialize-javascript@^2.1.1, serialize-javascript@^2.1.2: +serialize-javascript@^1.7.0, serialize-javascript@^2.1.1, serialize-javascript@^2.1.2: version "2.1.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.1.tgz#952907a04a3e3a75af7f73d92d15e233862048b2" integrity sha512-MPLPRpD4FNqWq9tTIjYG5LesFouDhdyH0EPY3gVK4DRD5+g4aDqdNSzLIwceulo3Yj+PL1bPh6laE5+H6LTcrQ== @@ -27123,9 +27159,9 @@ sort-on@^3.0.0: dot-prop "^4.1.1" source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" - integrity sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A== + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.2" @@ -27395,12 +27431,12 @@ ssri@^6.0.1: figgy-pudding "^3.5.1" ssri@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.0.1.tgz#b0cab7bbb11ac9ea07f003453e2011f8cbed9f34" - integrity sha512-FfndBvkXL9AHyGLNzU3r9AvYIBBZ7gm+m+kd0p8cT3/v4OliMAyipZAhLVEv1Zi/k4QFq9CstRGVd9pW/zcHFQ== + version "7.1.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-7.1.0.tgz#92c241bf6de82365b5c7fb4bd76e975522e1294d" + integrity sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g== dependencies: figgy-pudding "^3.5.1" - minipass "^3.0.0" + minipass "^3.1.1" stable@^0.1.8: version "0.1.8" @@ -28491,21 +28527,22 @@ terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser-webpack-plugin@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.1.2.tgz#2b9b8147a6f18918348200800cf9560c50f701bb" - integrity sha512-MF/C4KABwqYOfRDi87f7gG07GP7Wj/kyiX938UxIGIO6l5mkh8XJL7xtS0hX/CRdVQaZI7ThGUPZbznrCjsGpg== - dependencies: - cacache "^13.0.0" - find-cache-dir "^3.0.0" - jest-worker "^24.9.0" - schema-utils "^2.4.1" - serialize-javascript "^2.1.0" +terser-webpack-plugin@^2.3.4: + version "2.3.4" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.4.tgz#ac045703bd8da0936ce910d8fb6350d0e1dee5fe" + integrity sha512-Nv96Nws2R2nrFOpbzF6IxRDpIkkIfmhvOws+IqMvYdFLO7o6wAILWFKONFgaYy8+T4LVz77DQW0f7wOeDEAjrg== + dependencies: + cacache "^13.0.1" + find-cache-dir "^3.2.0" + jest-worker "^25.1.0" + p-limit "^2.2.2" + schema-utils "^2.6.4" + serialize-javascript "^2.1.2" source-map "^0.6.1" - terser "^4.3.4" + terser "^4.4.3" webpack-sources "^1.4.3" -terser@^4.1.2, terser@^4.3.4: +terser@^4.1.2: version "4.6.1" resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.1.tgz#913e35e0d38a75285a7913ba01d753c4089ebdbd" integrity sha512-w0f2OWFD7ka3zwetgVAhNMeyzEbj39ht2Tb0qKflw9PmW9Qbo5tjTh01QJLkhO9t9RDDQYvk+WXqpECI2C6i2A== @@ -28514,6 +28551,15 @@ terser@^4.1.2, terser@^4.3.4: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^4.4.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" + integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + test-exclude@^5.0.0, test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -29906,9 +29952,9 @@ unique-filename@^1.1.1: unique-slug "^2.0.0" unique-slug@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" - integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== dependencies: imurmurhash "^0.1.4" @@ -31825,11 +31871,16 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= -yallist@^3.0.0, yallist@^3.0.2: +yallist@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yallist@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" From e74ab191fecf2d7ce64a1229c7a37c7ec1166069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?= <mikecote@users.noreply.github.com> Date: Tue, 4 Feb 2020 14:54:27 -0500 Subject: [PATCH 50/60] Disable creating alerts client instances when ESO plugin is using an ephemeral encryption key (#56676) --- .../legacy/plugins/alerting/server/index.ts | 5 +- .../plugins/alerting/server/plugin.test.ts | 113 ++++++++++++++++++ .../legacy/plugins/alerting/server/plugin.ts | 18 ++- .../lib/detection_engine/rules/types.ts | 2 +- 4 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 x-pack/legacy/plugins/alerting/server/plugin.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/index.ts b/x-pack/legacy/plugins/alerting/server/index.ts index 2f8356c0e9d01..44ae1964ec98a 100644 --- a/x-pack/legacy/plugins/alerting/server/index.ts +++ b/x-pack/legacy/plugins/alerting/server/index.ts @@ -4,7 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AlertsClient as AlertsClientClass } from './alerts_client'; + +export type AlertsClient = PublicMethodsOf<AlertsClientClass>; + export { init } from './init'; export { AlertType, AlertingPlugin, AlertExecutorOptions } from './types'; -export { AlertsClient } from './alerts_client'; export { PluginSetupContract, PluginStartContract } from './plugin'; diff --git a/x-pack/legacy/plugins/alerting/server/plugin.test.ts b/x-pack/legacy/plugins/alerting/server/plugin.test.ts new file mode 100644 index 0000000000000..50142434a86dd --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/plugin.test.ts @@ -0,0 +1,113 @@ +/* + * 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 { Plugin } from './plugin'; +import { coreMock } from '../../../../../src/core/server/mocks'; +import { licensingMock } from '../../../../plugins/licensing/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; + +describe('Alerting Plugin', () => { + describe('start()', () => { + /** + * HACK: This test has put together to ensuire the function "getAlertsClientWithRequest" + * throws when needed. There's a lot of blockers for writing a proper test like + * misisng plugin start/setup mocks for taskManager and actions plugin, core.http.route + * is actually not a function in Kibana Platform, etc. This test contains what is needed + * to get to the necessary function within start(). + */ + describe('getAlertsClientWithRequest()', () => { + it('throws error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to true', async () => { + const context = coreMock.createPluginInitializerContext(); + const plugin = new Plugin(context); + + const coreSetup = coreMock.createSetup(); + const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup(); + await plugin.setup( + { + ...coreSetup, + http: { + ...coreSetup.http, + route: jest.fn(), + }, + } as any, + { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + } as any + ); + + const startContract = plugin.start( + coreMock.createStart() as any, + { + actions: { + execute: jest.fn(), + getActionsClientWithRequest: jest.fn(), + }, + } as any + ); + + expect(encryptedSavedObjectsSetup.usingEphemeralEncryptionKey).toEqual(true); + expect(() => + startContract.getAlertsClientWithRequest({} as any) + ).toThrowErrorMatchingInlineSnapshot( + `"Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml"` + ); + }); + + it(`doesn't throw error when encryptedSavedObjects plugin has usingEphemeralEncryptionKey set to false`, async () => { + const context = coreMock.createPluginInitializerContext(); + const plugin = new Plugin(context); + + const coreSetup = coreMock.createSetup(); + const encryptedSavedObjectsSetup = { + ...encryptedSavedObjectsMock.createSetup(), + usingEphemeralEncryptionKey: false, + }; + await plugin.setup( + { + ...coreSetup, + http: { + ...coreSetup.http, + route: jest.fn(), + }, + } as any, + { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + } as any + ); + + const startContract = plugin.start( + coreMock.createStart() as any, + { + actions: { + execute: jest.fn(), + getActionsClientWithRequest: jest.fn(), + }, + spaces: () => null, + } as any + ); + + const fakeRequest = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: jest.fn(), + }; + await startContract.getAlertsClientWithRequest(fakeRequest as any); + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index 3218d9eeb2ef4..a4de7af376fb0 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -42,7 +42,7 @@ export interface PluginSetupContract { } export interface PluginStartContract { listTypes: AlertTypeRegistry['list']; - getAlertsClientWithRequest(request: Hapi.Request): AlertsClient; + getAlertsClientWithRequest(request: Hapi.Request): PublicMethodsOf<AlertsClient>; } export class Plugin { @@ -52,6 +52,7 @@ export class Plugin { private adminClient?: IClusterClient; private serverBasePath?: string; private licenseState: LicenseState | null = null; + private isESOUsingEphemeralEncryptionKey?: boolean; constructor(initializerContext: AlertingPluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'alerting'); @@ -63,8 +64,9 @@ export class Plugin { plugins: AlertingPluginsSetup ): Promise<PluginSetupContract> { this.adminClient = core.elasticsearch.adminClient; - this.licenseState = new LicenseState(plugins.licensing.license$); + this.isESOUsingEphemeralEncryptionKey = + plugins.encryptedSavedObjects.usingEphemeralEncryptionKey; // Encrypted attributes plugins.encryptedSavedObjects.registerType({ @@ -106,7 +108,7 @@ export class Plugin { } public start(core: AlertingCoreStart, plugins: AlertingPluginsStart): PluginStartContract { - const { adminClient, serverBasePath } = this; + const { adminClient, serverBasePath, isESOUsingEphemeralEncryptionKey } = this; function spaceIdToNamespace(spaceId?: string): string | undefined { const spacesPlugin = plugins.spaces(); @@ -147,8 +149,14 @@ export class Plugin { return { listTypes: this.alertTypeRegistry!.list.bind(this.alertTypeRegistry!), - getAlertsClientWithRequest: (request: Hapi.Request) => - alertsClientFactory!.create(KibanaRequest.from(request), request), + getAlertsClientWithRequest: (request: Hapi.Request) => { + if (isESOUsingEphemeralEncryptionKey === true) { + throw new Error( + `Unable to create alerts client due to the Encrypted Saved Objects plugin using an ephemeral encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in kibana.yml` + ); + } + return alertsClientFactory!.create(KibanaRequest.from(request), request); + }, }; } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index 1b13a010bbea3..1d423c8b375d1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -14,7 +14,7 @@ import { SavedObjectsClientContract, } from 'kibana/server'; import { SIGNALS_ID } from '../../../../common/constants'; -import { AlertsClient } from '../../../../../alerting/server/alerts_client'; +import { AlertsClient } from '../../../../../alerting/server'; import { ActionsClient } from '../../../../../../../plugins/actions/server'; import { RuleAlertParams, RuleTypeParams, RuleAlertParamsRest } from '../types'; import { RequestFacade } from '../../../types'; From 954cbee48654b79b528517a1df471dfbabdd9476 Mon Sep 17 00:00:00 2001 From: Ryland Herrick <ryalnd@gmail.com> Date: Tue, 4 Feb 2020 14:24:57 -0600 Subject: [PATCH 51/60] Prevent http client from converting our form data (#56772) If we don't specify an undefined content-type like so, the client attempts to convert our form data into a JSON string, and the backend returns an understandable 415. This fixes rule imports. --- .../plugins/siem/public/containers/detection_engine/rules/api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 4f50a9bd14788..06fb0c6dc3480 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -246,6 +246,7 @@ export const importRules = async ({ `${DETECTION_ENGINE_RULES_URL}/_import`, { method: 'POST', + headers: { 'Content-Type': undefined }, query: { overwrite }, body: formData, asResponse: true, From f18d5913590bb3b8949d534436d4c0824f387266 Mon Sep 17 00:00:00 2001 From: Alison Goryachev <alison.goryachev@elastic.co> Date: Tue, 4 Feb 2020 15:37:39 -0500 Subject: [PATCH 52/60] [Rollups] Adjust max width for job detail panel (#56674) --- .../crud_app/sections/job_list/detail_panel/detail_panel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js index 4534ef698b718..f774b1d7f63b7 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_list/detail_panel/detail_panel.js @@ -232,7 +232,7 @@ export class DetailPanel extends Component { onClose={closeDetailPanel} aria-labelledby="rollupJobDetailsFlyoutTitle" size="m" - maxWidth={400} + maxWidth={550} > <EuiFlyoutHeader> <EuiTitle From 6a12c8c1604cc56f7bf1fd5de175a0c89f42924a Mon Sep 17 00:00:00 2001 From: Corey Robertson <corey.robertson@elastic.co> Date: Tue, 4 Feb 2020 16:41:02 -0500 Subject: [PATCH 53/60] [Canvas] Use unique Id for Canvas Embeddables (#56783) * Use uniqueID for embeddable registry * Adds type to renderer --- .../renderers/embeddable/embeddable.tsx | 10 ++++++---- .../components/element_content/element_content.js | 10 +++++++++- .../public/components/element_wrapper/lib/handlers.js | 2 ++ .../shareable_runtime/components/rendered_element.tsx | 1 + x-pack/legacy/plugins/canvas/types/renderers.ts | 2 ++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx index 8642ebd901bb4..c8cc1fe389619 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/renderers/embeddable/embeddable.tsx @@ -70,7 +70,9 @@ const embeddable = () => ({ { input, embeddableType }: EmbeddableExpression<EmbeddableInput>, handlers: RendererHandlers ) => { - if (!embeddablesRegistry[input.id]) { + const uniqueId = handlers.getElementId(); + + if (!embeddablesRegistry[uniqueId]) { const factory = Array.from(start.getEmbeddableFactories()).find( embeddableFactory => embeddableFactory.type === embeddableType ) as EmbeddableFactory<EmbeddableInput>; @@ -82,7 +84,7 @@ const embeddable = () => ({ const embeddableObject = await factory.createFromSavedObject(input.id, input); - embeddablesRegistry[input.id] = embeddableObject; + embeddablesRegistry[uniqueId] = embeddableObject; ReactDOM.unmountComponentAtNode(domNode); const subscription = embeddableObject.getInput$().subscribe(function(updatedInput) { @@ -100,12 +102,12 @@ const embeddable = () => ({ subscription.unsubscribe(); handlers.onEmbeddableDestroyed(); - delete embeddablesRegistry[input.id]; + delete embeddablesRegistry[uniqueId]; return ReactDOM.unmountComponentAtNode(domNode); }); } else { - embeddablesRegistry[input.id].updateInput(input); + embeddablesRegistry[uniqueId].updateInput(input); } }, }); diff --git a/x-pack/legacy/plugins/canvas/public/components/element_content/element_content.js b/x-pack/legacy/plugins/canvas/public/components/element_content/element_content.js index 1926fb4aaa5eb..114a457d167e7 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_content/element_content.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_content/element_content.js @@ -54,6 +54,7 @@ export const ElementContent = compose( onComplete, onEmbeddableInputChange, onEmbeddableDestroyed, + getElementId, } = handlers; return Style.it( @@ -76,7 +77,14 @@ export const ElementContent = compose( config={renderable.value} css={renderable.css} // This is an actual CSS stylesheet string, it will be scoped by RenderElement size={size} // Size is only passed for the purpose of triggering the resize event, it isn't really used otherwise - handlers={{ getFilter, setFilter, done, onEmbeddableInputChange, onEmbeddableDestroyed }} + handlers={{ + getFilter, + setFilter, + done, + onEmbeddableInputChange, + onEmbeddableDestroyed, + getElementId, + }} /> </ElementShareContainer> </div> diff --git a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js index e93cea597901f..d71c5ead2c802 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js @@ -36,6 +36,8 @@ export const createHandlers = dispatch => { completeFn = fn; }, + getElementId: () => element.id, + onEmbeddableInputChange(embeddableExpression) { dispatch(updateEmbeddableExpression({ elementId: element.id, embeddableExpression })); }, diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/components/rendered_element.tsx b/x-pack/legacy/plugins/canvas/shareable_runtime/components/rendered_element.tsx index 317a3417841b8..c4a009db3a376 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/components/rendered_element.tsx +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/components/rendered_element.tsx @@ -67,6 +67,7 @@ export class RenderedElementComponent extends PureComponent<Props> { done: () => {}, onDestroy: () => {}, onResize: () => {}, + getElementId: () => '', setFilter: () => {}, getFilter: () => '', onEmbeddableInputChange: () => {}, diff --git a/x-pack/legacy/plugins/canvas/types/renderers.ts b/x-pack/legacy/plugins/canvas/types/renderers.ts index af1710e69c257..2564b045d1cf7 100644 --- a/x-pack/legacy/plugins/canvas/types/renderers.ts +++ b/x-pack/legacy/plugins/canvas/types/renderers.ts @@ -9,6 +9,8 @@ type GenericCallback = (callback: () => void) => void; export interface RendererHandlers { /** Handler to invoke when an element has finished rendering */ done: () => void; + /** Get the id of the element being rendered. Can be used as a unique ID in a render function */ + getElementId: () => string; /** Handler to invoke when an element is deleted or changes to a different render type */ onDestroy: GenericCallback; /** Handler to invoke when an element's dimensions have changed*/ From 9af0157b4f6c6319022f1edf49c4e5a9452c0135 Mon Sep 17 00:00:00 2001 From: Tyler Smalley <tyler.smalley@elastic.co> Date: Tue, 4 Feb 2020 14:00:15 -0800 Subject: [PATCH 54/60] [docs] Update upgrade version path (#56658) When upgrading to the next major version, users should first upgrade to the last minor in the previous release. In 6.x this is 6.8. Signed-off-by: Tyler Smalley <tyler.smalley@elastic.co> --- docs/setup/upgrade.asciidoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/setup/upgrade.asciidoc b/docs/setup/upgrade.asciidoc index 8c03032bb8ac3..982f1167f3871 100644 --- a/docs/setup/upgrade.asciidoc +++ b/docs/setup/upgrade.asciidoc @@ -50,7 +50,7 @@ instructions. [[upgrade-6x]] === Upgrading from 6.x -The recommended path is to upgrade to 6.7 before upgrading to 7.0. This makes it +The recommended path is to upgrade to 6.8 before upgrading to 7.0. This makes it easier to identify the required changes, and enables you to use the Upgrade Assistant to prepare for your upgrade to 7.0. @@ -59,11 +59,11 @@ dashboards is supported. [float] [[upgrade-67]] -=== Upgrading from 6.7 -To help you prepare for your upgrade to 7.0, 6.7 includes an https://www.elastic.co/guide/en/kibana/6.7/upgrade-assistant.html[Upgrade Assistant] -To access the assistant, go to *Management > 7.0 Upgrade Assistant*. +=== Upgrading from 6.8 +To help you prepare for your upgrade to 7.0, 6.8 includes an https://www.elastic.co/guide/en/kibana/6.8/upgrade-assistant.html[Upgrade Assistant] +To access the assistant, go to *Management > 7.0 Upgrade Assistant*. -After you have addressed any issues that were identified by the Upgrade +After you have addressed any issues that were identified by the Upgrade Assistant, <<upgrade-standard,upgrade to 7.0>>. From 2fc1f791c978af5235995508d82a3e90cb40eb4a Mon Sep 17 00:00:00 2001 From: Andrew Goldstein <andrew-goldstein@users.noreply.github.com> Date: Tue, 4 Feb 2020 15:24:10 -0700 Subject: [PATCH 55/60] [SIEM] Fixes Signals count spinner (#56797) ## Fixes an issue where the Signals count spinner can spin forever Per the animated gif below, in `7.6` `BC 4`, the `Signals count` spinner on the Overview page spins forever until the signals index is created (in the current Kibana space): ![signals-count-loading-spinner](https://user-images.githubusercontent.com/4459398/73785251-2ca42000-4754-11ea-8671-daa81f351c9b.gif) The `Signals count` spinner will spin forever until the user clicks the `Detections` tab, which-in turn creates the signals index (if it doesn't exist), per the animated gif below: ![create-signals-index](https://user-images.githubusercontent.com/4459398/73785319-4ba2b200-4754-11ea-9bb0-a745a8b2be5d.gif) This behavior is an issue because: - When a fresh deployment is created on Elastic Cloud, a user won't understand why the `Signals count` widget is always spinning on the `Overview` page. (The user must click the `Detections` page to resolve this.) - In deployments where authentication is disabled, or, for _reasons_, a Detections index will never be created, the `Signals count` spinner on the Overview page will always spin. To reproduce: 1. Spin up a new `7.6` `BC 4` deployment on Elastic Cloud 2. Login to Kibana for the first time 3. Navigate to the SIEM app **Expected result** - All histograms on the Overview page eventually stop displaying their respective loading spinners **Actual result** - The `Signals count` widget spinner spins forever. (The user must click the `Detections` tab to create the signals index.) ## Deleting the signals index To reproduce the issue above when a signals index has already been created (by clicking on the Detections tab), run the following from the Kibana `Dev Tools` `Console`: ``` DELETE /.siem-signals-default-000001 ``` It is also possible to reproduce this issue by creating a new space, because it won't have a signals index. https://github.com/elastic/siem-team/issues/514 --- .../signals_histogram_panel/helpers.test.tsx | 35 +++++++++++++++++++ .../signals_histogram_panel/helpers.tsx | 14 ++++++++ .../signals_histogram_panel/index.tsx | 16 +++++---- .../detection_engine/detection_engine.tsx | 1 - 4 files changed, 59 insertions(+), 7 deletions(-) create mode 100644 x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.test.tsx diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.test.tsx new file mode 100644 index 0000000000000..2758625c0d4af --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.test.tsx @@ -0,0 +1,35 @@ +/* + * 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 { showInitialLoadingSpinner } from './helpers'; + +describe('helpers', () => { + describe('showInitialLoadingSpinner', () => { + test('it should (only) show the spinner during initial loading, while we are fetching data', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: true })).toBe( + true + ); + }); + + test('it should STOP showing the spinner (during initial loading) when the first data fetch completes', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: true, isLoadingSignals: false })).toBe( + false + ); + }); + + test('it should NOT show the spinner after initial loading has completed, even if the user requests more data (e.g. by clicking Refresh)', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: true })).toBe( + false + ); + }); + + test('it should NOT show the spinner after initial loading has completed', () => { + expect(showInitialLoadingSpinner({ isInitialLoading: false, isLoadingSignals: false })).toBe( + false + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.tsx index 551850fa610db..27ee552146092 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/helpers.tsx @@ -76,3 +76,17 @@ export const getSignalsHistogramQuery = ( }, }, }); + +/** + * Returns `true` when the signals histogram initial loading spinner should be shown + * + * @param isInitialLoading The loading spinner will only be displayed if this value is `true`, because after initial load, a different, non-spinner loading indicator is displayed + * @param isLoadingSignals When `true`, IO is being performed to request signals (for rendering in the histogram) + */ +export const showInitialLoadingSpinner = ({ + isInitialLoading, + isLoadingSignals, +}: { + isInitialLoading: boolean; + isLoadingSignals: boolean; +}): boolean => isInitialLoading && isLoadingSignals; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx index 29aaa951ff71a..4de471d6733cf 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx @@ -23,7 +23,7 @@ import { InspectButtonContainer } from '../../../../components/inspect'; import { useQuerySignals } from '../../../../containers/detection_engine/signals/use_query'; import { MatrixLoader } from '../../../../components/matrix_histogram/matrix_loader'; -import { formatSignalsData, getSignalsHistogramQuery } from './helpers'; +import { formatSignalsData, getSignalsHistogramQuery, showInitialLoadingSpinner } from './helpers'; import * as i18n from './translations'; const DEFAULT_PANEL_HEIGHT = 300; @@ -54,7 +54,6 @@ interface SignalsHistogramPanelProps { from: number; query?: Query; legendPosition?: Position; - loadingInitial?: boolean; panelHeight?: number; signalIndexName: string | null; setQuery: (params: RegisterQuery) => void; @@ -75,7 +74,6 @@ export const SignalsHistogramPanel = memo<SignalsHistogramPanelProps>( query, from, legendPosition = 'right', - loadingInitial = false, panelHeight = DEFAULT_PANEL_HEIGHT, setQuery, signalIndexName, @@ -86,7 +84,7 @@ export const SignalsHistogramPanel = memo<SignalsHistogramPanelProps>( title = i18n.HISTOGRAM_HEADER, updateDateRange, }) => { - const [isInitialLoading, setIsInitialLoading] = useState(loadingInitial || true); + const [isInitialLoading, setIsInitialLoading] = useState(true); const [defaultNumberFormat] = useUiSetting$<string>(DEFAULT_NUMBER_FORMAT); const [totalSignalsObj, setTotalSignalsObj] = useState<SignalsTotal>(defaultTotalSignalsObj); const [selectedStackByOption, setSelectedStackByOption] = useState<SignalsHistogramOption>( @@ -124,10 +122,16 @@ export const SignalsHistogramPanel = memo<SignalsHistogramPanelProps>( const formattedSignalsData = useMemo(() => formatSignalsData(signalsData), [signalsData]); useEffect(() => { - if (!loadingInitial && isInitialLoading && !isLoadingSignals && signalsData) { + let canceled = false; + + if (!canceled && !showInitialLoadingSpinner({ isInitialLoading, isLoadingSignals })) { setIsInitialLoading(false); } - }, [loadingInitial, isLoadingSignals, signalsData]); + + return () => { + canceled = true; // prevent long running data fetches from updating state after unmounting + }; + }, [isInitialLoading, isLoadingSignals, setIsInitialLoading]); useEffect(() => { return () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index d854c377e6ec8..ff6722840fd6b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -177,7 +177,6 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps> deleteQuery={deleteQuery} filters={filters} from={from} - loadingInitial={loading} query={query} setQuery={setQuery} showTotalSignalsCount={true} From 5f9386471b24913cd08e23573990ef82c73028fb Mon Sep 17 00:00:00 2001 From: Luke Elmers <luke.elmers@elastic.co> Date: Tue, 4 Feb 2020 17:35:49 -0700 Subject: [PATCH 56/60] Move ui/agg_types in to shim data plugin (#56353) --- .../actions/filters/brush_event.test.js | 2 +- src/legacy/core_plugins/data/public/index.ts | 50 ++++- src/legacy/core_plugins/data/public/plugin.ts | 28 ++- .../buckets/_terms_other_bucket_helper.js | 2 +- .../data/public/search/aggs}/agg_config.ts | 33 +-- .../data/public/search/aggs}/agg_configs.ts | 5 +- .../data/public/search/aggs}/agg_groups.ts | 4 +- .../public/search/aggs}/agg_params.test.ts | 2 +- .../data/public/search/aggs}/agg_params.ts | 4 +- .../data/public/search/aggs}/agg_type.test.ts | 12 +- .../data/public/search/aggs}/agg_type.ts | 21 +- .../data/public/search/aggs}/agg_types.ts | 26 ++- .../search/aggs}/buckets/_bucket_agg_type.ts | 2 +- .../search/aggs}/buckets/_interval_options.ts | 18 +- .../buckets/_terms_other_bucket_helper.js | 2 +- .../search/aggs}/buckets/bucket_agg_types.ts | 0 .../create_filter/date_histogram.test.ts | 2 +- .../buckets/create_filter/date_histogram.ts | 2 +- .../buckets/create_filter/date_range.test.ts | 2 +- .../aggs}/buckets/create_filter/date_range.ts | 2 +- .../buckets/create_filter/filters.test.ts | 0 .../aggs}/buckets/create_filter/filters.ts | 2 +- .../buckets/create_filter/histogram.test.ts | 2 +- .../aggs}/buckets/create_filter/histogram.ts | 2 +- .../buckets/create_filter/ip_range.test.ts | 2 +- .../aggs}/buckets/create_filter/ip_range.ts | 2 +- .../aggs}/buckets/create_filter/range.test.ts | 2 +- .../aggs}/buckets/create_filter/range.ts | 2 +- .../aggs}/buckets/create_filter/terms.test.ts | 2 +- .../aggs}/buckets/create_filter/terms.ts | 2 +- .../search/aggs}/buckets/date_histogram.ts | 14 +- .../search/aggs}/buckets/date_range.test.ts | 0 .../public/search/aggs}/buckets/date_range.ts | 4 +- .../public/search/aggs}/buckets/filter.ts | 2 +- .../public/search/aggs}/buckets/filters.ts | 6 +- .../search/aggs}/buckets/geo_hash.test.ts | 4 +- .../public/search/aggs}/buckets/geo_hash.ts | 6 +- .../public/search/aggs}/buckets/geo_tile.ts | 4 +- .../search/aggs}/buckets/histogram.test.ts | 0 .../public/search/aggs}/buckets/histogram.ts | 6 +- .../public/search/aggs}/buckets/ip_range.ts | 6 +- .../aggs}/buckets/lib/cidr_mask.test.ts | 0 .../search/aggs}/buckets/lib/cidr_mask.ts | 2 +- .../search/aggs}/buckets/lib/geo_utils.ts | 0 .../buckets/migrate_include_exclude_format.ts | 0 .../public/search/aggs}/buckets/range.test.ts | 2 +- .../data/public/search/aggs}/buckets/range.ts | 8 +- .../public/search/aggs}/buckets/range_key.ts | 0 .../aggs}/buckets/significant_terms.test.ts | 3 +- .../search/aggs}/buckets/significant_terms.ts | 10 +- .../public/search/aggs}/buckets/terms.test.ts | 0 .../data/public/search/aggs}/buckets/terms.ts | 39 ++-- .../aggs}/filter/agg_type_filters.test.ts | 11 +- .../search/aggs}/filter/agg_type_filters.ts | 8 +- .../data/public/search/aggs}/filter/index.ts | 0 .../search/aggs}/filter/prop_filter.test.ts | 0 .../public/search/aggs}/filter/prop_filter.ts | 0 .../data/public/search/aggs}/index.test.ts | 0 .../data/public/search/aggs/index.ts | 52 +++++ .../data/public/search/aggs}/metrics/avg.ts | 6 +- .../public/search/aggs}/metrics/bucket_avg.ts | 4 +- .../public/search/aggs}/metrics/bucket_max.ts | 4 +- .../public/search/aggs}/metrics/bucket_min.ts | 4 +- .../public/search/aggs}/metrics/bucket_sum.ts | 4 +- .../search/aggs}/metrics/cardinality.ts | 6 +- .../data/public/search/aggs}/metrics/count.ts | 6 +- .../search/aggs}/metrics/cumulative_sum.ts | 4 +- .../public/search/aggs}/metrics/derivative.ts | 4 +- .../public/search/aggs}/metrics/geo_bounds.ts | 6 +- .../search/aggs}/metrics/geo_centroid.ts | 6 +- .../lib/get_response_agg_config_class.ts | 0 .../metrics/lib/make_nested_label.test.ts | 0 .../aggs}/metrics/lib/make_nested_label.ts | 0 .../aggs}/metrics/lib/nested_agg_helpers.ts | 0 .../aggs}/metrics/lib/ordinal_suffix.test.ts | 0 .../aggs}/metrics/lib/ordinal_suffix.ts | 0 .../metrics/lib/parent_pipeline_agg_helper.ts | 4 +- .../metrics/lib/parent_pipeline_agg_writer.ts | 4 +- .../lib/sibling_pipeline_agg_helper.ts | 6 +- .../lib/sibling_pipeline_agg_writer.ts | 0 .../data/public/search/aggs}/metrics/max.ts | 6 +- .../search/aggs}/metrics/median.test.ts | 4 +- .../public/search/aggs}/metrics/median.ts | 6 +- .../search/aggs}/metrics/metric_agg_type.ts | 7 +- .../search/aggs}/metrics/metric_agg_types.ts | 0 .../data/public/search/aggs}/metrics/min.ts | 6 +- .../public/search/aggs}/metrics/moving_avg.ts | 4 +- .../aggs}/metrics/parent_pipeline.test.ts | 0 .../aggs}/metrics/percentile_ranks.test.ts | 4 +- .../search/aggs}/metrics/percentile_ranks.ts | 8 +- .../search/aggs}/metrics/percentiles.test.ts | 4 +- .../search/aggs}/metrics/percentiles.ts | 8 +- .../aggs}/metrics/percentiles_get_value.ts | 0 .../search/aggs}/metrics/serial_diff.ts | 4 +- .../aggs}/metrics/sibling_pipeline.test.ts | 0 .../aggs}/metrics/std_deviation.test.ts | 0 .../search/aggs}/metrics/std_deviation.ts | 12 +- .../data/public/search/aggs}/metrics/sum.ts | 6 +- .../search/aggs}/metrics/top_hit.test.ts | 2 +- .../public/search/aggs}/metrics/top_hit.ts | 22 +- .../public/search/aggs}/param_types/agg.ts | 0 .../public/search/aggs}/param_types/base.ts | 6 +- .../search/aggs}/param_types/field.test.ts | 2 +- .../public/search/aggs}/param_types/field.ts | 27 ++- .../param_types/filter/field_filters.test.ts | 2 +- .../aggs}/param_types/filter/field_filters.ts | 0 .../search/aggs}/param_types/filter/index.ts | 0 .../public/search/aggs}/param_types/index.ts | 0 .../search/aggs}/param_types/json.test.ts | 0 .../public/search/aggs}/param_types/json.ts | 0 .../search/aggs}/param_types/optioned.test.ts | 0 .../search/aggs}/param_types/optioned.ts | 0 .../search/aggs}/param_types/string.test.ts | 0 .../public/search/aggs}/param_types/string.ts | 0 .../data/public/search/aggs}/schemas.ts | 4 +- .../data/public/search/aggs/types.ts | 29 +++ .../data/public/search/aggs}/utils.test.tsx | 0 .../data/public/search/aggs}/utils.ts | 4 +- .../data/public/search/expressions/esaggs.ts | 4 +- .../data/public/search/expressions/utils.ts | 6 +- .../core_plugins/data/public/search/index.ts | 1 + .../data/public/search/search_service.ts | 93 ++++++++ .../core_plugins/data/public/search/types.ts | 1 + .../kibana/public/discover/kibana_services.ts | 2 +- .../public/components/agg.test.tsx | 14 +- .../public/components/agg.tsx | 4 +- .../public/components/agg_add.tsx | 4 +- .../public/components/agg_common_props.ts | 12 +- .../public/components/agg_group.test.tsx | 12 +- .../public/components/agg_group.tsx | 8 +- .../components/agg_group_helper.test.ts | 8 +- .../public/components/agg_group_helper.tsx | 14 +- .../public/components/agg_group_state.tsx | 4 +- .../public/components/agg_param_props.ts | 6 +- .../public/components/agg_params.test.tsx | 4 +- .../public/components/agg_params.tsx | 4 +- .../components/agg_params_helper.test.ts | 22 +- .../public/components/agg_params_helper.ts | 18 +- .../public/components/agg_select.tsx | 12 +- .../components/controls/agg_control_props.tsx | 4 +- .../components/controls/agg_utils.test.tsx | 8 +- .../public/components/controls/field.test.tsx | 6 +- .../public/components/controls/field.tsx | 7 +- .../public/components/controls/filter.tsx | 4 +- .../components/controls/metric_agg.test.tsx | 6 +- .../public/components/controls/order_agg.tsx | 8 +- .../public/components/controls/sub_agg.tsx | 4 +- .../public/components/controls/sub_metric.tsx | 4 +- .../public/components/controls/test_utils.ts | 6 +- .../controls/top_aggregate.test.tsx | 18 +- .../components/controls/top_aggregate.tsx | 6 +- .../components/controls/utils/agg_utils.ts | 10 +- .../components/controls/utils/use_handlers.ts | 8 +- .../public/components/sidebar/data_tab.tsx | 8 +- .../components/sidebar/state/actions.ts | 18 +- .../components/sidebar/state/reducers.ts | 4 +- .../public/legacy_imports.ts | 31 ++- .../public/vis_options_props.tsx | 4 +- .../public/vis_type_agg_filter.ts | 12 +- .../vis_type_table/public/legacy_imports.ts | 2 +- .../public/table_vis_controller.test.ts | 4 +- .../options/metrics_axes/index.test.tsx | 12 +- .../components/options/metrics_axes/index.tsx | 4 +- .../vis_type_vislib/public/legacy_imports.ts | 2 +- .../public/embeddable/query_geohash_bounds.ts | 4 +- .../visualizations/public/legacy_imports.ts | 7 +- .../public/legacy/build_pipeline.test.ts | 10 +- .../np_ready/public/legacy/build_pipeline.ts | 12 +- .../public/np_ready/public/vis.d.ts | 4 +- .../public/np_ready/public/vis.js | 2 +- .../agg_response/tabify/_get_columns.ts | 8 +- src/legacy/ui/public/agg_types/index.ts | 78 ++++++- .../ui/public/time_buckets/time_buckets.js | 2 +- .../ui/public/vis/__tests__/_agg_config.js | 4 +- .../config/editor_config_providers.test.ts | 8 +- .../vis/config/editor_config_providers.ts | 6 +- .../loader/pipeline_helpers/utilities.ts | 12 +- .../editor_frame_plugin/plugin.test.tsx | 3 - .../definitions/date_histogram.test.tsx | 34 ++- .../operations/definitions/date_histogram.tsx | 2 +- x-pack/legacy/plugins/rollup/public/legacy.ts | 4 +- .../plugins/rollup/public/legacy_imports.ts | 4 +- .../translations/translations/ja-JP.json | 209 +++++++++--------- .../translations/translations/zh-CN.json | 209 +++++++++--------- .../watcher/public/legacy/time_buckets.js | 2 +- 185 files changed, 1039 insertions(+), 707 deletions(-) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/__tests__/buckets/_terms_other_bucket_helper.js (99%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_config.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_configs.ts (98%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_groups.ts (87%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_params.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_params.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_type.test.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_type.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/agg_types.ts (73%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/_bucket_agg_type.ts (96%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/_interval_options.ts (66%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/_terms_other_bucket_helper.js (99%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/bucket_agg_types.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/date_histogram.test.ts (98%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/date_histogram.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/date_range.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/date_range.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/filters.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/filters.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/histogram.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/histogram.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/ip_range.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/ip_range.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/range.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/range.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/terms.test.ts (98%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/create_filter/terms.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/date_histogram.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/date_range.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/date_range.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/filter.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/filters.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/geo_hash.test.ts (98%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/geo_hash.ts (96%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/geo_tile.ts (92%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/histogram.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/histogram.ts (96%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/ip_range.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/lib/cidr_mask.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/lib/cidr_mask.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/lib/geo_utils.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/migrate_include_exclude_format.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/range.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/range.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/range_key.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/significant_terms.test.ts (96%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/significant_terms.ts (82%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/terms.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/buckets/terms.ts (85%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/filter/agg_type_filters.test.ts (88%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/filter/agg_type_filters.ts (92%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/filter/index.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/filter/prop_filter.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/filter/prop_filter.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/index.test.ts (100%) create mode 100644 src/legacy/core_plugins/data/public/search/aggs/index.ts rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/avg.ts (85%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/bucket_avg.ts (91%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/bucket_max.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/bucket_min.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/bucket_sum.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/cardinality.ts (86%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/count.ts (87%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/cumulative_sum.ts (88%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/derivative.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/geo_bounds.ts (84%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/geo_centroid.ts (84%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/get_response_agg_config_class.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/make_nested_label.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/make_nested_label.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/nested_agg_helpers.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/ordinal_suffix.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/ordinal_suffix.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/parent_pipeline_agg_helper.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/parent_pipeline_agg_writer.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/sibling_pipeline_agg_helper.ts (93%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/lib/sibling_pipeline_agg_writer.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/max.ts (86%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/median.test.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/median.ts (88%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/metric_agg_type.ts (92%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/metric_agg_types.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/min.ts (86%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/moving_avg.ts (92%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/parent_pipeline.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/percentile_ranks.test.ts (96%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/percentile_ranks.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/percentiles.test.ts (95%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/percentiles.ts (88%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/percentiles_get_value.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/serial_diff.ts (89%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/sibling_pipeline.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/std_deviation.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/std_deviation.ts (85%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/sum.ts (86%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/top_hit.test.ts (99%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/metrics/top_hit.ts (87%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/agg.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/base.ts (94%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/field.test.ts (98%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/field.ts (83%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/filter/field_filters.test.ts (97%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/filter/field_filters.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/filter/index.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/index.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/json.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/json.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/optioned.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/optioned.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/string.test.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/param_types/string.ts (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/schemas.ts (98%) create mode 100644 src/legacy/core_plugins/data/public/search/aggs/types.ts rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/utils.test.tsx (100%) rename src/legacy/{ui/public/agg_types => core_plugins/data/public/search/aggs}/utils.ts (92%) create mode 100644 src/legacy/core_plugins/data/public/search/search_service.ts diff --git a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js index a6fe58503cd02..743f6caee4edd 100644 --- a/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js +++ b/src/legacy/core_plugins/data/public/actions/filters/brush_event.test.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import moment from 'moment'; import expect from '@kbn/expect'; -jest.mock('../../../../../ui/public/agg_types/agg_configs', () => ({ +jest.mock('../../search/aggs', () => ({ AggConfigs: function AggConfigs() { return { createAggConfig: ({ params }) => ({ diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 7fe487667f94e..50120292a627a 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -18,7 +18,7 @@ */ // /// Define plugin function -import { DataPlugin as Plugin, DataStart } from './plugin'; +import { DataPlugin as Plugin } from './plugin'; export function plugin() { return new Plugin(); @@ -27,14 +27,58 @@ export function plugin() { // /// Export types & static code /** @public types */ -export { DataStart }; +export { DataSetup, DataStart } from './plugin'; export { SavedQueryAttributes, SavedQuery, SavedQueryTimeFilter, } from '../../../../plugins/data/public'; +export { + // agg_types + AggParam, + AggParamOption, + DateRangeKey, + IAggConfig, + IAggConfigs, + IAggType, + IFieldParamType, + IMetricAggType, + IpRangeKey, + ISchemas, + OptionedParamEditorProps, + OptionedValueProp, +} from './search/types'; /** @public static code */ export * from '../common'; export { FilterStateManager } from './filter/filter_manager'; -export { getRequestInspectorStats, getResponseInspectorStats } from './search'; +export { + // agg_types TODO need to group these under a namespace or prefix + AggParamType, + AggTypeFilters, // TODO convert to interface + aggTypeFilters, + AggTypeFieldFilters, // TODO convert to interface + AggGroupNames, + aggGroupNamesMap, + BUCKET_TYPES, + CidrMask, + convertDateRangeToString, + convertIPRangeToString, + intervalOptions, // only used in Discover + isDateHistogramBucketAggConfig, + isStringType, + isType, + isValidInterval, + isValidJson, + METRIC_TYPES, + OptionedParamType, + parentPipelineType, + propFilter, + Schema, + Schemas, + siblingPipelineType, + termsAggFilter, + // search_source + getRequestInspectorStats, + getResponseInspectorStats, +} from './search'; diff --git a/src/legacy/core_plugins/data/public/plugin.ts b/src/legacy/core_plugins/data/public/plugin.ts index da35366cdff31..ebc470555d87c 100644 --- a/src/legacy/core_plugins/data/public/plugin.ts +++ b/src/legacy/core_plugins/data/public/plugin.ts @@ -45,6 +45,8 @@ import { } from '../../../../plugins/embeddable/public/lib/triggers'; import { IUiActionsSetup, IUiActionsStart } from '../../../../plugins/ui_actions/public'; +import { SearchSetup, SearchStart, SearchService } from './search/search_service'; + export interface DataPluginSetupDependencies { data: DataPublicPluginSetup; expressions: ExpressionsSetup; @@ -56,12 +58,23 @@ export interface DataPluginStartDependencies { uiActions: IUiActionsStart; } +/** + * Interface for this plugin's returned `setup` contract. + * + * @public + */ +export interface DataSetup { + search: SearchSetup; +} + /** * Interface for this plugin's returned `start` contract. * * @public */ -export interface DataStart {} // eslint-disable-line @typescript-eslint/no-empty-interface +export interface DataStart { + search: SearchStart; +} /** * Data Plugin - public @@ -76,7 +89,10 @@ export interface DataStart {} // eslint-disable-line @typescript-eslint/no-empty */ export class DataPlugin - implements Plugin<void, DataStart, DataPluginSetupDependencies, DataPluginStartDependencies> { + implements + Plugin<DataSetup, DataStart, DataPluginSetupDependencies, DataPluginStartDependencies> { + private readonly search = new SearchService(); + public setup(core: CoreSetup, { data, uiActions }: DataPluginSetupDependencies) { setInjectedMetadata(core.injectedMetadata); @@ -89,6 +105,10 @@ export class DataPlugin uiActions.registerAction( valueClickAction(data.query.filterManager, data.query.timefilter.timefilter) ); + + return { + search: this.search.setup(core), + }; } public start(core: CoreStart, { data, uiActions }: DataPluginStartDependencies): DataStart { @@ -102,7 +122,9 @@ export class DataPlugin uiActions.attachAction(SELECT_RANGE_TRIGGER, SELECT_RANGE_ACTION); uiActions.attachAction(VALUE_CLICK_TRIGGER, VALUE_CLICK_ACTION); - return {}; + return { + search: this.search.start(core), + }; } public stop() {} diff --git a/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js b/src/legacy/core_plugins/data/public/search/aggs/__tests__/buckets/_terms_other_bucket_helper.js similarity index 99% rename from src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js rename to src/legacy/core_plugins/data/public/search/aggs/__tests__/buckets/_terms_other_bucket_helper.js index acf932c1fb451..247290731df57 100644 --- a/src/legacy/ui/public/agg_types/__tests__/buckets/_terms_other_bucket_helper.js +++ b/src/legacy/core_plugins/data/public/search/aggs/__tests__/buckets/_terms_other_bucket_helper.js @@ -24,7 +24,7 @@ import { mergeOtherBucketAggResponse, updateMissingBucket, } from '../../buckets/_terms_other_bucket_helper'; -import { Vis } from '../../../../../core_plugins/visualizations/public'; +import { Vis } from '../../../../../../../core_plugins/visualizations/public'; import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; const visConfigSingleTerm = { diff --git a/src/legacy/ui/public/agg_types/agg_config.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts similarity index 93% rename from src/legacy/ui/public/agg_types/agg_config.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_config.ts index 17a8b14b57d02..769347a26c34c 100644 --- a/src/legacy/ui/public/agg_types/agg_config.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_config.ts @@ -27,17 +27,17 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; -import { AggType } from './agg_type'; +import { IAggType } from './agg_type'; import { AggGroupNames } from './agg_groups'; import { writeParams } from './agg_params'; -import { AggConfigs } from './agg_configs'; +import { IAggConfigs } from './agg_configs'; import { Schema } from './schemas'; import { ISearchSource, FetchOptions, fieldFormats, KBN_FIELD_TYPES, -} from '../../../../plugins/data/public'; +} from '../../../../../../plugins/data/public'; export interface AggConfigOptions { enabled: boolean; @@ -60,13 +60,13 @@ const unknownSchema: Schema = { group: AggGroupNames.Metrics, }; -const getTypeFromRegistry = (type: string): AggType => { +const getTypeFromRegistry = (type: string): IAggType => { // We need to inline require here, since we're having a cyclic dependency // from somewhere inside agg_types back to AggConfig. - const aggTypes = require('./agg_types').aggTypes; + const aggTypes = require('../aggs').aggTypes; const registeredType = - aggTypes.metrics.find((agg: AggType) => agg.name === type) || - aggTypes.buckets.find((agg: AggType) => agg.name === type); + aggTypes.metrics.find((agg: IAggType) => agg.name === type) || + aggTypes.buckets.find((agg: IAggType) => agg.name === type); if (!registeredType) { throw new Error('unknown type'); @@ -85,6 +85,9 @@ const getSchemaFromRegistry = (schemas: any, schema: string): Schema => { return registeredSchema; }; +// TODO need to make a more explicit interface for this +export type IAggConfig = AggConfig; + export class AggConfig { /** * Ensure that all of the objects in the list have ids, the objects @@ -122,19 +125,19 @@ export class AggConfig { ); } - public aggConfigs: AggConfigs; + public aggConfigs: IAggConfigs; public id: string; public enabled: boolean; public params: any; - public parent?: AggConfigs; + public parent?: IAggConfigs; public brandNew?: boolean; private __schema: Schema; - private __type: AggType; + private __type: IAggType; private __typeDecorations: any; private subAggs: AggConfig[] = []; - constructor(aggConfigs: AggConfigs, opts: AggConfigOptions) { + constructor(aggConfigs: IAggConfigs, opts: AggConfigOptions) { this.aggConfigs = aggConfigs; this.id = String(opts.id || AggConfig.nextId(aggConfigs.aggs as any)); this.enabled = typeof opts.enabled === 'boolean' ? opts.enabled : true; @@ -207,7 +210,7 @@ export class AggConfig { return _.get(this.params, key); } - write(aggs?: AggConfigs) { + write(aggs?: IAggConfigs) { return writeParams<AggConfig>(this.type.params, this, aggs); } @@ -262,7 +265,7 @@ export class AggConfig { * @return {void|Object} - if the config has a dsl representation, it is * returned, else undefined is returned */ - toDsl(aggConfigs?: AggConfigs) { + toDsl(aggConfigs?: IAggConfigs) { if (this.type.hasNoDsl) return; const output = this.write(aggConfigs) as any; @@ -360,7 +363,7 @@ export class AggConfig { if (!this.type) return ''; return percentageMode - ? i18n.translate('common.ui.vis.aggConfig.percentageOfLabel', { + ? i18n.translate('data.search.aggs.percentageOfLabel', { defaultMessage: 'Percentage of {label}', values: { label: this.type.makeLabel(this) }, }) @@ -448,7 +451,7 @@ export class AggConfig { }); } - public setType(type: string | AggType) { + public setType(type: string | IAggType) { this.type = typeof type === 'string' ? getTypeFromRegistry(type) : type; } diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts similarity index 98% rename from src/legacy/ui/public/agg_types/agg_configs.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts index 47e2222abe1e8..7e7e4944b00da 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_configs.ts @@ -35,7 +35,7 @@ import { ISearchSource, FetchOptions, TimeRange, -} from '../../../../plugins/data/public'; +} from '../../../../../../plugins/data/public'; type Schemas = Record<string, any>; @@ -55,6 +55,9 @@ function parseParentAggs(dslLvlCursor: any, dsl: any) { } } +// TODO need to make a more explicit interface for this +export type IAggConfigs = AggConfigs; + export class AggConfigs { public indexPattern: IndexPattern; public schemas: any; diff --git a/src/legacy/ui/public/agg_types/agg_groups.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_groups.ts similarity index 87% rename from src/legacy/ui/public/agg_types/agg_groups.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_groups.ts index d08e875bf213e..d21f5c8968840 100644 --- a/src/legacy/ui/public/agg_types/agg_groups.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_groups.ts @@ -28,10 +28,10 @@ export const AggGroupNames = Object.freeze({ export type AggGroupNames = $Values<typeof AggGroupNames>; export const aggGroupNamesMap = () => ({ - [AggGroupNames.Metrics]: i18n.translate('common.ui.aggTypes.aggGroups.metricsText', { + [AggGroupNames.Metrics]: i18n.translate('data.search.aggs.aggGroups.metricsText', { defaultMessage: 'Metrics', }), - [AggGroupNames.Buckets]: i18n.translate('common.ui.aggTypes.aggGroups.bucketsText', { + [AggGroupNames.Buckets]: i18n.translate('data.search.aggs.aggGroups.bucketsText', { defaultMessage: 'Buckets', }), }); diff --git a/src/legacy/ui/public/agg_types/agg_params.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/agg_params.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts index 25e62e06d52d7..30ab272537dad 100644 --- a/src/legacy/ui/public/agg_types/agg_params.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_params.test.ts @@ -21,7 +21,7 @@ import { initParams } from './agg_params'; import { BaseParamType } from './param_types/base'; import { FieldParamType } from './param_types/field'; import { OptionedParamType } from './param_types/optioned'; -import { AggParamType } from '../agg_types/param_types/agg'; +import { AggParamType } from '../aggs/param_types/agg'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/agg_params.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_params.ts similarity index 97% rename from src/legacy/ui/public/agg_types/agg_params.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_params.ts index 262a57f4a5aa3..34727ff4614b9 100644 --- a/src/legacy/ui/public/agg_types/agg_params.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_params.ts @@ -25,7 +25,7 @@ import { JsonParamType } from './param_types/json'; import { BaseParamType } from './param_types/base'; import { AggConfig } from './agg_config'; -import { AggConfigs } from './agg_configs'; +import { IAggConfigs } from './agg_configs'; const paramTypeMap = { field: FieldParamType, @@ -73,7 +73,7 @@ export const writeParams = < >( params: Array<Partial<TAggParam>> = [], aggConfig: TAggConfig, - aggs?: AggConfigs, + aggs?: IAggConfigs, locals?: Record<string, any> ) => { const output = { params: {} as Record<string, any> }; diff --git a/src/legacy/ui/public/agg_types/agg_type.test.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts similarity index 94% rename from src/legacy/ui/public/agg_types/agg_type.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts index 9b34910e81e88..6d4c2d1317f50 100644 --- a/src/legacy/ui/public/agg_types/agg_type.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.test.ts @@ -18,7 +18,7 @@ */ import { AggType, AggTypeConfig } from './agg_type'; -import { AggConfig } from './agg_config'; +import { IAggConfig } from './agg_config'; import { npStart } from 'ui/new_platform'; jest.mock('ui/new_platform'); @@ -48,7 +48,7 @@ describe('AggType Class', () => { describe('makeLabel', () => { it('makes a function when the makeLabel config is not specified', () => { const makeLabel = () => 'label'; - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; const config: AggTypeConfig = { name: 'name', title: 'title', @@ -64,7 +64,7 @@ describe('AggType Class', () => { describe('getResponseAggs/getRequestAggs', () => { it('copies the value', () => { - const testConfig = (aggConfig: AggConfig) => [aggConfig]; + const testConfig = (aggConfig: IAggConfig) => [aggConfig]; const aggType = new AggType({ name: 'name', @@ -78,7 +78,7 @@ describe('AggType Class', () => { }); it('defaults to noop', () => { - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; const aggType = new AggType({ name: 'name', title: 'title', @@ -130,13 +130,13 @@ describe('AggType Class', () => { }); describe('getFormat', function() { - let aggConfig: AggConfig; + let aggConfig: IAggConfig; let field: any; beforeEach(() => { aggConfig = ({ getField: jest.fn(() => field), - } as unknown) as AggConfig; + } as unknown) as IAggConfig; }); it('returns the formatter for the aggConfig', () => { diff --git a/src/legacy/ui/public/agg_types/agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts similarity index 94% rename from src/legacy/ui/public/agg_types/agg_type.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_type.ts index 7ec688277b9c4..56299839d0a6d 100644 --- a/src/legacy/ui/public/agg_types/agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_type.ts @@ -23,11 +23,15 @@ import { npStart } from 'ui/new_platform'; import { initParams } from './agg_params'; import { AggConfig } from './agg_config'; -import { AggConfigs } from './agg_configs'; -import { Adapters } from '../../../../plugins/inspector/public'; +import { IAggConfigs } from './agg_configs'; +import { Adapters } from '../../../../../../plugins/inspector/public'; import { BaseParamType } from './param_types/base'; -import { AggParamType } from '../agg_types/param_types/agg'; -import { KBN_FIELD_TYPES, fieldFormats, ISearchSource } from '../../../../plugins/data/public'; +import { AggParamType } from './param_types/agg'; +import { + KBN_FIELD_TYPES, + fieldFormats, + ISearchSource, +} from '../../../../../../plugins/data/public'; export interface AggTypeConfig< TAggConfig extends AggConfig = AggConfig, @@ -48,7 +52,7 @@ export interface AggTypeConfig< decorateAggConfig?: () => any; postFlightRequest?: ( resp: any, - aggConfigs: AggConfigs, + aggConfigs: IAggConfigs, aggConfig: TAggConfig, searchSource: ISearchSource, inspectorAdapters: Adapters, @@ -66,6 +70,9 @@ const getFormat = (agg: AggConfig) => { return field ? field.format : fieldFormatsService.getDefaultInstance(KBN_FIELD_TYPES.STRING); }; +// TODO need to make a more explicit interface for this +export type IAggType = AggType; + export class AggType< TAggConfig extends AggConfig = AggConfig, TParam extends AggParamType<TAggConfig> = AggParamType<TAggConfig> @@ -178,7 +185,7 @@ export class AggType< */ postFlightRequest: ( resp: any, - aggConfigs: AggConfigs, + aggConfigs: IAggConfigs, aggConfig: TAggConfig, searchSource: ISearchSource, inspectorAdapters: Adapters, @@ -239,7 +246,7 @@ export class AggType< if (config.customLabels !== false) { params.push({ name: 'customLabel', - displayName: i18n.translate('common.ui.aggTypes.string.customLabel', { + displayName: i18n.translate('data.search.aggs.string.customLabel', { defaultMessage: 'Custom label', }), type: 'string', diff --git a/src/legacy/ui/public/agg_types/agg_types.ts b/src/legacy/core_plugins/data/public/search/aggs/agg_types.ts similarity index 73% rename from src/legacy/ui/public/agg_types/agg_types.ts rename to src/legacy/core_plugins/data/public/search/aggs/agg_types.ts index 1b05f5926ebfc..c16eb06eeb116 100644 --- a/src/legacy/ui/public/agg_types/agg_types.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/agg_types.ts @@ -50,8 +50,6 @@ import { bucketAvgMetricAgg } from './metrics/bucket_avg'; import { bucketMinMetricAgg } from './metrics/bucket_min'; import { bucketMaxMetricAgg } from './metrics/bucket_max'; -export { AggType } from './agg_type'; - export const aggTypes = { metrics: [ countMetricAgg, @@ -90,3 +88,27 @@ export const aggTypes = { geoTileBucketAgg, ], }; + +export { AggType } from './agg_type'; +export { AggConfig } from './agg_config'; +export { AggConfigs } from './agg_configs'; +export { FieldParamType } from './param_types'; +export { aggTypeFieldFilters } from './param_types/filter'; +export { parentPipelineAggHelper } from './metrics/lib/parent_pipeline_agg_helper'; + +// static code +export { AggParamType } from './param_types/agg'; +export { AggGroupNames, aggGroupNamesMap } from './agg_groups'; +export { intervalOptions } from './buckets/_interval_options'; // only used in Discover +export { isDateHistogramBucketAggConfig, setBounds } from './buckets/date_histogram'; +export { termsAggFilter } from './buckets/terms'; +export { isType, isStringType } from './buckets/migrate_include_exclude_format'; +export { CidrMask } from './buckets/lib/cidr_mask'; +export { convertDateRangeToString } from './buckets/date_range'; +export { convertIPRangeToString } from './buckets/ip_range'; +export { aggTypeFilters, propFilter } from './filter'; +export { OptionedParamType } from './param_types/optioned'; +export { isValidJson, isValidInterval } from './utils'; +export { BUCKET_TYPES } from './buckets/bucket_agg_types'; +export { METRIC_TYPES } from './metrics/metric_agg_types'; +export { ISchemas, Schema, Schemas } from './schemas'; diff --git a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts similarity index 96% rename from src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts index 9b7c97a8f11b6..546d054c5af97 100644 --- a/src/legacy/ui/public/agg_types/buckets/_bucket_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_bucket_agg_type.ts @@ -18,7 +18,7 @@ */ import { AggConfig } from '../agg_config'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; diff --git a/src/legacy/ui/public/agg_types/buckets/_interval_options.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts similarity index 66% rename from src/legacy/ui/public/agg_types/buckets/_interval_options.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts index 01d0abb7a366c..e196687607d19 100644 --- a/src/legacy/ui/public/agg_types/buckets/_interval_options.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_interval_options.ts @@ -21,7 +21,7 @@ import { IBucketAggConfig } from './_bucket_agg_type'; export const intervalOptions = [ { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.autoDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.autoDisplayName', { defaultMessage: 'Auto', }), val: 'auto', @@ -32,49 +32,49 @@ export const intervalOptions = [ }, }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.millisecondDisplayName', { defaultMessage: 'Millisecond', }), val: 'ms', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.secondDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.secondDisplayName', { defaultMessage: 'Second', }), val: 's', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.minuteDisplayName', { defaultMessage: 'Minute', }), val: 'm', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.hourlyDisplayName', { defaultMessage: 'Hourly', }), val: 'h', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.dailyDisplayName', { defaultMessage: 'Daily', }), val: 'd', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.weeklyDisplayName', { defaultMessage: 'Weekly', }), val: 'w', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.monthlyDisplayName', { defaultMessage: 'Monthly', }), val: 'M', }, { - display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName', { + display: i18n.translate('data.search.aggs.buckets.intervalOptions.yearlyDisplayName', { defaultMessage: 'Yearly', }), val: 'y', diff --git a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js b/src/legacy/core_plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.js similarity index 99% rename from src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js rename to src/legacy/core_plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.js index c8580183756f4..ddab360161744 100644 --- a/src/legacy/ui/public/agg_types/buckets/_terms_other_bucket_helper.js +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/_terms_other_bucket_helper.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { esFilters, esQuery } from '../../../../../plugins/data/public'; +import { esFilters, esQuery } from '../../../../../../../plugins/data/public'; import { AggGroupNames } from '../agg_groups'; /** diff --git a/src/legacy/ui/public/agg_types/buckets/bucket_agg_types.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/bucket_agg_types.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/bucket_agg_types.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/bucket_agg_types.ts diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts similarity index 98% rename from src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts index 9426df7d34c29..e212132257ef6 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.test.ts @@ -23,7 +23,7 @@ import { intervalOptions } from '../_interval_options'; import { AggConfigs } from '../../agg_configs'; import { IBucketDateHistogramAggConfig } from '../date_histogram'; import { BUCKET_TYPES } from '../bucket_agg_types'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.ts similarity index 94% rename from src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.ts index f91a92eab1c33..e634b5daf0ac3 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_histogram.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { IBucketDateHistogramAggConfig } from '../date_histogram'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterDateHistogram = ( agg: IBucketDateHistogramAggConfig, diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts index 9c2c4f72704f4..e224253a6e314 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.test.ts @@ -19,7 +19,7 @@ import moment from 'moment'; import { createFilterDateRange } from './date_range'; -import { fieldFormats } from '../../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.ts similarity index 94% rename from src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.ts index 01689d954a072..f7f2cfdb7bb61 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/date_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/date_range.ts @@ -20,7 +20,7 @@ import moment from 'moment'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { DateRangeKey } from '../date_range'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterDateRange = (agg: IBucketAggConfig, { from, to }: DateRangeKey) => { const filter: esFilters.RangeFilterParams = {}; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/create_filter/filters.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.test.ts diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.ts similarity index 94% rename from src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.ts index 6b614514580b6..715f6895374e6 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/filters.ts @@ -19,7 +19,7 @@ import { get } from 'lodash'; import { IBucketAggConfig } from '../_bucket_agg_type'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterFilters = (aggConfig: IBucketAggConfig, key: string) => { // have the aggConfig write agg dsl params diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts index ef49636f9e0c1..1a78967261fa6 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.test.ts @@ -20,7 +20,7 @@ import { createFilterHistogram } from './histogram'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -import { fieldFormats } from '../../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.ts similarity index 94% rename from src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.ts index fc587fa9ecdb6..820f3de5ae9f0 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/histogram.ts @@ -18,7 +18,7 @@ */ import { IBucketAggConfig } from '../_bucket_agg_type'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterHistogram = (aggConfig: IBucketAggConfig, key: string) => { const value = parseInt(key, 10); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts index a9eca3bbb7a56..e92ba5cb2852a 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.test.ts @@ -19,7 +19,7 @@ import { createFilterIpRange } from './ip_range'; import { AggConfigs } from '../../agg_configs'; -import { fieldFormats } from '../../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts similarity index 95% rename from src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts index a513b8c782739..d78f4579cd713 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/ip_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/ip_range.ts @@ -20,7 +20,7 @@ import { CidrMask } from '../lib/cidr_mask'; import { IBucketAggConfig } from '../_bucket_agg_type'; import { IpRangeKey } from '../ip_range'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterIpRange = (aggConfig: IBucketAggConfig, key: IpRangeKey) => { let range: esFilters.RangeFilterParams; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts index 720e952c28821..2f74f23721813 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.test.ts @@ -18,7 +18,7 @@ */ import { createFilterRange } from './range'; -import { fieldFormats } from '../../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../../../plugins/data/public'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.ts similarity index 93% rename from src/legacy/ui/public/agg_types/buckets/create_filter/range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.ts index 929827c6e3fec..125a30a1ab1dd 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/range.ts @@ -18,7 +18,7 @@ */ import { IBucketAggConfig } from '../_bucket_agg_type'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterRange = (aggConfig: IBucketAggConfig, params: any) => { return esFilters.buildRangeFilter( diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts similarity index 98% rename from src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts index 86c0aa24f529a..d5fd1337f2cb2 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.test.ts @@ -21,7 +21,7 @@ import { createFilterTerms } from './terms'; import { AggConfigs } from '../../agg_configs'; import { BUCKET_TYPES } from '../bucket_agg_types'; import { IBucketAggConfig } from '../_bucket_agg_type'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.ts similarity index 95% rename from src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.ts index 5bd770e672786..e0d1f91c1e16a 100644 --- a/src/legacy/ui/public/agg_types/buckets/create_filter/terms.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/create_filter/terms.ts @@ -18,7 +18,7 @@ */ import { IBucketAggConfig } from '../_bucket_agg_type'; -import { esFilters } from '../../../../../../plugins/data/public'; +import { esFilters } from '../../../../../../../../plugins/data/public'; export const createFilterTerms = (aggConfig: IBucketAggConfig, key: string, params: any) => { const field = aggConfig.params.field; diff --git a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts similarity index 95% rename from src/legacy/ui/public/agg_types/buckets/date_histogram.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts index 33672b54b1f2e..dc0f9baa6d0cc 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_histogram.ts @@ -22,19 +22,17 @@ import moment from 'moment-timezone'; import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; +import { timefilter } from 'ui/timefilter'; +import { TimeBuckets } from 'ui/time_buckets'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { createFilterDateHistogram } from './create_filter/date_histogram'; import { intervalOptions } from './_interval_options'; -import { timefilter } from '../../timefilter'; -import { dateHistogramInterval } from '../../../../core_plugins/data/public'; +import { dateHistogramInterval } from '../../../../common'; import { writeParams } from '../agg_params'; import { isMetricAggType } from '../metrics/metric_agg_type'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; - -// @ts-ignore -import { TimeBuckets } from '../../time_buckets'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); @@ -67,7 +65,7 @@ export function isDateHistogramBucketAggConfig(agg: any): agg is IBucketDateHist export const dateHistogramBucketAgg = new BucketAggType<IBucketDateHistogramAggConfig>({ name: BUCKET_TYPES.DATE_HISTOGRAM, - title: i18n.translate('common.ui.aggTypes.buckets.dateHistogramTitle', { + title: i18n.translate('data.search.aggs.buckets.dateHistogramTitle', { defaultMessage: 'Date Histogram', }), ordered: { @@ -81,7 +79,7 @@ export const dateHistogramBucketAgg = new BucketAggType<IBucketDateHistogramAggC } const field = agg.getFieldDisplayName(); - return i18n.translate('common.ui.aggTypes.buckets.dateHistogramLabel', { + return i18n.translate('data.search.aggs.buckets.dateHistogramLabel', { defaultMessage: '{fieldName} per {intervalDescription}', values: { fieldName: field, diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/date_range.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.test.ts diff --git a/src/legacy/ui/public/agg_types/buckets/date_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts similarity index 95% rename from src/legacy/ui/public/agg_types/buckets/date_range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts index ee04e0657f317..40ef7329dee6b 100644 --- a/src/legacy/ui/public/agg_types/buckets/date_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/date_range.ts @@ -24,9 +24,9 @@ import { BUCKET_TYPES } from './bucket_agg_types'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; -import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; -const dateRangeTitle = i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', { +const dateRangeTitle = i18n.translate('data.search.aggs.buckets.dateRangeTitle', { defaultMessage: 'Date Range', }); diff --git a/src/legacy/ui/public/agg_types/buckets/filter.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts similarity index 93% rename from src/legacy/ui/public/agg_types/buckets/filter.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts index 3f8a898f4c963..b52e2d6cfd4df 100644 --- a/src/legacy/ui/public/agg_types/buckets/filter.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filter.ts @@ -20,7 +20,7 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -const filterTitle = i18n.translate('common.ui.aggTypes.buckets.filterTitle', { +const filterTitle = i18n.translate('data.search.aggs.buckets.filterTitle', { defaultMessage: 'Filter', }); diff --git a/src/legacy/ui/public/agg_types/buckets/filters.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts similarity index 93% rename from src/legacy/ui/public/agg_types/buckets/filters.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts index d9b78b3063e23..6eaf788b83c04 100644 --- a/src/legacy/ui/public/agg_types/buckets/filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/filters.ts @@ -25,14 +25,14 @@ import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { createFilterFilters } from './create_filter/filters'; import { BucketAggType } from './_bucket_agg_type'; -import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { getQueryLog, esQuery, Query } from '../../../../../plugins/data/public'; +import { Storage } from '../../../../../../../plugins/kibana_utils/public'; +import { getQueryLog, esQuery, Query } from '../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from './bucket_agg_types'; const config = chrome.getUiSettingsClient(); const storage = new Storage(window.localStorage); -const filtersTitle = i18n.translate('common.ui.aggTypes.buckets.filtersTitle', { +const filtersTitle = i18n.translate('data.search.aggs.buckets.filtersTitle', { defaultMessage: 'Filters', description: 'The name of an aggregation, that allows to specify multiple individual filters to group data by.', diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts similarity index 98% rename from src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts index effa49f0ade6b..5ff68c5426e34 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.test.ts @@ -18,7 +18,7 @@ */ import { geoHashBucketAgg, IBucketGeoHashGridAggConfig } from './geo_hash'; -import { AggConfigs } from '../agg_configs'; +import { AggConfigs, IAggConfigs } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; jest.mock('ui/new_platform'); @@ -121,7 +121,7 @@ describe('Geohash Agg', () => { describe('getRequestAggs', () => { describe('initial aggregation creation', () => { - let aggConfigs: AggConfigs; + let aggConfigs: IAggConfigs; let geoHashGridAgg: IBucketGeoHashGridAggConfig; beforeEach(() => { diff --git a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.ts similarity index 96% rename from src/legacy/ui/public/agg_types/buckets/geo_hash.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.ts index b2519df6fb175..afd4e18dd266c 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_hash.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_hash.ts @@ -19,9 +19,9 @@ import { i18n } from '@kbn/i18n'; import { geohashColumns } from 'ui/vis/map/decode_geo_hash'; -import chrome from '../../chrome'; +import chrome from 'ui/chrome'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { geoContains, scaleBounds, GeoBoundingBox } from './lib/geo_utils'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -68,7 +68,7 @@ function getPrecision(val: string) { const isOutsideCollar = (bounds: GeoBoundingBox, collar: MapCollar) => bounds && collar && !geoContains(collar, bounds); -const geohashGridTitle = i18n.translate('common.ui.aggTypes.buckets.geohashGridTitle', { +const geohashGridTitle = i18n.translate('data.search.aggs.buckets.geohashGridTitle', { defaultMessage: 'Geohash', }); diff --git a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts similarity index 92% rename from src/legacy/ui/public/agg_types/buckets/geo_tile.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts index ef71e3947566a..57e8f6e8c5ded 100644 --- a/src/legacy/ui/public/agg_types/buckets/geo_tile.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/geo_tile.ts @@ -23,11 +23,11 @@ import { AggConfigOptions } from '../agg_config'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { IBucketAggConfig } from './_bucket_agg_type'; import { METRIC_TYPES } from '../metrics/metric_agg_types'; -const geotileGridTitle = i18n.translate('common.ui.aggTypes.buckets.geotileGridTitle', { +const geotileGridTitle = i18n.translate('data.search.aggs.buckets.geotileGridTitle', { defaultMessage: 'Geotile', }); diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/histogram.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.test.ts diff --git a/src/legacy/ui/public/agg_types/buckets/histogram.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts similarity index 96% rename from src/legacy/ui/public/agg_types/buckets/histogram.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts index 44327c7c19e6d..f7e9ef45961e0 100644 --- a/src/legacy/ui/public/agg_types/buckets/histogram.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/histogram.ts @@ -24,7 +24,7 @@ import { toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; import { BucketAggType, IBucketAggConfig } from './_bucket_agg_type'; import { createFilterHistogram } from './create_filter/histogram'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { BUCKET_TYPES } from './bucket_agg_types'; export interface AutoBounds { @@ -41,7 +41,7 @@ const getUIConfig = () => npStart.core.uiSettings; export const histogramBucketAgg = new BucketAggType<IBucketHistogramAggConfig>({ name: BUCKET_TYPES.HISTOGRAM, - title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', { + title: i18n.translate('data.search.aggs.buckets.histogramTitle', { defaultMessage: 'Histogram', }), ordered: {}, @@ -117,7 +117,7 @@ export const histogramBucketAgg = new BucketAggType<IBucketHistogramAggConfig>({ .catch((e: Error) => { if (e.name === 'AbortError') return; toastNotifications.addWarning( - i18n.translate('common.ui.aggTypes.histogram.missingMaxMinValuesWarning', { + i18n.translate('data.search.aggs.histogram.missingMaxMinValuesWarning', { defaultMessage: 'Unable to retrieve max and min values to auto-scale histogram buckets. This may lead to poor visualization performance.', }) diff --git a/src/legacy/ui/public/agg_types/buckets/ip_range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts similarity index 93% rename from src/legacy/ui/public/agg_types/buckets/ip_range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts index 41141dabf507c..e5497bef49165 100644 --- a/src/legacy/ui/public/agg_types/buckets/ip_range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/ip_range.ts @@ -25,9 +25,9 @@ import { BUCKET_TYPES } from './bucket_agg_types'; // @ts-ignore import { createFilterIpRange } from './create_filter/ip_range'; -import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES, fieldFormats } from '../../../../../../../plugins/data/public'; -const ipRangeTitle = i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { +const ipRangeTitle = i18n.translate('data.search.aggs.buckets.ipRangeTitle', { defaultMessage: 'IPv4 Range', }); @@ -57,7 +57,7 @@ export const ipRangeBucketAgg = new BucketAggType({ return new IpRangeFormat(); }, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.buckets.ipRangeLabel', { + return i18n.translate('data.search.aggs.buckets.ipRangeLabel', { defaultMessage: '{fieldName} IP ranges', values: { fieldName: aggConfig.getFieldDisplayName(), diff --git a/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/lib/cidr_mask.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/lib/cidr_mask.test.ts diff --git a/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts similarity index 95% rename from src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts index aadbbc8c82276..30c4e400fb806 100644 --- a/src/legacy/ui/public/agg_types/buckets/lib/cidr_mask.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/lib/cidr_mask.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Ipv4Address } from '../../../../../../plugins/kibana_utils/public'; +import { Ipv4Address } from '../../../../../../../../plugins/kibana_utils/public'; const NUM_BITS = 32; diff --git a/src/legacy/ui/public/agg_types/buckets/lib/geo_utils.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/lib/geo_utils.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/lib/geo_utils.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/lib/geo_utils.ts diff --git a/src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/migrate_include_exclude_format.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/migrate_include_exclude_format.ts diff --git a/src/legacy/ui/public/agg_types/buckets/range.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/buckets/range.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts index dd85c3b31939f..4c0fa7311461e 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.test.ts @@ -19,7 +19,7 @@ import { AggConfigs } from '../agg_configs'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { fieldFormats } from '../../../../../plugins/data/public'; +import { fieldFormats } from '../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/buckets/range.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.ts similarity index 89% rename from src/legacy/ui/public/agg_types/buckets/range.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/range.ts index f24473e0c68aa..f35db2cc759bd 100644 --- a/src/legacy/ui/public/agg_types/buckets/range.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/range.ts @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { BucketAggType } from './_bucket_agg_type'; -import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { RangeKey } from './range_key'; import { createFilterRange } from './create_filter/range'; import { BUCKET_TYPES } from './bucket_agg_types'; @@ -27,7 +27,7 @@ import { BUCKET_TYPES } from './bucket_agg_types'; const keyCaches = new WeakMap(); const formats = new WeakMap(); -const rangeTitle = i18n.translate('common.ui.aggTypes.buckets.rangeTitle', { +const rangeTitle = i18n.translate('data.search.aggs.buckets.rangeTitle', { defaultMessage: 'Range', }); @@ -36,7 +36,7 @@ export const rangeBucketAgg = new BucketAggType({ title: rangeTitle, createFilter: createFilterRange, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.buckets.rangesLabel', { + return i18n.translate('data.search.aggs.aggTypesLabel', { defaultMessage: '{fieldName} ranges', values: { fieldName: aggConfig.getFieldDisplayName(), @@ -69,7 +69,7 @@ export const rangeBucketAgg = new BucketAggType({ const format = agg.fieldOwnFormatter(); const gte = '\u2265'; const lt = '\u003c'; - return i18n.translate('common.ui.aggTypes.buckets.ranges.rangesFormatMessage', { + return i18n.translate('data.search.aggs.aggTypes.rangesFormatMessage', { defaultMessage: '{gte} {from} and {lt} {to}', values: { gte, diff --git a/src/legacy/ui/public/agg_types/buckets/range_key.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/range_key.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/range_key.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/range_key.ts diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts similarity index 96% rename from src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts index 8db9226e41eec..37b829bfc20fb 100644 --- a/src/legacy/ui/public/agg_types/buckets/significant_terms.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.test.ts @@ -18,6 +18,7 @@ */ import { AggConfigs } from '../index'; +import { IAggConfigs } from '../types'; import { BUCKET_TYPES } from './bucket_agg_types'; import { significantTermsBucketAgg } from './significant_terms'; import { IBucketAggConfig } from './_bucket_agg_type'; @@ -56,7 +57,7 @@ describe('Significant Terms Agg', () => { ); }; - const testSerializeAndWrite = (aggs: AggConfigs) => { + const testSerializeAndWrite = (aggs: IAggConfigs) => { const agg = aggs.aggs[0]; const { [BUCKET_TYPES.SIGNIFICANT_TERMS]: params } = agg.toDsl(); diff --git a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.ts similarity index 82% rename from src/legacy/ui/public/agg_types/buckets/significant_terms.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.ts index 38ca0768d3bc1..bc6c63d569b11 100644 --- a/src/legacy/ui/public/agg_types/buckets/significant_terms.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/significant_terms.ts @@ -22,9 +22,9 @@ import { BucketAggType } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; import { BUCKET_TYPES } from './bucket_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const significantTermsTitle = i18n.translate('common.ui.aggTypes.buckets.significantTermsTitle', { +const significantTermsTitle = i18n.translate('data.search.aggs.buckets.significantTermsTitle', { defaultMessage: 'Significant Terms', }); @@ -32,7 +32,7 @@ export const significantTermsBucketAgg = new BucketAggType({ name: BUCKET_TYPES.SIGNIFICANT_TERMS, title: significantTermsTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.buckets.significantTermsLabel', { + return i18n.translate('data.search.aggs.buckets.significantTermsLabel', { defaultMessage: 'Top {size} unusual terms in {fieldName}', values: { size: aggConfig.params.size, @@ -54,7 +54,7 @@ export const significantTermsBucketAgg = new BucketAggType({ }, { name: 'exclude', - displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.excludeLabel', { + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.excludeLabel', { defaultMessage: 'Exclude', }), type: 'string', @@ -64,7 +64,7 @@ export const significantTermsBucketAgg = new BucketAggType({ }, { name: 'include', - displayName: i18n.translate('common.ui.aggTypes.buckets.significantTerms.includeLabel', { + displayName: i18n.translate('data.search.aggs.buckets.significantTerms.includeLabel', { defaultMessage: 'Include', }), type: 'string', diff --git a/src/legacy/ui/public/agg_types/buckets/terms.test.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/buckets/terms.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/terms.test.ts diff --git a/src/legacy/ui/public/agg_types/buckets/terms.ts b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts similarity index 85% rename from src/legacy/ui/public/agg_types/buckets/terms.ts rename to src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts index 4ced1417402b5..b41b16af122fa 100644 --- a/src/legacy/ui/public/agg_types/buckets/terms.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/buckets/terms.ts @@ -19,19 +19,20 @@ import { noop } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { - getRequestInspectorStats, - getResponseInspectorStats, -} from '../../../../core_plugins/data/public'; +import { getRequestInspectorStats, getResponseInspectorStats } from '../../../index'; import { BucketAggType } from './_bucket_agg_type'; import { BUCKET_TYPES } from './bucket_agg_types'; import { IBucketAggConfig } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import { isStringType, migrateIncludeExcludeFormat } from './migrate_include_exclude_format'; -import { AggConfigs } from '../agg_configs'; +import { IAggConfigs } from '../agg_configs'; -import { Adapters } from '../../../../../plugins/inspector/public'; -import { ISearchSource, fieldFormats, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { Adapters } from '../../../../../../../plugins/inspector/public'; +import { + ISearchSource, + fieldFormats, + KBN_FIELD_TYPES, +} from '../../../../../../../plugins/data/public'; import { buildOtherBucketAgg, @@ -68,7 +69,7 @@ const [orderAggSchema] = new Schemas([ }, ]).all; -const termsTitle = i18n.translate('common.ui.aggTypes.buckets.termsTitle', { +const termsTitle = i18n.translate('data.search.aggs.buckets.termsTitle', { defaultMessage: 'Terms', }); @@ -98,7 +99,7 @@ export const termsBucketAgg = new BucketAggType({ createFilter: createFilterTerms, postFlightRequest: async ( resp: any, - aggConfigs: AggConfigs, + aggConfigs: IAggConfigs, aggConfig: IBucketAggConfig, searchSource: ISearchSource, inspectorAdapters: Adapters, @@ -113,11 +114,11 @@ export const termsBucketAgg = new BucketAggType({ nestedSearchSource.setField('aggs', filterAgg); const request = inspectorAdapters.requests.start( - i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketTitle', { + i18n.translate('data.search.aggs.buckets.terms.otherBucketTitle', { defaultMessage: 'Other bucket', }), { - description: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketDescription', { + description: i18n.translate('data.search.aggs.buckets.terms.otherBucketDescription', { defaultMessage: 'This request counts the number of documents that fall ' + 'outside the criterion of the data buckets.', @@ -212,13 +213,13 @@ export const termsBucketAgg = new BucketAggType({ default: 'desc', options: [ { - text: i18n.translate('common.ui.aggTypes.buckets.terms.orderDescendingTitle', { + text: i18n.translate('data.search.aggs.buckets.terms.orderDescendingTitle', { defaultMessage: 'Descending', }), value: 'desc', }, { - text: i18n.translate('common.ui.aggTypes.buckets.terms.orderAscendingTitle', { + text: i18n.translate('data.search.aggs.buckets.terms.orderAscendingTitle', { defaultMessage: 'Ascending', }), value: 'asc', @@ -238,10 +239,10 @@ export const termsBucketAgg = new BucketAggType({ { name: 'otherBucketLabel', type: 'string', - default: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketLabel', { + default: i18n.translate('data.search.aggs.buckets.terms.otherBucketLabel', { defaultMessage: 'Other', }), - displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForOtherBucketLabel', { + displayName: i18n.translate('data.search.aggs.otherBucket.labelForOtherBucketLabel', { defaultMessage: 'Label for other bucket', }), shouldShow: agg => agg.getParam('otherBucket'), @@ -254,13 +255,13 @@ export const termsBucketAgg = new BucketAggType({ }, { name: 'missingBucketLabel', - default: i18n.translate('common.ui.aggTypes.buckets.terms.missingBucketLabel', { + default: i18n.translate('data.search.aggs.buckets.terms.missingBucketLabel', { defaultMessage: 'Missing', description: `Default label used in charts when documents are missing a field. Visible when you create a chart with a terms aggregation and enable "Show missing values"`, }), type: 'string', - displayName: i18n.translate('common.ui.aggTypes.otherBucket.labelForMissingValuesLabel', { + displayName: i18n.translate('data.search.aggs.otherBucket.labelForMissingValuesLabel', { defaultMessage: 'Label for missing values', }), shouldShow: agg => agg.getParam('missingBucket'), @@ -268,7 +269,7 @@ export const termsBucketAgg = new BucketAggType({ }, { name: 'exclude', - displayName: i18n.translate('common.ui.aggTypes.buckets.terms.excludeLabel', { + displayName: i18n.translate('data.search.aggs.buckets.terms.excludeLabel', { defaultMessage: 'Exclude', }), type: 'string', @@ -278,7 +279,7 @@ export const termsBucketAgg = new BucketAggType({ }, { name: 'include', - displayName: i18n.translate('common.ui.aggTypes.buckets.terms.includeLabel', { + displayName: i18n.translate('data.search.aggs.buckets.terms.includeLabel', { defaultMessage: 'Include', }), type: 'string', diff --git a/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts similarity index 88% rename from src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts index 0344f304877f2..cc1288d339692 100644 --- a/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.test.ts @@ -17,9 +17,10 @@ * under the License. */ -import { IndexPattern } from '../../../../../plugins/data/public'; +import { IndexPattern } from '../../../../../../../plugins/data/public'; import { AggTypeFilters } from './agg_type_filters'; -import { AggConfig, AggType } from '..'; +import { AggConfig } from '..'; +import { IAggType } from '../types'; describe('AggTypeFilters', () => { let registry: AggTypeFilters; @@ -31,13 +32,13 @@ describe('AggTypeFilters', () => { }); it('should filter nothing without registered filters', async () => { - const aggTypes = [{ name: 'count' }, { name: 'sum' }] as AggType[]; + const aggTypes = [{ name: 'count' }, { name: 'sum' }] as IAggType[]; const filtered = registry.filter(aggTypes, indexPattern, aggConfig); expect(filtered).toEqual(aggTypes); }); it('should pass all aggTypes to the registered filter', async () => { - const aggTypes = [{ name: 'count' }, { name: 'sum' }] as AggType[]; + const aggTypes = [{ name: 'count' }, { name: 'sum' }] as IAggType[]; const filter = jest.fn(); registry.addFilter(filter); registry.filter(aggTypes, indexPattern, aggConfig); @@ -46,7 +47,7 @@ describe('AggTypeFilters', () => { }); it('should allow registered filters to filter out aggTypes', async () => { - const aggTypes = [{ name: 'count' }, { name: 'sum' }, { name: 'avg' }] as AggType[]; + const aggTypes = [{ name: 'count' }, { name: 'sum' }, { name: 'avg' }] as IAggType[]; let filtered = registry.filter(aggTypes, indexPattern, aggConfig); expect(filtered).toEqual(aggTypes); diff --git a/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts similarity index 92% rename from src/legacy/ui/public/agg_types/filter/agg_type_filters.ts rename to src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts index 2cc4a6e962214..d3b38ce041d7e 100644 --- a/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/filter/agg_type_filters.ts @@ -17,12 +17,12 @@ * under the License. */ import { IndexPattern } from 'src/plugins/data/public'; -import { AggType, AggConfig } from '..'; +import { IAggConfig, IAggType } from '../types'; type AggTypeFilter = ( - aggType: AggType, + aggType: IAggType, indexPattern: IndexPattern, - aggConfig: AggConfig + aggConfig: IAggConfig ) => boolean; /** @@ -49,7 +49,7 @@ class AggTypeFilters { * @param aggConfig The aggConfig for which the returning list will be used. * @return A filtered list of the passed aggTypes. */ - public filter(aggTypes: AggType[], indexPattern: IndexPattern, aggConfig: AggConfig) { + public filter(aggTypes: IAggType[], indexPattern: IndexPattern, aggConfig: IAggConfig) { const allFilters = Array.from(this.filters); const allowedAggTypes = aggTypes.filter(aggType => { const isAggTypeAllowed = allFilters.every(filter => filter(aggType, indexPattern, aggConfig)); diff --git a/src/legacy/ui/public/agg_types/filter/index.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/index.ts similarity index 100% rename from src/legacy/ui/public/agg_types/filter/index.ts rename to src/legacy/core_plugins/data/public/search/aggs/filter/index.ts diff --git a/src/legacy/ui/public/agg_types/filter/prop_filter.test.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/filter/prop_filter.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.test.ts diff --git a/src/legacy/ui/public/agg_types/filter/prop_filter.ts b/src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.ts similarity index 100% rename from src/legacy/ui/public/agg_types/filter/prop_filter.ts rename to src/legacy/core_plugins/data/public/search/aggs/filter/prop_filter.ts diff --git a/src/legacy/ui/public/agg_types/index.test.ts b/src/legacy/core_plugins/data/public/search/aggs/index.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/index.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/index.test.ts diff --git a/src/legacy/core_plugins/data/public/search/aggs/index.ts b/src/legacy/core_plugins/data/public/search/aggs/index.ts new file mode 100644 index 0000000000000..0fef7f38aae74 --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/index.ts @@ -0,0 +1,52 @@ +/* + * 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. + */ + +export { aggTypes } from './agg_types'; +export { AggType } from './agg_type'; +export { AggConfig } from './agg_config'; +export { AggConfigs } from './agg_configs'; +export { FieldParamType } from './param_types'; +export { MetricAggType } from './metrics/metric_agg_type'; +export { AggTypeFilters } from './filter'; +export { aggTypeFieldFilters, AggTypeFieldFilters } from './param_types/filter'; +export { + parentPipelineAggHelper, + parentPipelineType, +} from './metrics/lib/parent_pipeline_agg_helper'; +export { + siblingPipelineAggHelper, + siblingPipelineType, +} from './metrics/lib/sibling_pipeline_agg_helper'; + +// static code +export { AggParamType } from './param_types/agg'; +export { AggGroupNames, aggGroupNamesMap } from './agg_groups'; +export { intervalOptions } from './buckets/_interval_options'; // only used in Discover +export { isDateHistogramBucketAggConfig, setBounds } from './buckets/date_histogram'; +export { termsAggFilter } from './buckets/terms'; +export { isType, isStringType } from './buckets/migrate_include_exclude_format'; +export { CidrMask } from './buckets/lib/cidr_mask'; +export { convertDateRangeToString } from './buckets/date_range'; +export { convertIPRangeToString } from './buckets/ip_range'; +export { aggTypeFilters, propFilter } from './filter'; +export { OptionedParamType } from './param_types/optioned'; +export { isValidJson, isValidInterval } from './utils'; +export { BUCKET_TYPES } from './buckets/bucket_agg_types'; +export { METRIC_TYPES } from './metrics/metric_agg_types'; +export { ISchemas, Schema, Schemas } from './schemas'; diff --git a/src/legacy/ui/public/agg_types/metrics/avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/avg.ts similarity index 85% rename from src/legacy/ui/public/agg_types/metrics/avg.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/avg.ts index 0222a8e543223..b80671a43d2af 100644 --- a/src/legacy/ui/public/agg_types/metrics/avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/avg.ts @@ -20,9 +20,9 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const averageTitle = i18n.translate('common.ui.aggTypes.metrics.averageTitle', { +const averageTitle = i18n.translate('data.search.aggs.metrics.averageTitle', { defaultMessage: 'Average', }); @@ -30,7 +30,7 @@ export const avgMetricAgg = new MetricAggType({ name: METRIC_TYPES.AVG, title: averageTitle, makeLabel: aggConfig => { - return i18n.translate('common.ui.aggTypes.metrics.averageLabel', { + return i18n.translate('data.search.aggs.metrics.averageLabel', { defaultMessage: 'Average {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/bucket_avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts similarity index 91% rename from src/legacy/ui/public/agg_types/metrics/bucket_avg.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts index 7142546dbd494..9fb28f8631bc6 100644 --- a/src/legacy/ui/public/agg_types/metrics/bucket_avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_avg.ts @@ -25,11 +25,11 @@ import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -const overallAverageLabel = i18n.translate('common.ui.aggTypes.metrics.overallAverageLabel', { +const overallAverageLabel = i18n.translate('data.search.aggs.metrics.overallAverageLabel', { defaultMessage: 'overall average', }); -const averageBucketTitle = i18n.translate('common.ui.aggTypes.metrics.averageBucketTitle', { +const averageBucketTitle = i18n.translate('data.search.aggs.metrics.averageBucketTitle', { defaultMessage: 'Average Bucket', }); diff --git a/src/legacy/ui/public/agg_types/metrics/bucket_max.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/bucket_max.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts index aa5b0521709a5..83837f0de5114 100644 --- a/src/legacy/ui/public/agg_types/metrics/bucket_max.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_max.ts @@ -24,11 +24,11 @@ import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -const overallMaxLabel = i18n.translate('common.ui.aggTypes.metrics.overallMaxLabel', { +const overallMaxLabel = i18n.translate('data.search.aggs.metrics.overallMaxLabel', { defaultMessage: 'overall max', }); -const maxBucketTitle = i18n.translate('common.ui.aggTypes.metrics.maxBucketTitle', { +const maxBucketTitle = i18n.translate('data.search.aggs.metrics.maxBucketTitle', { defaultMessage: 'Max Bucket', }); diff --git a/src/legacy/ui/public/agg_types/metrics/bucket_min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/bucket_min.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts index b5c0b8865e106..d96197693dc2e 100644 --- a/src/legacy/ui/public/agg_types/metrics/bucket_min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_min.ts @@ -22,11 +22,11 @@ import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -const overallMinLabel = i18n.translate('common.ui.aggTypes.metrics.overallMinLabel', { +const overallMinLabel = i18n.translate('data.search.aggs.metrics.overallMinLabel', { defaultMessage: 'overall min', }); -const minBucketTitle = i18n.translate('common.ui.aggTypes.metrics.minBucketTitle', { +const minBucketTitle = i18n.translate('data.search.aggs.metrics.minBucketTitle', { defaultMessage: 'Min Bucket', }); diff --git a/src/legacy/ui/public/agg_types/metrics/bucket_sum.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_sum.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/bucket_sum.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_sum.ts index d4faa81c4041c..1f9392c5bec35 100644 --- a/src/legacy/ui/public/agg_types/metrics/bucket_sum.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/bucket_sum.ts @@ -23,11 +23,11 @@ import { makeNestedLabel } from './lib/make_nested_label'; import { siblingPipelineAggHelper } from './lib/sibling_pipeline_agg_helper'; import { METRIC_TYPES } from './metric_agg_types'; -const overallSumLabel = i18n.translate('common.ui.aggTypes.metrics.overallSumLabel', { +const overallSumLabel = i18n.translate('data.search.aggs.metrics.overallSumLabel', { defaultMessage: 'overall sum', }); -const sumBucketTitle = i18n.translate('common.ui.aggTypes.metrics.sumBucketTitle', { +const sumBucketTitle = i18n.translate('data.search.aggs.metrics.sumBucketTitle', { defaultMessage: 'Sum Bucket', }); diff --git a/src/legacy/ui/public/agg_types/metrics/cardinality.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts similarity index 86% rename from src/legacy/ui/public/agg_types/metrics/cardinality.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts index c69ffae3b4871..147e925521088 100644 --- a/src/legacy/ui/public/agg_types/metrics/cardinality.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/cardinality.ts @@ -21,9 +21,9 @@ import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const uniqueCountTitle = i18n.translate('common.ui.aggTypes.metrics.uniqueCountTitle', { +const uniqueCountTitle = i18n.translate('data.search.aggs.metrics.uniqueCountTitle', { defaultMessage: 'Unique Count', }); @@ -31,7 +31,7 @@ export const cardinalityMetricAgg = new MetricAggType({ name: METRIC_TYPES.CARDINALITY, title: uniqueCountTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.metrics.uniqueCountLabel', { + return i18n.translate('data.search.aggs.metrics.uniqueCountLabel', { defaultMessage: 'Unique count of {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/count.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts similarity index 87% rename from src/legacy/ui/public/agg_types/metrics/count.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts index 22a939cd9a3fd..14a9bd073ff2b 100644 --- a/src/legacy/ui/public/agg_types/metrics/count.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/count.ts @@ -19,18 +19,18 @@ import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; export const countMetricAgg = new MetricAggType({ name: METRIC_TYPES.COUNT, - title: i18n.translate('common.ui.aggTypes.metrics.countTitle', { + title: i18n.translate('data.search.aggs.metrics.countTitle', { defaultMessage: 'Count', }), hasNoDsl: true, makeLabel() { - return i18n.translate('common.ui.aggTypes.metrics.countLabel', { + return i18n.translate('data.search.aggs.metrics.countLabel', { defaultMessage: 'Count', }); }, diff --git a/src/legacy/ui/public/agg_types/metrics/cumulative_sum.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/cumulative_sum.ts similarity index 88% rename from src/legacy/ui/public/agg_types/metrics/cumulative_sum.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/cumulative_sum.ts index bad2de8cb16dc..a5d02459900bb 100644 --- a/src/legacy/ui/public/agg_types/metrics/cumulative_sum.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/cumulative_sum.ts @@ -23,11 +23,11 @@ import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -const cumulativeSumLabel = i18n.translate('common.ui.aggTypes.metrics.cumulativeSumLabel', { +const cumulativeSumLabel = i18n.translate('data.search.aggs.metrics.cumulativeSumLabel', { defaultMessage: 'cumulative sum', }); -const cumulativeSumTitle = i18n.translate('common.ui.aggTypes.metrics.cumulativeSumTitle', { +const cumulativeSumTitle = i18n.translate('data.search.aggs.metrics.cumulativeSumTitle', { defaultMessage: 'Cumulative Sum', }); diff --git a/src/legacy/ui/public/agg_types/metrics/derivative.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/derivative.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/derivative.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/derivative.ts index 42921621a2933..1169a527b0668 100644 --- a/src/legacy/ui/public/agg_types/metrics/derivative.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/derivative.ts @@ -23,11 +23,11 @@ import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -const derivativeLabel = i18n.translate('common.ui.aggTypes.metrics.derivativeLabel', { +const derivativeLabel = i18n.translate('data.search.aggs.metrics.derivativeLabel', { defaultMessage: 'derivative', }); -const derivativeTitle = i18n.translate('common.ui.aggTypes.metrics.derivativeTitle', { +const derivativeTitle = i18n.translate('data.search.aggs.metrics.derivativeTitle', { defaultMessage: 'Derivative', }); diff --git a/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/geo_bounds.ts similarity index 84% rename from src/legacy/ui/public/agg_types/metrics/geo_bounds.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/geo_bounds.ts index b8ce03cdf11ec..53bc72f9ce1da 100644 --- a/src/legacy/ui/public/agg_types/metrics/geo_bounds.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/geo_bounds.ts @@ -20,13 +20,13 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const geoBoundsTitle = i18n.translate('common.ui.aggTypes.metrics.geoBoundsTitle', { +const geoBoundsTitle = i18n.translate('data.search.aggs.metrics.geoBoundsTitle', { defaultMessage: 'Geo Bounds', }); -const geoBoundsLabel = i18n.translate('common.ui.aggTypes.metrics.geoBoundsLabel', { +const geoBoundsLabel = i18n.translate('data.search.aggs.metrics.geoBoundsLabel', { defaultMessage: 'Geo Bounds', }); diff --git a/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/geo_centroid.ts similarity index 84% rename from src/legacy/ui/public/agg_types/metrics/geo_centroid.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/geo_centroid.ts index 5313e31796a5b..a79b2b34ad1ca 100644 --- a/src/legacy/ui/public/agg_types/metrics/geo_centroid.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/geo_centroid.ts @@ -20,13 +20,13 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const geoCentroidTitle = i18n.translate('common.ui.aggTypes.metrics.geoCentroidTitle', { +const geoCentroidTitle = i18n.translate('data.search.aggs.metrics.geoCentroidTitle', { defaultMessage: 'Geo Centroid', }); -const geoCentroidLabel = i18n.translate('common.ui.aggTypes.metrics.geoCentroidLabel', { +const geoCentroidLabel = i18n.translate('data.search.aggs.metrics.geoCentroidLabel', { defaultMessage: 'Geo Centroid', }); diff --git a/src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/get_response_agg_config_class.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/get_response_agg_config_class.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/make_nested_label.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/make_nested_label.test.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/make_nested_label.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/make_nested_label.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/make_nested_label.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/nested_agg_helpers.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/nested_agg_helpers.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/nested_agg_helpers.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.test.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/ordinal_suffix.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/ordinal_suffix.ts diff --git a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts similarity index 94% rename from src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts index 4d558e50304e6..0d1b2472bb8e2 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_helper.ts @@ -36,7 +36,7 @@ const metricAggFilter = [ '!geo_centroid', ]; -const metricAggTitle = i18n.translate('common.ui.aggTypes.metrics.metricAggTitle', { +const metricAggTitle = i18n.translate('data.search.aggs.metrics.metricAggTitle', { defaultMessage: 'Metric agg', }); @@ -51,7 +51,7 @@ const [metricAggSchema] = new Schemas([ ]).all; const parentPipelineType = i18n.translate( - 'common.ui.aggTypes.metrics.parentPipelineAggregationsSubtypeTitle', + 'data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle', { defaultMessage: 'Parent Pipeline Aggregations', } diff --git a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_writer.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts similarity index 94% rename from src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_writer.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts index 684fe721a754a..bc0359b2a213d 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/parent_pipeline_agg_writer.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/parent_pipeline_agg_writer.ts @@ -17,13 +17,13 @@ * under the License. */ -import { AggConfigs } from '../../agg_configs'; +import { IAggConfigs } from '../../agg_configs'; import { IMetricAggConfig } from '../metric_agg_type'; export const parentPipelineAggWriter = ( agg: IMetricAggConfig, output: Record<string, any>, - aggConfigs?: AggConfigs + aggConfigs?: IAggConfigs ): void => { const customMetric = agg.getParam('customMetric'); const metricAgg = agg.getParam('metricAgg'); diff --git a/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts similarity index 93% rename from src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts index 9dd737bd6708e..3956bda1812ad 100644 --- a/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_helper.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_helper.ts @@ -47,7 +47,7 @@ const [metricAggSchema] = new Schemas([ { group: 'none', name: 'metricAgg', - title: i18n.translate('common.ui.aggTypes.metrics.metricAggTitle', { + title: i18n.translate('data.search.aggs.metrics.metricAggTitle', { defaultMessage: 'Metric agg', }), aggFilter: metricAggFilter, @@ -57,7 +57,7 @@ const [metricAggSchema] = new Schemas([ const [bucketAggSchema] = new Schemas([ { group: 'none', - title: i18n.translate('common.ui.aggTypes.metrics.bucketAggTitle', { + title: i18n.translate('data.search.aggs.metrics.bucketAggTitle', { defaultMessage: 'Bucket agg', }), name: 'bucketAgg', @@ -66,7 +66,7 @@ const [bucketAggSchema] = new Schemas([ ]).all; const siblingPipelineType = i18n.translate( - 'common.ui.aggTypes.metrics.siblingPipelineAggregationsSubtypeTitle', + 'data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle', { defaultMessage: 'Sibling pipeline aggregations', } diff --git a/src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_writer.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/lib/sibling_pipeline_agg_writer.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/lib/sibling_pipeline_agg_writer.ts diff --git a/src/legacy/ui/public/agg_types/metrics/max.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/max.ts similarity index 86% rename from src/legacy/ui/public/agg_types/metrics/max.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/max.ts index 5c43511acee72..d561788936b51 100644 --- a/src/legacy/ui/public/agg_types/metrics/max.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/max.ts @@ -20,9 +20,9 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const maxTitle = i18n.translate('common.ui.aggTypes.metrics.maxTitle', { +const maxTitle = i18n.translate('data.search.aggs.metrics.maxTitle', { defaultMessage: 'Max', }); @@ -30,7 +30,7 @@ export const maxMetricAgg = new MetricAggType({ name: METRIC_TYPES.MAX, title: maxTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.metrics.maxLabel', { + return i18n.translate('data.search.aggs.metrics.maxLabel', { defaultMessage: 'Max {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/median.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts similarity index 95% rename from src/legacy/ui/public/agg_types/metrics/median.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts index 819c24f135cdc..9affb0e3b2814 100644 --- a/src/legacy/ui/public/agg_types/metrics/median.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.test.ts @@ -17,13 +17,13 @@ * under the License. */ -import { AggConfigs } from '../agg_configs'; +import { AggConfigs, IAggConfigs } from '../agg_configs'; import { METRIC_TYPES } from './metric_agg_types'; jest.mock('ui/new_platform'); describe('AggTypeMetricMedianProvider class', () => { - let aggConfigs: AggConfigs; + let aggConfigs: IAggConfigs; beforeEach(() => { const field = { diff --git a/src/legacy/ui/public/agg_types/metrics/median.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts similarity index 88% rename from src/legacy/ui/public/agg_types/metrics/median.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts index 5792d4a7c2ba3..be080aaa5ee6f 100644 --- a/src/legacy/ui/public/agg_types/metrics/median.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/median.ts @@ -22,9 +22,9 @@ import { METRIC_TYPES } from './metric_agg_types'; // @ts-ignore import { percentilesMetricAgg } from './percentiles'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const medianTitle = i18n.translate('common.ui.aggTypes.metrics.medianTitle', { +const medianTitle = i18n.translate('data.search.aggs.metrics.medianTitle', { defaultMessage: 'Median', }); @@ -33,7 +33,7 @@ export const medianMetricAgg = new MetricAggType({ dslName: 'percentiles', title: medianTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.metrics.medianLabel', { + return i18n.translate('data.search.aggs.metrics.medianLabel', { defaultMessage: 'Median {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts similarity index 92% rename from src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts index 5cd3dffb10b9d..e7d286c187ef8 100644 --- a/src/legacy/ui/public/agg_types/metrics/metric_agg_type.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_type.ts @@ -23,7 +23,7 @@ import { AggType, AggTypeConfig } from '../agg_type'; import { AggParamType } from '../param_types/agg'; import { AggConfig } from '../agg_config'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; export interface IMetricAggConfig extends AggConfig { type: InstanceType<typeof MetricAggType>; @@ -43,6 +43,9 @@ interface MetricAggTypeConfig<TMetricAggConfig extends AggConfig> subtype?: string; } +// TODO need to make a more explicit interface for this +export type IMetricAggType = MetricAggType; + export class MetricAggType<TMetricAggConfig extends AggConfig = IMetricAggConfig> extends AggType< TMetricAggConfig, MetricAggParam<TMetricAggConfig> @@ -83,7 +86,7 @@ export class MetricAggType<TMetricAggConfig extends AggConfig = IMetricAggConfig this.subtype = config.subtype || - i18n.translate('common.ui.aggTypes.metrics.metricAggregationsSubtypeTitle', { + i18n.translate('data.search.aggs.metrics.metricAggregationsSubtypeTitle', { defaultMessage: 'Metric Aggregations', }); diff --git a/src/legacy/ui/public/agg_types/metrics/metric_agg_types.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_types.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/metric_agg_types.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/metric_agg_types.ts diff --git a/src/legacy/ui/public/agg_types/metrics/min.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts similarity index 86% rename from src/legacy/ui/public/agg_types/metrics/min.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts index 5f8ca72954cc2..4885105163435 100644 --- a/src/legacy/ui/public/agg_types/metrics/min.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/min.ts @@ -19,9 +19,9 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const minTitle = i18n.translate('common.ui.aggTypes.metrics.minTitle', { +const minTitle = i18n.translate('data.search.aggs.metrics.minTitle', { defaultMessage: 'Min', }); @@ -29,7 +29,7 @@ export const minMetricAgg = new MetricAggType({ name: METRIC_TYPES.MIN, title: minTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.metrics.minLabel', { + return i18n.translate('data.search.aggs.metrics.minLabel', { defaultMessage: 'Min {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/moving_avg.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/moving_avg.ts similarity index 92% rename from src/legacy/ui/public/agg_types/metrics/moving_avg.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/moving_avg.ts index b6a8e7a5d6a99..cb733507858bc 100644 --- a/src/legacy/ui/public/agg_types/metrics/moving_avg.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/moving_avg.ts @@ -23,11 +23,11 @@ import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -const movingAvgTitle = i18n.translate('common.ui.aggTypes.metrics.movingAvgTitle', { +const movingAvgTitle = i18n.translate('data.search.aggs.metrics.movingAvgTitle', { defaultMessage: 'Moving Avg', }); -const movingAvgLabel = i18n.translate('common.ui.aggTypes.metrics.movingAvgLabel', { +const movingAvgLabel = i18n.translate('data.search.aggs.metrics.movingAvgLabel', { defaultMessage: 'moving avg', }); diff --git a/src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/parent_pipeline.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/parent_pipeline.test.ts diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts similarity index 96% rename from src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts index 7461b5cf07ee7..655e918ce07de 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.test.ts @@ -18,13 +18,13 @@ */ import { IPercentileRanksAggConfig, percentileRanksMetricAgg } from './percentile_ranks'; -import { AggConfigs } from '../agg_configs'; +import { AggConfigs, IAggConfigs } from '../agg_configs'; import { METRIC_TYPES } from './metric_agg_types'; jest.mock('ui/new_platform'); describe('AggTypesMetricsPercentileRanksProvider class', function() { - let aggConfigs: AggConfigs; + let aggConfigs: IAggConfigs; beforeEach(() => { const field = { diff --git a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts index cbd46e3f5b28d..38b47a7e97d2f 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentile_ranks.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentile_ranks.ts @@ -24,7 +24,7 @@ import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_respons import { getPercentileValue } from './percentiles_get_value'; import { METRIC_TYPES } from './metric_agg_types'; -import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { fieldFormats, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; // required by the values editor @@ -41,7 +41,7 @@ const valueProps = { const customLabel = this.getParam('customLabel'); const label = customLabel || this.getFieldDisplayName(); - return i18n.translate('common.ui.aggTypes.metrics.percentileRanks.valuePropsLabel', { + return i18n.translate('data.search.aggs.metrics.percentileRanks.valuePropsLabel', { defaultMessage: 'Percentile rank {format} of "{label}"', values: { format: format.convert(this.key, 'text'), label }, }); @@ -50,11 +50,11 @@ const valueProps = { export const percentileRanksMetricAgg = new MetricAggType<IPercentileRanksAggConfig>({ name: METRIC_TYPES.PERCENTILE_RANKS, - title: i18n.translate('common.ui.aggTypes.metrics.percentileRanksTitle', { + title: i18n.translate('data.search.aggs.metrics.percentileRanksTitle', { defaultMessage: 'Percentile Ranks', }), makeLabel(agg) { - return i18n.translate('common.ui.aggTypes.metrics.percentileRanksLabel', { + return i18n.translate('data.search.aggs.metrics.percentileRanksLabel', { defaultMessage: 'Percentile ranks of {field}', values: { field: agg.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts similarity index 95% rename from src/legacy/ui/public/agg_types/metrics/percentiles.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts index c9f4bcc3862a0..dd1aaca973e47 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.test.ts @@ -18,13 +18,13 @@ */ import { IPercentileAggConfig, percentilesMetricAgg } from './percentiles'; -import { AggConfigs } from '../agg_configs'; +import { AggConfigs, IAggConfigs } from '../agg_configs'; import { METRIC_TYPES } from './metric_agg_types'; jest.mock('ui/new_platform'); describe('AggTypesMetricsPercentilesProvider class', () => { - let aggConfigs: AggConfigs; + let aggConfigs: IAggConfigs; beforeEach(() => { const field = { diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts similarity index 88% rename from src/legacy/ui/public/agg_types/metrics/percentiles.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts index 040324d8da5df..39dc0d0f181e9 100644 --- a/src/legacy/ui/public/agg_types/metrics/percentiles.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; import { getPercentileValue } from './percentiles_get_value'; @@ -36,7 +36,7 @@ const valueProps = { const customLabel = this.getParam('customLabel'); const label = customLabel || this.getFieldDisplayName(); - return i18n.translate('common.ui.aggTypes.metrics.percentiles.valuePropsLabel', { + return i18n.translate('data.search.aggs.metrics.percentiles.valuePropsLabel', { defaultMessage: '{percentile} percentile of {label}', values: { percentile: ordinalSuffix(this.key), label }, }); @@ -45,11 +45,11 @@ const valueProps = { export const percentilesMetricAgg = new MetricAggType<IPercentileAggConfig>({ name: METRIC_TYPES.PERCENTILES, - title: i18n.translate('common.ui.aggTypes.metrics.percentilesTitle', { + title: i18n.translate('data.search.aggs.metrics.percentilesTitle', { defaultMessage: 'Percentiles', }), makeLabel(agg) { - return i18n.translate('common.ui.aggTypes.metrics.percentilesLabel', { + return i18n.translate('data.search.aggs.metrics.percentilesLabel', { defaultMessage: 'Percentiles of {field}', values: { field: agg.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/percentiles_get_value.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/percentiles_get_value.ts diff --git a/src/legacy/ui/public/agg_types/metrics/serial_diff.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/serial_diff.ts similarity index 89% rename from src/legacy/ui/public/agg_types/metrics/serial_diff.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/serial_diff.ts index bb5431fbbefd9..5af6e1952d135 100644 --- a/src/legacy/ui/public/agg_types/metrics/serial_diff.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/serial_diff.ts @@ -23,11 +23,11 @@ import { parentPipelineAggHelper } from './lib/parent_pipeline_agg_helper'; import { makeNestedLabel } from './lib/make_nested_label'; import { METRIC_TYPES } from './metric_agg_types'; -const serialDiffTitle = i18n.translate('common.ui.aggTypes.metrics.serialDiffTitle', { +const serialDiffTitle = i18n.translate('data.search.aggs.metrics.serialDiffTitle', { defaultMessage: 'Serial Diff', }); -const serialDiffLabel = i18n.translate('common.ui.aggTypes.metrics.serialDiffLabel', { +const serialDiffLabel = i18n.translate('data.search.aggs.metrics.serialDiffLabel', { defaultMessage: 'serial diff', }); diff --git a/src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/sibling_pipeline.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/sibling_pipeline.test.ts diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/metrics/std_deviation.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.test.ts diff --git a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.ts similarity index 85% rename from src/legacy/ui/public/agg_types/metrics/std_deviation.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.ts index b2e6d3b3ca4d0..caf3bb71dd89a 100644 --- a/src/legacy/ui/public/agg_types/metrics/std_deviation.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/std_deviation.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; import { getResponseAggConfigClass, IResponseAggConfig } from './lib/get_response_agg_config_class'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; interface ValProp { valProp: string[]; @@ -51,7 +51,7 @@ const responseAggConfigProps = { keyedDetails(this: IStdDevAggConfig, customLabel: string, fieldDisplayName: string) { const label = customLabel || - i18n.translate('common.ui.aggTypes.metrics.standardDeviation.keyDetailsLabel', { + i18n.translate('data.search.aggs.metrics.standardDeviation.keyDetailsLabel', { defaultMessage: 'Standard Deviation of {fieldDisplayName}', values: { fieldDisplayName }, }); @@ -59,14 +59,14 @@ const responseAggConfigProps = { return { std_lower: { valProp: ['std_deviation_bounds', 'lower'], - title: i18n.translate('common.ui.aggTypes.metrics.standardDeviation.lowerKeyDetailsTitle', { + title: i18n.translate('data.search.aggs.metrics.standardDeviation.lowerKeyDetailsTitle', { defaultMessage: 'Lower {label}', values: { label }, }), }, std_upper: { valProp: ['std_deviation_bounds', 'upper'], - title: i18n.translate('common.ui.aggTypes.metrics.standardDeviation.upperKeyDetailsTitle', { + title: i18n.translate('data.search.aggs.metrics.standardDeviation.upperKeyDetailsTitle', { defaultMessage: 'Upper {label}', values: { label }, }), @@ -78,11 +78,11 @@ const responseAggConfigProps = { export const stdDeviationMetricAgg = new MetricAggType<IStdDevAggConfig>({ name: METRIC_TYPES.STD_DEV, dslName: 'extended_stats', - title: i18n.translate('common.ui.aggTypes.metrics.standardDeviationTitle', { + title: i18n.translate('data.search.aggs.metrics.standardDeviationTitle', { defaultMessage: 'Standard Deviation', }), makeLabel(agg) { - return i18n.translate('common.ui.aggTypes.metrics.standardDeviationLabel', { + return i18n.translate('data.search.aggs.metrics.standardDeviationLabel', { defaultMessage: 'Standard Deviation of {field}', values: { field: agg.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/sum.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/sum.ts similarity index 86% rename from src/legacy/ui/public/agg_types/metrics/sum.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/sum.ts index ce79c761ce799..f3450ba1700c8 100644 --- a/src/legacy/ui/public/agg_types/metrics/sum.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/sum.ts @@ -20,9 +20,9 @@ import { i18n } from '@kbn/i18n'; import { MetricAggType } from './metric_agg_type'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; -const sumTitle = i18n.translate('common.ui.aggTypes.metrics.sumTitle', { +const sumTitle = i18n.translate('data.search.aggs.metrics.sumTitle', { defaultMessage: 'Sum', }); @@ -30,7 +30,7 @@ export const sumMetricAgg = new MetricAggType({ name: METRIC_TYPES.SUM, title: sumTitle, makeLabel(aggConfig) { - return i18n.translate('common.ui.aggTypes.metrics.sumLabel', { + return i18n.translate('data.search.aggs.metrics.sumLabel', { defaultMessage: 'Sum of {field}', values: { field: aggConfig.getFieldDisplayName() }, }); diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts similarity index 99% rename from src/legacy/ui/public/agg_types/metrics/top_hit.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts index 3e861c052d367..a973de4fe8659 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.test.ts @@ -21,7 +21,7 @@ import { dropRight, last } from 'lodash'; import { topHitMetricAgg } from './top_hit'; import { AggConfigs } from '../agg_configs'; import { IMetricAggConfig } from './metric_agg_type'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/metrics/top_hit.ts b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.ts similarity index 87% rename from src/legacy/ui/public/agg_types/metrics/top_hit.ts rename to src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.ts index 43fe33bdebeb9..81bd14ded75b0 100644 --- a/src/legacy/ui/public/agg_types/metrics/top_hit.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/metrics/top_hit.ts @@ -22,7 +22,7 @@ import { i18n } from '@kbn/i18n'; import { IMetricAggConfig, MetricAggType } from './metric_agg_type'; import { aggTypeFieldFilters } from '../param_types/filter'; import { METRIC_TYPES } from './metric_agg_types'; -import { KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; // @ts-ignore import { wrapWithInlineComp } from '../buckets/inline_comp_wrapper'; @@ -46,14 +46,14 @@ aggTypeFieldFilters.addFilter((field, aggConfig) => { export const topHitMetricAgg = new MetricAggType({ name: METRIC_TYPES.TOP_HITS, - title: i18n.translate('common.ui.aggTypes.metrics.topHitTitle', { + title: i18n.translate('data.search.aggs.metrics.topHitTitle', { defaultMessage: 'Top Hit', }), makeLabel(aggConfig) { - const lastPrefixLabel = i18n.translate('common.ui.aggTypes.metrics.topHit.lastPrefixLabel', { + const lastPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.lastPrefixLabel', { defaultMessage: 'Last', }); - const firstPrefixLabel = i18n.translate('common.ui.aggTypes.metrics.topHit.firstPrefixLabel', { + const firstPrefixLabel = i18n.translate('data.search.aggs.metrics.topHit.firstPrefixLabel', { defaultMessage: 'First', }); @@ -106,7 +106,7 @@ export const topHitMetricAgg = new MetricAggType({ type: 'optioned', options: [ { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.minLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.minLabel', { defaultMessage: 'Min', }), isCompatible: isNumericFieldSelected, @@ -114,7 +114,7 @@ export const topHitMetricAgg = new MetricAggType({ value: 'min', }, { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.maxLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.maxLabel', { defaultMessage: 'Max', }), isCompatible: isNumericFieldSelected, @@ -122,7 +122,7 @@ export const topHitMetricAgg = new MetricAggType({ value: 'max', }, { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.sumLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.sumLabel', { defaultMessage: 'Sum', }), isCompatible: isNumericFieldSelected, @@ -130,7 +130,7 @@ export const topHitMetricAgg = new MetricAggType({ value: 'sum', }, { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.averageLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.averageLabel', { defaultMessage: 'Average', }), isCompatible: isNumericFieldSelected, @@ -138,7 +138,7 @@ export const topHitMetricAgg = new MetricAggType({ value: 'average', }, { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.concatenateLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.concatenateLabel', { defaultMessage: 'Concatenate', }), isCompatible(aggConfig: IMetricAggConfig) { @@ -174,13 +174,13 @@ export const topHitMetricAgg = new MetricAggType({ default: 'desc', options: [ { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.descendingLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.descendingLabel', { defaultMessage: 'Descending', }), value: 'desc', }, { - text: i18n.translate('common.ui.aggTypes.metrics.topHit.ascendingLabel', { + text: i18n.translate('data.search.aggs.metrics.topHit.ascendingLabel', { defaultMessage: 'Ascending', }), value: 'asc', diff --git a/src/legacy/ui/public/agg_types/param_types/agg.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/agg.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/agg.ts diff --git a/src/legacy/ui/public/agg_types/param_types/base.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts similarity index 94% rename from src/legacy/ui/public/agg_types/param_types/base.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts index 15ec44e2ca5ae..1523cb03eb966 100644 --- a/src/legacy/ui/public/agg_types/param_types/base.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/base.ts @@ -17,9 +17,9 @@ * under the License. */ -import { AggConfigs } from '../agg_configs'; +import { IAggConfigs } from '../agg_configs'; import { AggConfig } from '../agg_config'; -import { FetchOptions, ISearchSource } from '../../../../../plugins/data/public'; +import { FetchOptions, ISearchSource } from '../../../../../../../plugins/data/public'; export class BaseParamType<TAggConfig extends AggConfig = AggConfig> { name: string; @@ -31,7 +31,7 @@ export class BaseParamType<TAggConfig extends AggConfig = AggConfig> { write: ( aggConfig: TAggConfig, output: Record<string, any>, - aggConfigs?: AggConfigs, + aggConfigs?: IAggConfigs, locals?: Record<string, any> ) => void; serialize: (value: any, aggConfig?: TAggConfig) => any; diff --git a/src/legacy/ui/public/agg_types/param_types/field.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts similarity index 98% rename from src/legacy/ui/public/agg_types/param_types/field.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts index 9cea2934d7459..d0fa711d89c70 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.test.ts @@ -19,7 +19,7 @@ import { BaseParamType } from './base'; import { FieldParamType } from './field'; -import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../plugins/data/public'; +import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../../../../../../../plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts similarity index 83% rename from src/legacy/ui/public/agg_types/param_types/field.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts index d01e059c6c616..c41c159ad0f78 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/field.ts @@ -19,16 +19,18 @@ // @ts-ignore import { i18n } from '@kbn/i18n'; +import { npStart } from 'ui/new_platform'; import { AggConfig } from '../agg_config'; -import { SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; +import { SavedObjectNotFound } from '../../../../../../../plugins/kibana_utils/public'; import { BaseParamType } from './base'; -import { npStart } from '../../new_platform'; import { propFilter } from '../filter'; -import { Field, IFieldList } from '../../../../../plugins/data/public'; -import { isNestedField } from '../../../../../plugins/data/public'; +import { Field, IFieldList, isNestedField } from '../../../../../../../plugins/data/public'; const filterByType = propFilter('type'); +// TODO need to make a more explicit interface for this +export type IFieldParamType = FieldParamType; + export class FieldParamType extends BaseParamType { required = true; scriptable = true; @@ -47,15 +49,12 @@ export class FieldParamType extends BaseParamType { if (!field) { throw new TypeError( - i18n.translate( - 'common.ui.aggTypes.paramTypes.field.requiredFieldParameterErrorMessage', - { - defaultMessage: '{fieldParameter} is a required parameter', - values: { - fieldParameter: '"field"', - }, - } - ) + i18n.translate('data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage', { + defaultMessage: '{fieldParameter} is a required parameter', + values: { + fieldParameter: '"field"', + }, + }) ); } @@ -91,7 +90,7 @@ export class FieldParamType extends BaseParamType { if (!validField) { npStart.core.notifications.toasts.addDanger( i18n.translate( - 'common.ui.aggTypes.paramTypes.field.invalidSavedFieldParameterErrorMessage', + 'data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage', { defaultMessage: 'Saved {fieldParameter} parameter is now invalid. Please select a new field.', diff --git a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts similarity index 97% rename from src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts index 384c142408012..fb53e72b85c60 100644 --- a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.test.ts @@ -20,7 +20,7 @@ import { IndexedArray } from 'ui/indexed_array'; import { AggTypeFieldFilters } from './field_filters'; import { AggConfig } from '../../agg_config'; -import { Field } from '../../../../../../plugins/data/public'; +import { Field } from '../../../../../../../../plugins/data/public'; describe('AggTypeFieldFilters', () => { let registry: AggTypeFieldFilters; diff --git a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/filter/field_filters.ts diff --git a/src/legacy/ui/public/agg_types/param_types/filter/index.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/filter/index.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/filter/index.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/filter/index.ts diff --git a/src/legacy/ui/public/agg_types/param_types/index.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/index.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/index.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/index.ts diff --git a/src/legacy/ui/public/agg_types/param_types/json.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/json.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/json.test.ts diff --git a/src/legacy/ui/public/agg_types/param_types/json.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/json.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/json.ts diff --git a/src/legacy/ui/public/agg_types/param_types/optioned.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/optioned.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.test.ts diff --git a/src/legacy/ui/public/agg_types/param_types/optioned.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/optioned.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/optioned.ts diff --git a/src/legacy/ui/public/agg_types/param_types/string.test.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/string.test.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/string.test.ts diff --git a/src/legacy/ui/public/agg_types/param_types/string.ts b/src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts similarity index 100% rename from src/legacy/ui/public/agg_types/param_types/string.ts rename to src/legacy/core_plugins/data/public/search/aggs/param_types/string.ts diff --git a/src/legacy/ui/public/agg_types/schemas.ts b/src/legacy/core_plugins/data/public/search/aggs/schemas.ts similarity index 98% rename from src/legacy/ui/public/agg_types/schemas.ts rename to src/legacy/core_plugins/data/public/search/aggs/schemas.ts index 05723cac1869d..1aa5ebe08656b 100644 --- a/src/legacy/ui/public/agg_types/schemas.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/schemas.ts @@ -45,7 +45,7 @@ export interface Schema { aggSettings?: any; } -class Schemas { +export class Schemas { // @ts-ignore all: IndexedArray<Schema>; @@ -103,5 +103,3 @@ class Schemas { .commit(); } } - -export { Schemas }; diff --git a/src/legacy/core_plugins/data/public/search/aggs/types.ts b/src/legacy/core_plugins/data/public/search/aggs/types.ts new file mode 100644 index 0000000000000..2c918abf99fca --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/aggs/types.ts @@ -0,0 +1,29 @@ +/* + * 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. + */ + +export { IAggConfig } from './agg_config'; +export { IAggConfigs } from './agg_configs'; +export { IAggType } from './agg_type'; +export { AggParam, AggParamOption } from './agg_params'; +export { IFieldParamType } from './param_types'; +export { IMetricAggType } from './metrics/metric_agg_type'; +export { DateRangeKey } from './buckets/date_range'; +export { IpRangeKey } from './buckets/ip_range'; +export { OptionedValueProp, OptionedParamEditorProps } from './param_types/optioned'; +export { ISchemas } from './schemas'; diff --git a/src/legacy/ui/public/agg_types/utils.test.tsx b/src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx similarity index 100% rename from src/legacy/ui/public/agg_types/utils.test.tsx rename to src/legacy/core_plugins/data/public/search/aggs/utils.test.tsx diff --git a/src/legacy/ui/public/agg_types/utils.ts b/src/legacy/core_plugins/data/public/search/aggs/utils.ts similarity index 92% rename from src/legacy/ui/public/agg_types/utils.ts rename to src/legacy/core_plugins/data/public/search/aggs/utils.ts index e382f821b31a9..62f07ce44ab46 100644 --- a/src/legacy/ui/public/agg_types/utils.ts +++ b/src/legacy/core_plugins/data/public/search/aggs/utils.ts @@ -17,8 +17,8 @@ * under the License. */ -import { isValidEsInterval } from '../../../core_plugins/data/common/parse_es_interval/is_valid_es_interval'; -import { leastCommonInterval } from '../vis/lib/least_common_interval'; +import { leastCommonInterval } from 'ui/vis/lib/least_common_interval'; +import { isValidEsInterval } from '../../../common'; /** * Check a string if it's a valid JSON. diff --git a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts index b4ea2cd378d61..43927337ce574 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/esaggs.ts @@ -19,7 +19,7 @@ import { get, has } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { AggConfigs } from 'ui/agg_types/agg_configs'; +import { AggConfigs, IAggConfigs } from 'ui/agg_types'; import { createFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; import { KibanaContext, @@ -50,7 +50,7 @@ import { serializeAggConfig } from './utils'; export interface RequestHandlerParams { searchSource: ISearchSource; - aggs: AggConfigs; + aggs: IAggConfigs; timeRange?: TimeRange; query?: Query; filters?: esFilters.Filter[]; diff --git a/src/legacy/core_plugins/data/public/search/expressions/utils.ts b/src/legacy/core_plugins/data/public/search/expressions/utils.ts index 4f104f2569a98..f0958ba20820f 100644 --- a/src/legacy/core_plugins/data/public/search/expressions/utils.ts +++ b/src/legacy/core_plugins/data/public/search/expressions/utils.ts @@ -17,12 +17,12 @@ * under the License. */ -import { AggConfig } from 'ui/agg_types/agg_config'; -import { AggConfigs } from '../../../../../ui/public/agg_types/agg_configs'; +import { AggConfigs } from '../aggs'; +import { IAggConfig } from '../aggs/types'; import { KibanaDatatableColumnMeta } from '../../../../../../plugins/expressions/common/expression_types'; import { IndexPattern } from '../../../../../../plugins/data/public'; -export const serializeAggConfig = (aggConfig: AggConfig): KibanaDatatableColumnMeta => { +export const serializeAggConfig = (aggConfig: IAggConfig): KibanaDatatableColumnMeta => { return { type: aggConfig.type.name, indexPatternId: aggConfig.getIndexPattern().id, diff --git a/src/legacy/core_plugins/data/public/search/index.ts b/src/legacy/core_plugins/data/public/search/index.ts index c975d5772e0a8..90e191b769a8d 100644 --- a/src/legacy/core_plugins/data/public/search/index.ts +++ b/src/legacy/core_plugins/data/public/search/index.ts @@ -17,5 +17,6 @@ * under the License. */ +export * from './aggs'; export { getRequestInspectorStats, getResponseInspectorStats } from './utils'; export { serializeAggConfig } from './expressions/utils'; diff --git a/src/legacy/core_plugins/data/public/search/search_service.ts b/src/legacy/core_plugins/data/public/search/search_service.ts new file mode 100644 index 0000000000000..45f9ff17328ad --- /dev/null +++ b/src/legacy/core_plugins/data/public/search/search_service.ts @@ -0,0 +1,93 @@ +/* + * 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 { CoreSetup, CoreStart } from '../../../../../core/public'; +import { + aggTypes, + AggType, + AggConfig, + AggConfigs, + FieldParamType, + MetricAggType, + aggTypeFieldFilters, + setBounds, + parentPipelineAggHelper, + siblingPipelineAggHelper, +} from './aggs'; + +interface AggsSetup { + types: typeof aggTypes; +} + +interface AggsStart { + types: typeof aggTypes; + AggConfig: typeof AggConfig; + AggConfigs: typeof AggConfigs; + AggType: typeof AggType; + aggTypeFieldFilters: typeof aggTypeFieldFilters; + FieldParamType: typeof FieldParamType; + MetricAggType: typeof MetricAggType; + parentPipelineAggHelper: typeof parentPipelineAggHelper; + siblingPipelineAggHelper: typeof siblingPipelineAggHelper; + setBounds: typeof setBounds; +} + +export interface SearchSetup { + aggs: AggsSetup; +} + +export interface SearchStart { + aggs: AggsStart; +} + +/** + * The contract provided here is a new platform shim for ui/agg_types. + * + * Once it has been refactored to work with new platform services, + * it will move into the existing search service in src/plugins/data/public/search + */ +export class SearchService { + public setup(core: CoreSetup): SearchSetup { + return { + aggs: { + types: aggTypes, // TODO convert to registry + // TODO add other items as needed + }, + }; + } + + public start(core: CoreStart): SearchStart { + return { + aggs: { + types: aggTypes, // TODO convert to registry + AggConfig, // TODO make static + AggConfigs, + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, // TODO make static + siblingPipelineAggHelper, // TODO make static + setBounds, // TODO make static + }, + }; + } + + public stop() {} +} diff --git a/src/legacy/core_plugins/data/public/search/types.ts b/src/legacy/core_plugins/data/public/search/types.ts index 140ceea487099..47ea1d168f379 100644 --- a/src/legacy/core_plugins/data/public/search/types.ts +++ b/src/legacy/core_plugins/data/public/search/types.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from './aggs/types'; export * from './utils/types'; diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index d1e1dafe7c878..f698a2ee361e0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -55,7 +55,7 @@ export { buildVislibDimensions } from '../../../visualizations/public'; export { callAfterBindingsWorkaround } from 'ui/compat'; export { getRequestInspectorStats, getResponseInspectorStats } from '../../../data/public'; // @ts-ignore -export { intervalOptions } from 'ui/agg_types/buckets/_interval_options'; +export { intervalOptions } from 'ui/agg_types'; // @ts-ignore export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx index 81c866923232e..f5ce55e82967d 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg.test.tsx @@ -24,7 +24,7 @@ import { act } from 'react-dom/test-utils'; import { IndexPattern } from 'src/plugins/data/public'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggType, AggGroupNames } from '../legacy_imports'; +import { IAggType, AggGroupNames } from '../legacy_imports'; import { DefaultEditorAgg, DefaultEditorAggProps } from './agg'; import { DefaultEditorAggParams } from './agg_params'; import { AGGS_ACTION_KEYS } from './agg_group_state'; @@ -117,7 +117,7 @@ describe('DefaultEditorAgg component', () => { (defaultProps.agg as any).brandNew = false; defaultProps.agg.type = { makeLabel: () => 'Agg description', - } as AggType; + } as IAggType; const comp = mount(<DefaultEditorAgg {...defaultProps} />); act(() => { @@ -258,11 +258,11 @@ describe('DefaultEditorAgg component', () => { it('should disable min_doc_count when agg is histogram or date_histogram', () => { defaultProps.agg.type = { name: 'histogram', - } as AggType; + } as IAggType; const compHistogram = shallow(<DefaultEditorAgg {...defaultProps} />); defaultProps.agg.type = { name: 'date_histogram', - } as AggType; + } as IAggType; const compDateHistogram = shallow(<DefaultEditorAgg {...defaultProps} />); expect(compHistogram.find(DefaultEditorAggParams).props()).toHaveProperty('disabledParams', [ @@ -276,7 +276,7 @@ describe('DefaultEditorAgg component', () => { it('should set error when agg is not histogram or date_histogram', () => { defaultProps.agg.type = { name: 'aggType', - } as AggType; + } as IAggType; const comp = shallow(<DefaultEditorAgg {...defaultProps} />); expect(comp.find(DefaultEditorAggParams).prop('aggError')).toBeDefined(); @@ -285,7 +285,7 @@ describe('DefaultEditorAgg component', () => { it('should set min_doc_count to true when agg type was changed to histogram', () => { defaultProps.agg.type = { name: 'aggType', - } as AggType; + } as IAggType; const comp = mount(<DefaultEditorAgg {...defaultProps} />); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'histogram' } } }); @@ -299,7 +299,7 @@ describe('DefaultEditorAgg component', () => { it('should set min_doc_count to 0 when agg type was changed to date_histogram', () => { defaultProps.agg.type = { name: 'aggType', - } as AggType; + } as IAggType; const comp = mount(<DefaultEditorAgg {...defaultProps} />); comp.setProps({ agg: { ...defaultProps.agg, type: { name: 'date_histogram' } } }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx index 871bd0cdf6811..5450c29450bac 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg.tsx @@ -28,7 +28,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggConfig } from '../legacy_imports'; +import { IAggConfig } from '../legacy_imports'; import { DefaultEditorAggParams } from './agg_params'; import { DefaultEditorAggCommonProps } from './agg_common_props'; import { AGGS_ACTION_KEYS, AggsAction } from './agg_group_state'; @@ -36,7 +36,7 @@ import { RowsOrColumnsControl } from './controls/rows_or_columns'; import { RadiusRatioOptionControl } from './controls/radius_ratio_option'; export interface DefaultEditorAggProps extends DefaultEditorAggCommonProps { - agg: AggConfig; + agg: IAggConfig; aggIndex: number; aggIsTooLow: boolean; dragHandleProps: {} | null; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx index f5175126c31c1..d8df5b315fca0 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_add.tsx @@ -29,10 +29,10 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { AggConfig, AggGroupNames, Schema } from '../legacy_imports'; +import { IAggConfig, AggGroupNames, Schema } from '../legacy_imports'; interface DefaultEditorAggAddProps { - group?: AggConfig[]; + group?: IAggConfig[]; groupName: string; schemas: Schema[]; stats: { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts index 8d803810b647a..17d2c18d2532c 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_common_props.ts @@ -18,25 +18,25 @@ */ import { VisState, VisParams } from 'src/legacy/core_plugins/visualizations/public'; -import { AggType, AggConfig, AggGroupNames, Schema } from '../legacy_imports'; +import { IAggType, IAggConfig, AggGroupNames, Schema } from '../legacy_imports'; -type AggId = AggConfig['id']; -type AggParams = AggConfig['params']; +type AggId = IAggConfig['id']; +type AggParams = IAggConfig['params']; export type AddSchema = (schemas: Schema) => void; -export type ReorderAggs = (sourceAgg: AggConfig, destinationAgg: AggConfig) => void; +export type ReorderAggs = (sourceAgg: IAggConfig, destinationAgg: IAggConfig) => void; export interface DefaultEditorCommonProps { formIsTouched: boolean; groupName: AggGroupNames; - metricAggs: AggConfig[]; + metricAggs: IAggConfig[]; state: VisState; setAggParamValue: <T extends keyof AggParams>( aggId: AggId, paramName: T, value: AggParams[T] ) => void; - onAggTypeChange: (aggId: AggId, aggType: AggType) => void; + onAggTypeChange: (aggId: AggId, aggType: IAggType) => void; } export interface DefaultEditorAggCommonProps extends DefaultEditorCommonProps { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx index 9cbcc31bdc60e..c36c0176439f9 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfigs, AggConfig, Schema } from '../legacy_imports'; +import { IAggConfigs, IAggConfig, Schema } from '../legacy_imports'; import { DefaultEditorAggGroup, DefaultEditorAggGroupProps } from './agg_group'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; @@ -56,7 +56,7 @@ jest.mock('./agg_add', () => ({ describe('DefaultEditorAgg component', () => { let defaultProps: DefaultEditorAggGroupProps; - let aggs: AggConfigs; + let aggs: IAggConfigs; let setTouched: jest.Mock; let setValidity: jest.Mock; let reorderAggs: jest.Mock; @@ -76,7 +76,7 @@ describe('DefaultEditorAgg component', () => { }, }, schema: { group: 'metrics' }, - } as AggConfig, + } as IAggConfig, { id: '3', params: { @@ -85,7 +85,7 @@ describe('DefaultEditorAgg component', () => { }, }, schema: { group: 'metrics' }, - } as AggConfig, + } as IAggConfig, { id: '2', params: { @@ -94,9 +94,9 @@ describe('DefaultEditorAgg component', () => { }, }, schema: { group: 'buckets' }, - } as AggConfig, + } as IAggConfig, ], - } as AggConfigs; + } as IAggConfigs; defaultProps = { formIsTouched: false, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx index 3491414bec809..768a9669025e4 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group.tsx @@ -30,7 +30,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggConfig, aggGroupNamesMap, AggGroupNames, Schema } from '../legacy_imports'; +import { IAggConfig, aggGroupNamesMap, AggGroupNames, Schema } from '../legacy_imports'; import { DefaultEditorAgg } from './agg'; import { DefaultEditorAggAdd } from './agg_add'; import { AddSchema, ReorderAggs, DefaultEditorAggCommonProps } from './agg_common_props'; @@ -69,8 +69,8 @@ function DefaultEditorAggGroup({ }: DefaultEditorAggGroupProps) { const groupNameLabel = (aggGroupNamesMap() as any)[groupName]; // e.g. buckets can have no aggs - const group: AggConfig[] = useMemo( - () => state.aggs.aggs.filter((agg: AggConfig) => agg.schema.group === groupName) || [], + const group: IAggConfig[] = useMemo( + () => state.aggs.aggs.filter((agg: IAggConfig) => agg.schema.group === groupName) || [], [groupName, state.aggs.aggs] ); @@ -151,7 +151,7 @@ function DefaultEditorAggGroup({ )} <EuiDroppable droppableId={`agg_group_dnd_${groupName}`}> <> - {group.map((agg: AggConfig, index: number) => ( + {group.map((agg: IAggConfig, index: number) => ( <EuiDraggable key={agg.id} index={index} diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts index dc007a294e0e1..b18e5af27f8b4 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AggConfig } from '../legacy_imports'; +import { IAggConfig } from '../legacy_imports'; import { isAggRemovable, calcAggIsTooLow, @@ -27,7 +27,7 @@ import { import { AggsState } from './agg_group_state'; describe('DefaultEditorGroup helpers', () => { - let group: AggConfig[]; + let group: IAggConfig[]; beforeEach(() => { group = [ @@ -39,7 +39,7 @@ describe('DefaultEditorGroup helpers', () => { }, }, schema: { name: 'metric', min: 1, mustBeFirst: true }, - } as AggConfig, + } as IAggConfig, { id: '2', params: { @@ -48,7 +48,7 @@ describe('DefaultEditorGroup helpers', () => { }, }, schema: { name: 'metric', min: 2 }, - } as AggConfig, + } as IAggConfig, ]; }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx index 87f0d00d50a1d..d2e8e5401c0f7 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_helper.tsx @@ -18,12 +18,12 @@ */ import { findIndex, isEmpty } from 'lodash'; -import { AggConfig } from '../legacy_imports'; +import { IAggConfig } from '../legacy_imports'; import { AggsState } from './agg_group_state'; -const isAggRemovable = (agg: AggConfig, group: AggConfig[]) => { +const isAggRemovable = (agg: IAggConfig, group: IAggConfig[]) => { const metricCount = group.reduce( - (count, aggregation: AggConfig) => + (count, aggregation: IAggConfig) => aggregation.schema.name === agg.schema.name ? ++count : count, 0 ); @@ -31,20 +31,20 @@ const isAggRemovable = (agg: AggConfig, group: AggConfig[]) => { return metricCount > agg.schema.min; }; -const getEnabledMetricAggsCount = (group: AggConfig[]) => { +const getEnabledMetricAggsCount = (group: IAggConfig[]) => { return group.reduce( - (count, aggregation: AggConfig) => + (count, aggregation: IAggConfig) => aggregation.schema.name === 'metric' && aggregation.enabled ? ++count : count, 0 ); }; -const calcAggIsTooLow = (agg: AggConfig, aggIndex: number, group: AggConfig[]) => { +const calcAggIsTooLow = (agg: IAggConfig, aggIndex: number, group: IAggConfig[]) => { if (!agg.schema.mustBeFirst) { return false; } - const firstDifferentSchema = findIndex(group, (aggr: AggConfig) => { + const firstDifferentSchema = findIndex(group, (aggr: IAggConfig) => { return aggr.schema !== agg.schema; }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx index b06ca1c2ce57a..d022297ae72b3 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_group_state.tsx @@ -17,7 +17,7 @@ * under the License. */ -import { AggConfig } from '../legacy_imports'; +import { IAggConfig } from '../legacy_imports'; export enum AGGS_ACTION_KEYS { TOUCHED = 'aggsTouched', @@ -52,7 +52,7 @@ function aggGroupReducer(state: AggsState, action: AggsAction): AggsState { } } -function initAggsState(group: AggConfig[]): AggsState { +function initAggsState(group: IAggConfig[]): AggsState { return group.reduce((state, agg) => { state[agg.id] = { touched: false, valid: true }; return state; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts index 01a41d3c412c2..fc535884c69ff 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_param_props.ts @@ -19,7 +19,7 @@ import { Field } from 'src/plugins/data/public'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfig, AggParam, EditorConfig } from '../legacy_imports'; +import { IAggConfig, AggParam, EditorConfig } from '../legacy_imports'; import { ComboBoxGroupedOptions } from '../utils'; // NOTE: we cannot export the interface with export { InterfaceName } @@ -27,7 +27,7 @@ import { ComboBoxGroupedOptions } from '../utils'; // https://github.com/babel/babel/issues/7641 // export interface AggParamCommonProps<T, P = AggParam> { - agg: AggConfig; + agg: IAggConfig; aggParam: P; disabled?: boolean; editorConfig: EditorConfig; @@ -36,7 +36,7 @@ export interface AggParamCommonProps<T, P = AggParam> { showValidation: boolean; state: VisState; value?: T; - metricAggs: AggConfig[]; + metricAggs: IAggConfig[]; } export interface AggParamEditorProps<T, P = AggParam> extends AggParamCommonProps<T, P> { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx index d782c819c7c41..5636059394bac 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.test.tsx @@ -23,7 +23,7 @@ import { mount, shallow } from 'enzyme'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; import { IndexPattern } from 'src/plugins/data/public'; import { DefaultEditorAggParams, DefaultEditorAggParamsProps } from './agg_params'; -import { AggConfig, AggGroupNames } from '../legacy_imports'; +import { IAggConfig, AggGroupNames } from '../legacy_imports'; const mockEditorConfig = { useNormalizedEsInterval: { hidden: false, fixedValue: false }, @@ -97,7 +97,7 @@ describe('DefaultEditorAggParams component', () => { schema: { title: '', }, - } as any) as AggConfig, + } as any) as IAggConfig, groupName: AggGroupNames.Metrics, formIsTouched: false, indexPattern: {} as IndexPattern, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx index 47e98f175ab73..1b450957f3b26 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params.tsx @@ -24,7 +24,7 @@ import useUnmount from 'react-use/lib/useUnmount'; import { IndexPattern } from 'src/plugins/data/public'; import { - AggConfig, + IAggConfig, AggGroupNames, editorConfigProviders, FixedParam, @@ -54,7 +54,7 @@ type EditorParamConfigType = EditorParamConfig & { }; export interface DefaultEditorAggParamsProps extends DefaultEditorCommonProps { - agg: AggConfig; + agg: IAggConfig; aggError?: string; aggIndex?: number; aggIsTooLow?: boolean; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts index 6f584b4329500..ec56d22143699 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.test.ts @@ -20,8 +20,8 @@ import { IndexPattern, Field } from 'src/plugins/data/public'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; import { - AggConfig, - AggType, + IAggConfig, + IAggType, AggGroupNames, BUCKET_TYPES, IndexedArray, @@ -42,10 +42,10 @@ jest.mock('ui/new_platform'); describe('DefaultEditorAggParams helpers', () => { describe('getAggParamsToRender', () => { - let agg: AggConfig; + let agg: IAggConfig; let editorConfig: EditorConfig; const state = {} as VisState; - const metricAggs: AggConfig[] = []; + const metricAggs: IAggConfig[] = []; const emptyParams = { basic: [], advanced: [], @@ -57,14 +57,14 @@ describe('DefaultEditorAggParams helpers', () => { params: [{ name: 'interval' }], }, schema: {}, - } as AggConfig; + } as IAggConfig; const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); expect(params).toEqual(emptyParams); }); it('should not create any param if there is no agg type', () => { - agg = {} as AggConfig; + agg = {} as IAggConfig; const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); expect(params).toEqual(emptyParams); @@ -75,7 +75,7 @@ describe('DefaultEditorAggParams helpers', () => { type: { params: [{ name: 'interval' }], }, - } as AggConfig; + } as IAggConfig; editorConfig = { interval: { hidden: true, @@ -94,7 +94,7 @@ describe('DefaultEditorAggParams helpers', () => { schema: { hideCustomLabel: true, }, - } as AggConfig; + } as IAggConfig; const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); expect(params).toEqual(emptyParams); @@ -131,7 +131,7 @@ describe('DefaultEditorAggParams helpers', () => { orderBy: 'orderBy', field: 'field', }, - } as any) as AggConfig; + } as any) as IAggConfig; const params = getAggParamsToRender({ agg, editorConfig, metricAggs, state }); expect(params).toEqual({ @@ -166,14 +166,14 @@ describe('DefaultEditorAggParams helpers', () => { describe('getAggTypeOptions', () => { it('should return agg type options grouped by subtype', () => { const indexPattern = {} as IndexPattern; - const aggs = getAggTypeOptions({} as AggConfig, indexPattern, 'metrics'); + const aggs = getAggTypeOptions({} as IAggConfig, indexPattern, 'metrics'); expect(aggs).toEqual(['indexedFields']); }); }); describe('isInvalidParamsTouched', () => { - let aggType: AggType; + let aggType: IAggType; const aggTypeState = { touched: false, valid: true, diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts index 21154bd7ad603..5a9d95725c8e4 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_params_helper.ts @@ -29,17 +29,17 @@ import { aggTypeFilters, aggTypeFieldFilters, aggTypes, - AggConfig, + IAggConfig, AggParam, - FieldParamType, - AggType, + IFieldParamType, + IAggType, EditorConfig, } from '../legacy_imports'; interface ParamInstanceBase { - agg: AggConfig; + agg: IAggConfig; editorConfig: EditorConfig; - metricAggs: AggConfig[]; + metricAggs: IAggConfig[]; state: VisState; } @@ -73,7 +73,7 @@ function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamIns } // if field param exists, compute allowed fields if (param.type === 'field') { - const availableFields: Field[] = (param as FieldParamType).getAvailableFields( + const availableFields: Field[] = (param as IFieldParamType).getAvailableFields( agg.getIndexPattern().fields ); fields = aggTypeFieldFilters.filter(availableFields, agg); @@ -117,10 +117,10 @@ function getAggParamsToRender({ agg, editorConfig, metricAggs, state }: ParamIns } function getAggTypeOptions( - agg: AggConfig, + agg: IAggConfig, indexPattern: IndexPattern, groupName: string -): ComboBoxGroupedOptions<AggType> { +): ComboBoxGroupedOptions<IAggType> { const aggTypeOptions = aggTypeFilters.filter((aggTypes as any)[groupName], indexPattern, agg); return groupAndSortBy(aggTypeOptions as any[], 'subtype', 'title'); } @@ -135,7 +135,7 @@ function getAggTypeOptions( * @param aggParams State of aggregation parameters. */ function isInvalidParamsTouched( - aggType: AggType, + aggType: IAggType, aggTypeState: AggTypeState, aggParams: AggParamsState ) { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx index 2a9c74521e525..0ec19bfa1b843 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/agg_select.tsx @@ -24,20 +24,20 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { IndexPattern } from 'src/plugins/data/public'; -import { AggType, documentationLinks } from '../legacy_imports'; +import { IAggType, documentationLinks } from '../legacy_imports'; import { ComboBoxGroupedOptions } from '../utils'; import { AGG_TYPE_ACTION_KEYS, AggTypeAction } from './agg_params_state'; interface DefaultEditorAggSelectProps { aggError?: string; - aggTypeOptions: ComboBoxGroupedOptions<AggType>; + aggTypeOptions: ComboBoxGroupedOptions<IAggType>; id: string; indexPattern: IndexPattern; showValidation: boolean; isSubAggregation: boolean; - value: AggType; + value: IAggType; onChangeAggType: React.Dispatch<AggTypeAction>; - setValue: (aggType: AggType) => void; + setValue: (aggType: IAggType) => void; } function DefaultEditorAggSelect({ @@ -51,7 +51,7 @@ function DefaultEditorAggSelect({ isSubAggregation, onChangeAggType, }: DefaultEditorAggSelectProps) { - const selectedOptions: ComboBoxGroupedOptions<AggType> = value + const selectedOptions: ComboBoxGroupedOptions<IAggType> = value ? [{ label: value.title, target: value }] : []; @@ -104,7 +104,7 @@ function DefaultEditorAggSelect({ (options: EuiComboBoxOptionProps[]) => { const selectedOption = get(options, '0.target'); if (selectedOption) { - setValue(selectedOption as AggType); + setValue(selectedOption as IAggType); } }, [setValue] diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx index c8b5196d3b299..7f04b851902de 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_control_props.tsx @@ -18,11 +18,11 @@ */ import { VisParams } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; import { DefaultEditorAggCommonProps } from '../agg_common_props'; export interface AggControlProps { - agg: AggConfig; + agg: IAggConfig; editorStateParams: VisParams; setAggParamValue: DefaultEditorAggCommonProps['setAggParamValue']; setStateParamValue: DefaultEditorAggCommonProps['setStateParamValue']; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx index 5c69fd0f1c091..0b847e3747b30 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/agg_utils.test.tsx @@ -20,7 +20,7 @@ import React, { FunctionComponent } from 'react'; import { mount, ReactWrapper } from 'enzyme'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; import { safeMakeLabel, useAvailableOptions, @@ -57,7 +57,7 @@ const metricAggs = [ return 'avg'; }, }, -] as AggConfig[]; +] as IAggConfig[]; const incompatibleAggs = [ { @@ -74,7 +74,7 @@ const incompatibleAggs = [ return 'percentiles'; }, }, -] as AggConfig[]; +] as IAggConfig[]; const aggFilter = ['!top_hits', '!percentiles']; describe('Aggregations utils', () => { @@ -222,7 +222,7 @@ describe('Aggregations utils', () => { }); test('should not fail and return a safety string if makeLabel func is not exist', () => { - const label = safeMakeLabel({} as AggConfig); + const label = safeMakeLabel({} as IAggConfig); expect(label).toEqual(expect.any(String)); }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx index e43304fe07347..636ef8f872d0e 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.test.tsx @@ -26,7 +26,7 @@ import { Field } from 'src/plugins/data/public'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; import { ComboBoxGroupedOptions } from '../../utils'; import { FieldParamEditor, FieldParamEditorProps } from './field'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; function callComboBoxOnChange(comp: ReactWrapper, value: any = []) { const comboBoxProps: EuiComboBoxProps<string> = comp.find(EuiComboBox).props(); @@ -64,7 +64,7 @@ describe('FieldParamEditor component', () => { ]; defaultProps = { - agg: {} as AggConfig, + agg: {} as IAggConfig, aggParam: { name: 'field', type: 'field', @@ -80,7 +80,7 @@ describe('FieldParamEditor component', () => { setValidity, setTouched, state: {} as VisState, - metricAggs: [] as AggConfig[], + metricAggs: [] as IAggConfig[], }; }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx index 38c55e8fe3f24..f374353afabec 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/field.tsx @@ -24,7 +24,7 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Field } from 'src/plugins/data/public'; -import { AggConfig, AggParam, FieldParamType } from '../../legacy_imports'; +import { AggParam, IAggConfig, IFieldParamType } from '../../legacy_imports'; import { formatListAsProse, parseCommaSeparatedList, useValidation } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { ComboBoxGroupedOptions } from '../../utils'; @@ -126,9 +126,10 @@ function FieldParamEditor({ ); } -function getFieldTypesString(agg: AggConfig) { +function getFieldTypesString(agg: IAggConfig) { const param = - get(agg, 'type.params', []).find((p: AggParam) => p.name === 'field') || ({} as FieldParamType); + get(agg, 'type.params', []).find((p: AggParam) => p.name === 'field') || + ({} as IFieldParamType); return formatListAsProse(parseCommaSeparatedList(param.filterFieldTypes), { inclusive: false }); } diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx index 38c5b552553ae..3622b27bad403 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/filter.tsx @@ -22,7 +22,7 @@ import { EuiForm, EuiButtonIcon, EuiFieldText, EuiFormRow, EuiSpacer } from '@el import { i18n } from '@kbn/i18n'; import { Query, QueryStringInput } from '../../../../../../plugins/data/public'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; interface FilterRowProps { id: string; @@ -34,7 +34,7 @@ interface FilterRowProps { dataTestSubj: string; onChangeValue(id: string, query: Query, label: string): void; onRemoveFilter(id: string): void; - agg: AggConfig; + agg: IAggConfig; } function FilterRow({ diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx index 9b6fd204e7207..cf7af1aa5cb3a 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/metric_agg.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; import { DEFAULT_OPTIONS, aggFilter, MetricAggParamEditor } from './metric_agg'; jest.mock('./utils', () => ({ @@ -44,7 +44,7 @@ const agg = { makeLabel() { return 'cumulative_sum'; }, -} as AggConfig; +} as IAggConfig; const metricAggs = [ agg, @@ -69,7 +69,7 @@ const metricAggs = [ return 'max'; }, }, -] as AggConfig[]; +] as IAggConfig[]; describe('MetricAggParamEditor', () => { let defaultProps: Partial<AggParamEditorProps<string>>; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx index 6bb9ad334d149..10679b578d54e 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/order_agg.tsx @@ -20,7 +20,7 @@ import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType, AggConfig, AggGroupNames } from '../../legacy_imports'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../legacy_imports'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -35,7 +35,7 @@ function OrderAggParamEditor({ setValue, setValidity, setTouched, -}: AggParamEditorProps<AggConfig, AggParamType>) { +}: AggParamEditorProps<IAggConfig, AggParamType>) { const orderBy = agg.params.orderBy; useEffect(() => { @@ -51,7 +51,7 @@ function OrderAggParamEditor({ const { onAggTypeChange, setAggParamValue } = useSubAggParamsHandlers( agg, aggParam, - value as AggConfig, + value as IAggConfig, setValue ); @@ -63,7 +63,7 @@ function OrderAggParamEditor({ <> <EuiSpacer size="m" /> <DefaultEditorAggParams - agg={value as AggConfig} + agg={value as IAggConfig} groupName={AggGroupNames.Metrics} className="visEditorAgg__subAgg" formIsTouched={formIsTouched} diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx index 71a8294541bbd..5dc28b59a52b3 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_agg.tsx @@ -20,7 +20,7 @@ import React, { useEffect } from 'react'; import { EuiSpacer } from '@elastic/eui'; -import { AggParamType, AggConfig, AggGroupNames } from '../../legacy_imports'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../legacy_imports'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -35,7 +35,7 @@ function SubAggParamEditor({ setValue, setValidity, setTouched, -}: AggParamEditorProps<AggConfig, AggParamType>) { +}: AggParamEditorProps<IAggConfig, AggParamType>) { useEffect(() => { // we aren't creating a custom aggConfig if (agg.params.metricAgg !== 'custom') { diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx index 9898d943870bc..45ff0610d88ed 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/sub_metric.tsx @@ -21,7 +21,7 @@ import React, { useEffect } from 'react'; import { EuiFormLabel, EuiSpacer } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { AggParamType, AggConfig, AggGroupNames } from '../../legacy_imports'; +import { AggParamType, IAggConfig, AggGroupNames } from '../../legacy_imports'; import { useSubAggParamsHandlers } from './utils'; import { AggParamEditorProps } from '../agg_param_props'; import { DefaultEditorAggParams } from '../agg_params'; @@ -35,7 +35,7 @@ function SubMetricParamEditor({ setValue, setValidity, setTouched, -}: AggParamEditorProps<AggConfig, AggParamType>) { +}: AggParamEditorProps<IAggConfig, AggParamType>) { const metricTitle = i18n.translate('visDefaultEditor.controls.metrics.metricTitle', { defaultMessage: 'Metric', }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts b/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts index 4e811f4543412..894bc594a08d7 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/test_utils.ts @@ -18,14 +18,14 @@ */ import { VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfig, AggParam, EditorConfig } from '../../legacy_imports'; +import { IAggConfig, AggParam, EditorConfig } from '../../legacy_imports'; export const aggParamCommonPropsMock = { - agg: {} as AggConfig, + agg: {} as IAggConfig, aggParam: {} as AggParam, editorConfig: {} as EditorConfig, formIsTouched: false, - metricAggs: [] as AggConfig[], + metricAggs: [] as IAggConfig[], state: {} as VisState, showValidation: false, }; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx index b0c3fe00606aa..4ce0712040bd5 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.test.tsx @@ -25,10 +25,10 @@ import { TopAggregateParamEditorProps, } from './top_aggregate'; import { aggParamCommonPropsMock } from './test_utils'; -import { AggConfig } from '../../legacy_imports'; +import { IAggConfig } from '../../legacy_imports'; describe('TopAggregateParamEditor', () => { - let agg: AggConfig; + let agg: IAggConfig; let aggParam: any; let defaultProps: TopAggregateParamEditorProps; let options: AggregateValueProp[]; @@ -37,17 +37,17 @@ describe('TopAggregateParamEditor', () => { options = [ { text: 'Min', - isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'number'), + isCompatible: jest.fn((aggr: IAggConfig) => aggr.params.field.type === 'number'), value: 'min', }, { text: 'Max', - isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'number'), + isCompatible: jest.fn((aggr: IAggConfig) => aggr.params.field.type === 'number'), value: 'max', }, { text: 'Average', - isCompatible: jest.fn((aggr: AggConfig) => aggr.params.field.type === 'string'), + isCompatible: jest.fn((aggr: IAggConfig) => aggr.params.field.type === 'string'), value: 'average', }, ]; @@ -69,7 +69,7 @@ describe('TopAggregateParamEditor', () => { }, }, getAggParams: jest.fn(() => [{ name: 'aggregate', options }]), - } as any) as AggConfig; + } as any) as IAggConfig; defaultProps = { ...aggParamCommonPropsMock, agg, @@ -150,7 +150,7 @@ describe('TopAggregateParamEditor', () => { type: 'string', }, }, - } as AggConfig; + } as IAggConfig; comp.setProps({ agg }); @@ -165,7 +165,7 @@ describe('TopAggregateParamEditor', () => { type: 'date', }, }, - } as AggConfig; + } as IAggConfig; comp.setProps({ agg }); @@ -179,7 +179,7 @@ describe('TopAggregateParamEditor', () => { type: 'string', }, }, - } as AggConfig; + } as IAggConfig; comp.setProps({ agg, value: undefined }); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.tsx index 338e2fe463a80..346dfc0156f07 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/top_aggregate.tsx @@ -23,7 +23,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - AggConfig, + IAggConfig, AggParam, OptionedValueProp, OptionedParamEditorProps, @@ -32,13 +32,13 @@ import { import { AggParamEditorProps } from '../agg_param_props'; export interface AggregateValueProp extends OptionedValueProp { - isCompatible(aggConfig: AggConfig): boolean; + isCompatible(aggConfig: IAggConfig): boolean; } export type TopAggregateParamEditorProps = AggParamEditorProps<AggregateValueProp> & OptionedParamEditorProps<AggregateValueProp>; -export function getCompatibleAggs(agg: AggConfig): AggregateValueProp[] { +export function getCompatibleAggs(agg: IAggConfig): AggregateValueProp[] { const { options = [] } = agg .getAggParams() .find(({ name }: AggParam) => name === 'aggregate') as OptionedParamType; diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts b/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts index 4c8ba23e63268..8aeae488942cd 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/agg_utils.ts @@ -20,7 +20,7 @@ import { useEffect, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { AggConfig } from '../../../legacy_imports'; +import { IAggConfig } from '../../../legacy_imports'; type AggFilter = string[]; @@ -43,7 +43,7 @@ function useCompatibleAggCallback(aggFilter: AggFilter) { function useFallbackMetric( setValue: (value?: string) => void, aggFilter: AggFilter, - metricAggs?: AggConfig[], + metricAggs?: IAggConfig[], value?: string, fallbackValue?: string ) { @@ -69,7 +69,7 @@ function useFallbackMetric( */ function useAvailableOptions( aggFilter: AggFilter, - metricAggs: AggConfig[] = [], + metricAggs: IAggConfig[] = [], defaultOptions: Array<{ text: string; value: string }> = [] ) { const isCompatibleAgg = useCompatibleAggCallback(aggFilter); @@ -107,7 +107,7 @@ function useValidation(setValidity: (isValid: boolean) => void, isValid: boolean }, [isValid, setValidity]); } -function safeMakeLabel(agg: AggConfig): string { +function safeMakeLabel(agg: IAggConfig): string { try { return agg.makeLabel(); } catch (e) { @@ -118,7 +118,7 @@ function safeMakeLabel(agg: AggConfig): string { } function isCompatibleAggregation(aggFilter: string[]) { - return (agg: AggConfig) => { + return (agg: IAggConfig) => { return !aggFilter.includes(`!${agg.type.name}`); }; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts index c2da648edcf81..c7816d5a9d305 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/controls/utils/use_handlers.ts @@ -19,14 +19,14 @@ import { useCallback } from 'react'; -import { AggConfig, AggParamType } from '../../../legacy_imports'; +import { IAggConfig, AggParamType } from '../../../legacy_imports'; -type SetValue = (value?: AggConfig) => void; +type SetValue = (value?: IAggConfig) => void; function useSubAggParamsHandlers( - agg: AggConfig, + agg: IAggConfig, aggParam: AggParamType, - subAgg: AggConfig, + subAgg: IAggConfig, setValue: SetValue ) { const setAggParamValue = useCallback( diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx index c7597ef43dfa6..efd17f02a0e09 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/data_tab.tsx @@ -23,11 +23,11 @@ import { EuiSpacer } from '@elastic/eui'; import { VisState } from 'src/legacy/core_plugins/visualizations/public'; import { - AggConfig, + IAggConfig, AggGroupNames, ISchemas, parentPipelineType, - MetricAggType, + IMetricAggType, } from '../../legacy_imports'; import { DefaultEditorAggGroup } from '../agg_group'; import { @@ -45,7 +45,7 @@ export interface DefaultEditorDataTabProps { dispatch: React.Dispatch<EditorAction>; formIsTouched: boolean; isTabSelected: boolean; - metricAggs: AggConfig[]; + metricAggs: IAggConfig[]; schemas: ISchemas; state: VisState; setTouched(isTouched: boolean): void; @@ -67,7 +67,7 @@ function DefaultEditorDataTab({ () => findLast( metricAggs, - ({ type }: { type: MetricAggType }) => type.subtype === parentPipelineType + ({ type }: { type: IMetricAggType }) => type.subtype === parentPipelineType ), [metricAggs] ); diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts index 5738916d2ff80..93fa1083bebf9 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/actions.ts @@ -18,7 +18,7 @@ */ import { Vis, VisParams } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfig, Schema } from '../../../legacy_imports'; +import { IAggConfig, Schema } from '../../../legacy_imports'; import { EditorStateActionTypes } from './constants'; export interface ActionType<T, P> { @@ -26,14 +26,14 @@ export interface ActionType<T, P> { payload: P; } -type AggId = AggConfig['id']; -type AggParams = AggConfig['params']; +type AggId = IAggConfig['id']; +type AggParams = IAggConfig['params']; type AddNewAgg = ActionType<EditorStateActionTypes.ADD_NEW_AGG, { schema: Schema }>; type DiscardChanges = ActionType<EditorStateActionTypes.DISCARD_CHANGES, Vis>; type ChangeAggType = ActionType< EditorStateActionTypes.CHANGE_AGG_TYPE, - { aggId: AggId; value: AggConfig['type'] } + { aggId: AggId; value: IAggConfig['type'] } >; type SetAggParamValue<T extends AggParams = AggParams> = ActionType< EditorStateActionTypes.SET_AGG_PARAM_VALUE, @@ -50,11 +50,11 @@ type SetStateParamValue<T extends keyof AggParams = keyof AggParams> = ActionTyp type RemoveAgg = ActionType<EditorStateActionTypes.REMOVE_AGG, { aggId: AggId }>; type ReorderAggs = ActionType< EditorStateActionTypes.REORDER_AGGS, - { sourceAgg: AggConfig; destinationAgg: AggConfig } + { sourceAgg: IAggConfig; destinationAgg: IAggConfig } >; type ToggleEnabledAgg = ActionType< EditorStateActionTypes.TOGGLE_ENABLED_AGG, - { aggId: AggId; enabled: AggConfig['enabled'] } + { aggId: AggId; enabled: IAggConfig['enabled'] } >; type UpdateStateParams = ActionType< EditorStateActionTypes.UPDATE_STATE_PARAMS, @@ -75,7 +75,7 @@ export type EditorAction = export interface EditorActions { addNewAgg(schema: Schema): AddNewAgg; discardChanges(vis: Vis): DiscardChanges; - changeAggType(aggId: AggId, value: AggConfig['type']): ChangeAggType; + changeAggType(aggId: AggId, value: IAggConfig['type']): ChangeAggType; setAggParamValue<T extends keyof AggParams>( aggId: AggId, paramName: T, @@ -86,8 +86,8 @@ export interface EditorActions { value: AggParams[T] ): SetStateParamValue<T>; removeAgg(aggId: AggId): RemoveAgg; - reorderAggs(sourceAgg: AggConfig, destinationAgg: AggConfig): ReorderAggs; - toggleEnabledAgg(aggId: AggId, enabled: AggConfig['enabled']): ToggleEnabledAgg; + reorderAggs(sourceAgg: IAggConfig, destinationAgg: IAggConfig): ReorderAggs; + toggleEnabledAgg(aggId: AggId, enabled: IAggConfig['enabled']): ToggleEnabledAgg; updateStateParams(params: VisParams): UpdateStateParams; } diff --git a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts index 8e1cfd6bc9c13..851263f0ed702 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/components/sidebar/state/reducers.ts @@ -20,7 +20,7 @@ import { cloneDeep } from 'lodash'; import { Vis, VisState } from 'src/legacy/core_plugins/visualizations/public'; -import { AggConfigs, AggConfig, AggGroupNames, move } from '../../../legacy_imports'; +import { AggConfigs, IAggConfig, AggGroupNames, move } from '../../../legacy_imports'; import { EditorStateActionTypes } from './constants'; import { getEnabledMetricAggsCount } from '../../agg_group_helper'; import { EditorAction } from './actions'; @@ -32,7 +32,7 @@ function initEditorState(vis: Vis) { function editorStateReducer(state: VisState, action: EditorAction): VisState { switch (action.type) { case EditorStateActionTypes.ADD_NEW_AGG: { - const aggConfig = state.aggs.createAggConfig(action.payload as AggConfig, { + const aggConfig = state.aggs.createAggConfig(action.payload as IAggConfig, { addToAggConfigs: false, }); aggConfig.brandNew = true; diff --git a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts index 5c617f3dc8681..f023b808cb0a7 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/legacy_imports.ts @@ -20,34 +20,33 @@ /* `ui/agg_types` dependencies */ export { AggType, - AggConfig, + IAggType, + IAggConfig, AggConfigs, + IAggConfigs, AggParam, AggGroupNames, aggGroupNamesMap, aggTypes, FieldParamType, + IFieldParamType, BUCKET_TYPES, METRIC_TYPES, ISchemas, Schema, termsAggFilter, } from 'ui/agg_types'; -export { aggTypeFilters, propFilter } from 'ui/agg_types/filter'; -export { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; -export { AggParamType } from 'ui/agg_types/param_types/agg'; -export { MetricAggType } from 'ui/agg_types/metrics/metric_agg_type'; -export { parentPipelineType } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; -export { siblingPipelineType } from 'ui/agg_types/metrics/lib/sibling_pipeline_agg_helper'; -export { isType, isStringType } from 'ui/agg_types/buckets/migrate_include_exclude_format'; -export { - OptionedValueProp, - OptionedParamEditorProps, - OptionedParamType, -} from 'ui/agg_types/param_types/optioned'; -export { isValidJson, isValidInterval } from 'ui/agg_types/utils'; -export { AggParamOption } from 'ui/agg_types/agg_params'; -export { CidrMask } from 'ui/agg_types/buckets/lib/cidr_mask'; +export { aggTypeFilters, propFilter } from 'ui/agg_types'; +export { aggTypeFieldFilters } from 'ui/agg_types'; +export { AggParamType } from 'ui/agg_types'; +export { MetricAggType, IMetricAggType } from 'ui/agg_types'; +export { parentPipelineType } from 'ui/agg_types'; +export { siblingPipelineType } from 'ui/agg_types'; +export { isType, isStringType } from 'ui/agg_types'; +export { OptionedValueProp, OptionedParamEditorProps, OptionedParamType } from 'ui/agg_types'; +export { isValidJson, isValidInterval } from 'ui/agg_types'; +export { AggParamOption } from 'ui/agg_types'; +export { CidrMask } from 'ui/agg_types'; export { PersistedState } from 'ui/persisted_state'; export { IndexedArray } from 'ui/indexed_array'; diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx index f51e359d99573..babcb59c6582e 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx +++ b/src/legacy/core_plugins/vis_default_editor/public/vis_options_props.tsx @@ -17,11 +17,11 @@ * under the License. */ -import { AggConfigs, PersistedState } from './legacy_imports'; +import { IAggConfigs, PersistedState } from './legacy_imports'; import { Vis } from '../../visualizations/public'; export interface VisOptionsProps<VisParamType = unknown> { - aggs: AggConfigs; + aggs: IAggConfigs; hasHistogramAgg: boolean; isTabSelected: boolean; stateParams: VisParamType; diff --git a/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts b/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts index c1832d5512817..60b675f50a342 100644 --- a/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts +++ b/src/legacy/core_plugins/vis_default_editor/public/vis_type_agg_filter.ts @@ -17,7 +17,7 @@ * under the License. */ import { IndexPattern } from 'src/plugins/data/public'; -import { AggType, AggConfig, aggTypeFilters, propFilter } from './legacy_imports'; +import { IAggType, IAggConfig, aggTypeFilters, propFilter } from './legacy_imports'; const filterByName = propFilter('name'); @@ -25,7 +25,9 @@ const filterByName = propFilter('name'); * This filter checks the defined aggFilter in the schemas of that visualization * and limits available aggregations based on that. */ -aggTypeFilters.addFilter((aggType: AggType, indexPatterns: IndexPattern, aggConfig: AggConfig) => { - const doesSchemaAllowAggType = filterByName([aggType], aggConfig.schema.aggFilter).length !== 0; - return doesSchemaAllowAggType; -}); +aggTypeFilters.addFilter( + (aggType: IAggType, indexPatterns: IndexPattern, aggConfig: IAggConfig) => { + const doesSchemaAllowAggType = filterByName([aggType], aggConfig.schema.aggFilter).length !== 0; + return doesSchemaAllowAggType; + } +); diff --git a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts index 8a454957b7ab9..efa6c0029e6d1 100644 --- a/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_table/public/legacy_imports.ts @@ -19,7 +19,7 @@ export { npSetup, npStart } from 'ui/new_platform'; export { getFormat } from 'ui/visualize/loader/pipeline_helpers/utilities'; -export { AggConfig, AggGroupNames, Schemas } from 'ui/agg_types'; +export { IAggConfig, AggGroupNames, Schemas } from 'ui/agg_types'; // @ts-ignore export { PrivateProvider } from 'ui/private/private'; diff --git a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts index 16181a3f70ff1..d8912975227bf 100644 --- a/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts +++ b/src/legacy/core_plugins/vis_type_table/public/table_vis_controller.test.ts @@ -27,7 +27,7 @@ import './table_vis.mock'; import StubIndexPattern from 'test_utils/stub_index_pattern'; import { getAngularModule } from './get_inner_angular'; import { initTableVisLegacyModule } from './table_vis_legacy_module'; -import { npStart, AggConfig, tabifyAggResponse } from './legacy_imports'; +import { npStart, IAggConfig, tabifyAggResponse } from './legacy_imports'; import { tableVisTypeDefinition } from './table_vis_type'; import { Vis } from '../../visualizations/public'; import { setup as visualizationsSetup } from '../../visualizations/public/np_ready/public/legacy'; @@ -148,7 +148,7 @@ describe('Table Vis - Controller', () => { // basically a parameterized beforeEach function initController(vis: Vis) { - vis.aggs.aggs.forEach((agg: AggConfig, i: number) => { + vis.aggs.aggs.forEach((agg: IAggConfig, i: number) => { agg.id = 'agg_' + (i + 1); }); diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx index 514b957765a99..944ed7e20d1f7 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.test.tsx @@ -27,7 +27,7 @@ import { Positions } from '../../../utils/collections'; import { ValueAxesPanel } from './value_axes_panel'; import { CategoryAxisPanel } from './category_axis_panel'; import { ChartTypes } from '../../../utils/collections'; -import { AggConfig, AggType } from '../../../legacy_imports'; +import { IAggConfig, IAggType } from '../../../legacy_imports'; import { defaultValueAxisId, valueAxis, seriesParam, categoryAxis } from './mocks'; jest.mock('ui/new_platform'); @@ -44,17 +44,17 @@ jest.mock('./value_axes_panel', () => ({ const SERIES_PARAMS = 'seriesParams'; const VALUE_AXES = 'valueAxes'; -const aggCount: AggConfig = { +const aggCount: IAggConfig = { id: '1', type: { name: 'count' }, makeLabel: () => 'Count', -} as AggConfig; +} as IAggConfig; -const aggAverage: AggConfig = { +const aggAverage: IAggConfig = { id: '2', - type: { name: 'average' } as AggType, + type: { name: 'average' } as IAggType, makeLabel: () => 'Average', -} as AggConfig; +} as IAggConfig; const createAggs = (aggs: any[]) => ({ aggs, diff --git a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx index c4dcbfaa47265..cdc8996f3fdeb 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx +++ b/src/legacy/core_plugins/vis_type_vislib/public/components/options/metrics_axes/index.tsx @@ -21,7 +21,7 @@ import React, { useState, useEffect, useCallback, useMemo } from 'react'; import { cloneDeep, uniq, get } from 'lodash'; import { EuiSpacer } from '@elastic/eui'; -import { AggConfig } from '../../../legacy_imports'; +import { IAggConfig } from '../../../legacy_imports'; import { BasicVislibParams, ValueAxis, SeriesParam, Axis } from '../../../types'; import { ValidationVisOptionsProps } from '../../common'; import { SeriesPanel } from './series_panel'; @@ -99,7 +99,7 @@ function MetricsAxisOptions(props: ValidationVisOptionsProps<BasicVislibParams>) stateParams.valueAxes.forEach((axis, axisNumber) => { let newCustomLabel = ''; - const matchingSeries: AggConfig[] = []; + const matchingSeries: IAggConfig[] = []; series.forEach((serie, seriesIndex) => { if ((axisNumber === 0 && !serie.valueAxis) || serie.valueAxis === axis.id) { diff --git a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts index df7278f2b761f..50a91df01de7c 100644 --- a/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts +++ b/src/legacy/core_plugins/vis_type_vislib/public/legacy_imports.ts @@ -17,7 +17,7 @@ * under the License. */ -export { AggType, AggConfig, AggGroupNames, Schemas } from 'ui/agg_types'; +export { AggType, AggGroupNames, IAggConfig, IAggType, Schemas } from 'ui/agg_types'; // @ts-ignore export { SimpleEmitter } from 'ui/utils/simple_emitter'; // @ts-ignore diff --git a/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts b/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts index 46ade8ce465c0..719d69e21a826 100644 --- a/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts +++ b/src/legacy/core_plugins/visualizations/public/embeddable/query_geohash_bounds.ts @@ -21,7 +21,7 @@ import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; import { toastNotifications } from 'ui/notify'; -import { AggConfig } from 'ui/agg_types'; +import { IAggConfig } from 'ui/agg_types'; import { timefilter } from 'ui/timefilter'; import { Vis } from '../np_ready/public'; import { esFilters, Query, SearchSource, ISearchSource } from '../../../../../plugins/data/public'; @@ -42,7 +42,7 @@ interface QueryGeohashBoundsParams { * TODO: Remove this as a part of elastic/kibana#30593 */ export async function queryGeohashBounds(vis: Vis, params: QueryGeohashBoundsParams) { - const agg = vis.getAggConfig().aggs.find((a: AggConfig) => { + const agg = vis.getAggConfig().aggs.find((a: IAggConfig) => { return get(a, 'type.dslName') === 'geohash_grid'; }); diff --git a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts index 3088c4e67a3b7..5cff588d951b0 100644 --- a/src/legacy/core_plugins/visualizations/public/legacy_imports.ts +++ b/src/legacy/core_plugins/visualizations/public/legacy_imports.ts @@ -18,12 +18,13 @@ */ export { PersistedState } from '../../../ui/public/persisted_state'; -export { AggConfig } from '../../../ui/public/agg_types/agg_config'; -export { AggConfigs } from '../../../ui/public/agg_types/agg_configs'; export { + AggConfigs, + IAggConfig, + IAggConfigs, isDateHistogramBucketAggConfig, setBounds, -} from '../../../ui/public/agg_types/buckets/date_histogram'; +} from '../../../ui/public/agg_types'; export { createFormat } from '../../../ui/public/visualize/loader/pipeline_helpers/utilities'; export { I18nContext } from '../../../ui/public/i18n'; import chrome from '../../../ui/public/chrome'; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts index e733bad2c0127..d1017de35474a 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.test.ts @@ -27,11 +27,11 @@ import { Schemas, } from './build_pipeline'; import { Vis, VisState } from '..'; -import { AggConfig } from '../../../legacy_imports'; +import { IAggConfig } from '../../../legacy_imports'; import { searchSourceMock } from '../../../legacy_mocks'; jest.mock('ui/new_platform'); -jest.mock('ui/agg_types/buckets/date_histogram', () => ({ +jest.mock('ui/agg_types', () => ({ setBounds: () => {}, dateHistogramBucketAgg: () => {}, isDateHistogramBucketAggConfig: () => true, @@ -367,7 +367,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => { }); describe('buildVislibDimensions', () => { - let aggs: AggConfig[]; + let aggs: IAggConfig[]; let visState: any; let vis: Vis; let params: any; @@ -385,7 +385,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => { name: 'metric', }, params: {}, - } as AggConfig, + } as IAggConfig, ]; params = { @@ -453,7 +453,7 @@ describe('visualize loader pipeline helpers: build pipeline', () => { it('with two numeric metrics, mixed normal and percent mode should have corresponding formatters', async () => { const aggConfig = aggs[0]; - aggs = [{ ...aggConfig } as AggConfig, { ...aggConfig, id: '5' } as AggConfig]; + aggs = [{ ...aggConfig } as IAggConfig, { ...aggConfig, id: '5' } as IAggConfig]; visState = { params: { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index 6749a44b4d5b3..04a296a888e87 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -23,7 +23,7 @@ import moment from 'moment'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { ISearchSource } from 'src/plugins/data/public'; import { - AggConfig, + IAggConfig, setBounds, isDateHistogramBucketAggConfig, createFormat, @@ -85,7 +85,7 @@ const vislibCharts: string[] = [ ]; export const getSchemas = (vis: Vis, timeRange?: any): Schemas => { - const createSchemaConfig = (accessor: number, agg: AggConfig): SchemaConfig => { + const createSchemaConfig = (accessor: number, agg: IAggConfig): SchemaConfig => { if (isDateHistogramBucketAggConfig(agg)) { agg.params.timeRange = timeRange; setBounds(agg, true); @@ -130,10 +130,10 @@ export const getSchemas = (vis: Vis, timeRange?: any): Schemas => { const schemas: Schemas = { metric: [], }; - const responseAggs = vis.aggs.getResponseAggs().filter((agg: AggConfig) => agg.enabled); + const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled); const isHierarchical = vis.isHierarchical(); - const metrics = responseAggs.filter((agg: AggConfig) => agg.type.type === 'metrics'); - responseAggs.forEach((agg: AggConfig) => { + const metrics = responseAggs.filter((agg: IAggConfig) => agg.type.type === 'metrics'); + responseAggs.forEach((agg: IAggConfig) => { let skipMetrics = false; let schemaName = agg.schema ? agg.schema.name || agg.schema : null; if (typeof schemaName === 'object') { @@ -224,7 +224,7 @@ export const prepareDimension = (variable: string, data: any) => { const adjustVislibDimensionFormmaters = (vis: Vis, dimensions: { y: any[] }): void => { const visState = vis.getCurrentState(); const visConfig = visState.params; - const responseAggs = vis.aggs.getResponseAggs().filter((agg: AggConfig) => agg.enabled); + const responseAggs = vis.aggs.getResponseAggs().filter((agg: IAggConfig) => agg.enabled); (dimensions.y || []).forEach(yDimension => { const yAgg = responseAggs[yDimension.accessor]; diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts index 6e6a2174d6ad1..71bf9bcf983ff 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.d.ts @@ -18,7 +18,7 @@ */ import { VisType } from './types'; -import { AggConfigs } from '../../legacy_imports'; +import { IAggConfigs } from '../../legacy_imports'; import { Status } from './legacy/update_status'; export interface Vis { @@ -39,7 +39,7 @@ export interface VisState { title: string; type: VisType; params: VisParams; - aggs: AggConfigs; + aggs: IAggConfigs; } export declare class VisualizationController { diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js index 4f1526c20cb6f..0c2e5012df439 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/vis.js @@ -21,7 +21,7 @@ * @name Vis * * @description This class consists of aggs, params, listeners, title, and type. - * - Aggs: Instances of AggConfig. + * - Aggs: Instances of IAggConfig. * - Params: The settings in the Options tab. * * Not to be confused with vislib/vis.js. diff --git a/src/legacy/ui/public/agg_response/tabify/_get_columns.ts b/src/legacy/ui/public/agg_response/tabify/_get_columns.ts index a3127c039049b..4144d5be16012 100644 --- a/src/legacy/ui/public/agg_response/tabify/_get_columns.ts +++ b/src/legacy/ui/public/agg_response/tabify/_get_columns.ts @@ -18,15 +18,15 @@ */ import { groupBy } from 'lodash'; -import { AggConfig } from '../../agg_types'; +import { IAggConfig } from '../../agg_types'; export interface AggColumn { - aggConfig: AggConfig; + aggConfig: IAggConfig; id: string; name: string; } -const getColumn = (agg: AggConfig, i: number): AggColumn => { +const getColumn = (agg: IAggConfig, i: number): AggColumn => { return { aggConfig: agg, id: `col-${i}-${agg.id}`, @@ -40,7 +40,7 @@ const getColumn = (agg: AggConfig, i: number): AggColumn => { * @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates * @param {boolean} minimalColumns - setting to true will only return a column for the last bucket/metric instead of one for each level */ -export function tabifyGetColumns(aggs: AggConfig[], minimalColumns: boolean) { +export function tabifyGetColumns(aggs: IAggConfig[], minimalColumns: boolean) { // pick the columns if (minimalColumns) { return aggs.map((agg, i) => getColumn(agg, i)); diff --git a/src/legacy/ui/public/agg_types/index.ts b/src/legacy/ui/public/agg_types/index.ts index cf2733b9a9f36..ac5d0bed7ef15 100644 --- a/src/legacy/ui/public/agg_types/index.ts +++ b/src/legacy/ui/public/agg_types/index.ts @@ -17,15 +17,69 @@ * under the License. */ -export { aggTypes } from './agg_types'; -export { AggParam } from './agg_params'; -export { AggConfig } from './agg_config'; -export { AggConfigs } from './agg_configs'; -export { AggGroupNames, aggGroupNamesMap } from './agg_groups'; -export { FieldParamType } from './param_types'; -export { BUCKET_TYPES } from './buckets/bucket_agg_types'; -export { METRIC_TYPES } from './metrics/metric_agg_types'; -export { ISchemas, Schema, Schemas } from './schemas'; -export { AggType } from './agg_type'; -export { setBounds } from './buckets/date_histogram'; -export { termsAggFilter } from './buckets/terms'; +/** + * Nothing to see here! + * + * Agg Types have moved to the data plugin, and are being + * re-exported from ui/agg_types for backwards compatibility. + */ + +import { start as dataStart } from '../../../core_plugins/data/public/legacy'; + +// runtime contracts +export const { + types: aggTypes, + AggConfig, + AggConfigs, + AggType, + aggTypeFieldFilters, + FieldParamType, + MetricAggType, + parentPipelineAggHelper, + siblingPipelineAggHelper, + setBounds, +} = dataStart.search.aggs; + +// types +export { + IAggConfig, + IAggConfigs, + IAggType, + IFieldParamType, + IMetricAggType, + AggParam, + AggParamOption, + BUCKET_TYPES, + DateRangeKey, + IpRangeKey, + ISchemas, + METRIC_TYPES, + OptionedParamEditorProps, + OptionedValueProp, +} from '../../../core_plugins/data/public'; + +// static code +export { + AggParamType, + AggTypeFilters, + aggTypeFilters, + AggTypeFieldFilters, + AggGroupNames, + aggGroupNamesMap, + CidrMask, + convertDateRangeToString, + convertIPRangeToString, + intervalOptions, + isDateHistogramBucketAggConfig, + isStringType, + isType, + isValidInterval, + isValidJson, + OptionedParamType, + parentPipelineType, + propFilter, + Schema, + Schemas, + siblingPipelineType, + termsAggFilter, +} from '../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/time_buckets/time_buckets.js b/src/legacy/ui/public/time_buckets/time_buckets.js index 50a57d866099e..a611de45fa859 100644 --- a/src/legacy/ui/public/time_buckets/time_buckets.js +++ b/src/legacy/ui/public/time_buckets/time_buckets.js @@ -144,7 +144,7 @@ TimeBuckets.prototype.getDuration = function() { * generated. * * Input can be one of the following: - * - Any object from src/legacy/ui/agg_types/buckets/_interval_options.js + * - Any object from src/legacy/ui/agg_types.js * - "auto" * - Pass a valid moment unit * - a moment.duration object. diff --git a/src/legacy/ui/public/vis/__tests__/_agg_config.js b/src/legacy/ui/public/vis/__tests__/_agg_config.js index 2ccbaf6c1645e..7dccf3eec18aa 100644 --- a/src/legacy/ui/public/vis/__tests__/_agg_config.js +++ b/src/legacy/ui/public/vis/__tests__/_agg_config.js @@ -21,8 +21,8 @@ import sinon from 'sinon'; import expect from '@kbn/expect'; import ngMock from 'ng_mock'; import { Vis } from '../../../../core_plugins/visualizations/public'; -import { AggType } from '../../agg_types/agg_type'; -import { AggConfig } from '../../agg_types/agg_config'; +import { AggType, AggConfig } from '../../agg_types'; + import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; describe('AggConfig', function() { diff --git a/src/legacy/ui/public/vis/config/editor_config_providers.test.ts b/src/legacy/ui/public/vis/config/editor_config_providers.test.ts index 9d93930c09ebc..d52c9119dd76a 100644 --- a/src/legacy/ui/public/vis/config/editor_config_providers.test.ts +++ b/src/legacy/ui/public/vis/config/editor_config_providers.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AggConfig } from 'ui/agg_types'; +import { IAggConfig } from 'ui/agg_types'; import { EditorConfigProviderRegistry } from './editor_config_providers'; import { EditorParamConfig, FixedParam, NumericIntervalParam, TimeIntervalParam } from './types'; @@ -48,7 +48,7 @@ describe('EditorConfigProvider', () => { const provider = jest.fn<any, any>(() => ({})); registry.register(provider); expect(provider).not.toHaveBeenCalled(); - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; registry.getConfigForAgg(indexPattern, aggConfig); expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); }); @@ -60,7 +60,7 @@ describe('EditorConfigProvider', () => { registry.register(provider2); expect(provider).not.toHaveBeenCalled(); expect(provider2).not.toHaveBeenCalled(); - const aggConfig = {} as AggConfig; + const aggConfig = {} as IAggConfig; registry.getConfigForAgg(indexPattern, aggConfig); expect(provider).toHaveBeenCalledWith(indexPattern, aggConfig); expect(provider2).toHaveBeenCalledWith(indexPattern, aggConfig); @@ -72,7 +72,7 @@ describe('EditorConfigProvider', () => { } function getOutputConfig(reg: EditorConfigProviderRegistry) { - return reg.getConfigForAgg(indexPattern, {} as AggConfig).singleParam; + return reg.getConfigForAgg(indexPattern, {} as IAggConfig).singleParam; } it('should have hidden true if at least one config was hidden true', () => { diff --git a/src/legacy/ui/public/vis/config/editor_config_providers.ts b/src/legacy/ui/public/vis/config/editor_config_providers.ts index 1e82a3ca2762e..ec82597d5fb19 100644 --- a/src/legacy/ui/public/vis/config/editor_config_providers.ts +++ b/src/legacy/ui/public/vis/config/editor_config_providers.ts @@ -18,7 +18,7 @@ */ import { IndexPattern } from 'src/plugins/data/public'; -import { AggConfig } from 'ui/agg_types'; +import { IAggConfig } from 'ui/agg_types'; import { parseEsInterval } from '../../../../core_plugins/data/public'; import { TimeIntervalParam, @@ -29,7 +29,7 @@ import { } from './types'; import { leastCommonInterval, leastCommonMultiple } from '../lib'; -type EditorConfigProvider = (indexPattern: IndexPattern, aggConfig: AggConfig) => EditorConfig; +type EditorConfigProvider = (indexPattern: IndexPattern, aggConfig: IAggConfig) => EditorConfig; class EditorConfigProviderRegistry { private providers: Set<EditorConfigProvider> = new Set(); @@ -38,7 +38,7 @@ class EditorConfigProviderRegistry { this.providers.add(configProvider); } - public getConfigForAgg(indexPattern: IndexPattern, aggConfig: AggConfig): EditorConfig { + public getConfigForAgg(indexPattern: IndexPattern, aggConfig: IAggConfig): EditorConfig { const configs = Array.from(this.providers).map(provider => provider(indexPattern, aggConfig)); return this.mergeConfigs(configs); } diff --git a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts index e763eb1b90791..d8227343159e6 100644 --- a/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts +++ b/src/legacy/ui/public/visualize/loader/pipeline_helpers/utilities.ts @@ -19,15 +19,15 @@ import { i18n } from '@kbn/i18n'; import { identity } from 'lodash'; -import { AggConfig } from 'ui/agg_types'; +import { IAggConfig } from 'ui/agg_types'; import { npStart } from 'ui/new_platform'; import { SerializedFieldFormat } from 'src/plugins/expressions/public'; import { fieldFormats } from '../../../../../../plugins/data/public'; import { Vis } from '../../../../../core_plugins/visualizations/public'; import { tabifyGetColumns } from '../../../agg_response/tabify/_get_columns'; -import { DateRangeKey, convertDateRangeToString } from '../../../agg_types/buckets/date_range'; -import { IpRangeKey, convertIPRangeToString } from '../../../agg_types/buckets/ip_range'; +import { DateRangeKey, convertDateRangeToString } from '../../../agg_types'; +import { IpRangeKey, convertIPRangeToString } from '../../../agg_types'; interface TermsFieldFormatParams { otherBucketLabel: string; @@ -62,7 +62,7 @@ const getFieldFormat = ( return new DefaultFieldFormat(); }; -export const createFormat = (agg: AggConfig): SerializedFieldFormat => { +export const createFormat = (agg: IAggConfig): SerializedFieldFormat => { const format: SerializedFieldFormat = agg.params.field ? agg.params.field.format.toJSON() : {}; const formats: Record<string, () => SerializedFieldFormat> = { date_range: () => ({ id: 'date_range', params: format }), @@ -106,7 +106,7 @@ export const getFormat: FormatFactory = mapping => { const format = getFieldFormat(id, mapping.params); const gte = '\u2265'; const lt = '\u003c'; - return i18n.translate('common.ui.aggTypes.buckets.ranges.rangesFormatMessage', { + return i18n.translate('common.ui.aggTypes.rangesFormatMessage', { defaultMessage: '{gte} {from} and {lt} {to}', values: { gte, @@ -155,7 +155,7 @@ export const getFormat: FormatFactory = mapping => { } }; -export const getTableAggs = (vis: Vis): AggConfig[] => { +export const getTableAggs = (vis: Vis): IAggConfig[] => { if (!vis.aggs || !vis.aggs.getResponseAggs) { return []; } diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx index da9fdd7927d60..7a6067dd5f23c 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/plugin.test.tsx @@ -14,9 +14,6 @@ import { } from './mocks'; jest.mock('ui/new_platform'); -jest.mock('ui/chrome', () => ({ - getSavedObjectsClient: jest.fn(), -})); // mock away actual dependencies to prevent all of it being loaded jest.mock('../../../../../../src/legacy/core_plugins/interpreter/public/registries', () => {}); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx index 2bd6c7106a952..5be92e31f4934 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.test.tsx @@ -14,33 +14,31 @@ import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { createMockedIndexPattern } from '../../mocks'; import { IndexPatternPrivateState } from '../../types'; -jest.mock('ui/new_platform', () => { +jest.mock(`ui/new_platform`, () => { // Due to the way we are handling shims in the NP migration, we need // to mock core here so that upstream services don't cause these // tests to fail. Ordinarly `jest.mock('ui/new_platform')` would be // sufficient, however we need to mock one of the `uiSettings` return // values for this suite, so we must manually assemble the mock. // Because babel hoists `jest` we must use an inline `require` - // to ensure the core mocks are available (`jest.doMock` doesn't + // to ensure the mocks are available (`jest.doMock` doesn't // work in this case). This mock should be able to be replaced // altogether once Lens has migrated to the new platform. - const { coreMock } = require('src/core/public/mocks'); // eslint-disable-line @typescript-eslint/no-var-requires + const { + createUiNewPlatformMock, + } = require('../../../../../../../../src/legacy/ui/public/new_platform/__mocks__/helpers'); // eslint-disable-line @typescript-eslint/no-var-requires + // This is basically duplicating what would ordinarily happen in + // `ui/new_platform/__mocks__` + const { npSetup, npStart } = createUiNewPlatformMock(); + // Override the core mock provided by `ui/new_platform` + npStart.core.uiSettings.get = (path: string) => { + if (path === 'histogram:maxBars') { + return 10; + } + }; return { - npSetup: { - core: coreMock.createSetup(), - }, - npStart: { - core: { - ...coreMock.createStart(), - uiSettings: { - get: (path: string) => { - if (path === 'histogram:maxBars') { - return 10; - } - }, - }, - }, - }, + npSetup, + npStart, }; }); diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx index dbb6278352f09..ae12be90ddd05 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/operations/definitions/date_histogram.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; // TODO: make this new-platform compatible -import { isValidInterval } from 'ui/agg_types/utils'; +import { isValidInterval } from 'ui/agg_types'; import { EuiForm, diff --git a/x-pack/legacy/plugins/rollup/public/legacy.ts b/x-pack/legacy/plugins/rollup/public/legacy.ts index a2738372ff346..a70f181dc86fb 100644 --- a/x-pack/legacy/plugins/rollup/public/legacy.ts +++ b/x-pack/legacy/plugins/rollup/public/legacy.ts @@ -6,8 +6,8 @@ import { npSetup, npStart } from 'ui/new_platform'; import { editorConfigProviders } from 'ui/vis/config'; -import { aggTypeFilters } from 'ui/agg_types/filter'; -import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; +import { aggTypeFilters } from 'ui/agg_types'; +import { aggTypeFieldFilters } from 'ui/agg_types'; import { addSearchStrategy } from '../../../../../src/plugins/data/public'; import { RollupPlugin } from './plugin'; import { setup as management } from '../../../../../src/legacy/core_plugins/management/public/legacy'; diff --git a/x-pack/legacy/plugins/rollup/public/legacy_imports.ts b/x-pack/legacy/plugins/rollup/public/legacy_imports.ts index 981f97963591e..4fece0fddfa3e 100644 --- a/x-pack/legacy/plugins/rollup/public/legacy_imports.ts +++ b/x-pack/legacy/plugins/rollup/public/legacy_imports.ts @@ -7,6 +7,6 @@ // @ts-ignore export { findIllegalCharactersInIndexName, INDEX_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/indices'; -export { AggTypeFilters } from 'ui/agg_types/filter'; -export { AggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; +export { AggTypeFilters } from 'ui/agg_types'; +export { AggTypeFieldFilters } from 'ui/agg_types'; export { EditorConfigProviderRegistry } from 'ui/vis/config'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 58d97ce263bea..351bc9def3082 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -77,109 +77,110 @@ }, "messages": { "common.ui.aggResponse.allDocsTitle": "すべてのドキュメント", - "common.ui.aggTypes.aggGroups.bucketsText": "バケット", - "common.ui.aggTypes.aggGroups.metricsText": "メトリック", - "common.ui.aggTypes.buckets.dateHistogramLabel": "{intervalDescription}ごとの {fieldName}", - "common.ui.aggTypes.buckets.dateHistogramTitle": "日付ヒストグラム", - "common.ui.aggTypes.buckets.dateRangeTitle": "日付範囲", - "common.ui.aggTypes.buckets.filtersTitle": "フィルター", - "common.ui.aggTypes.buckets.filterTitle": "フィルター", - "common.ui.aggTypes.buckets.geohashGridTitle": "ジオハッシュ", - "common.ui.aggTypes.buckets.geotileGridTitle": "ジオタイル", - "common.ui.aggTypes.buckets.histogramTitle": "ヒストグラム", - "common.ui.aggTypes.buckets.intervalOptions.autoDisplayName": "自動", - "common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName": "日ごと", - "common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName": "1 時間ごと", - "common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName": "ミリ秒", - "common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName": "分", - "common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName": "月ごと", - "common.ui.aggTypes.buckets.intervalOptions.secondDisplayName": "秒", - "common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName": "週ごと", - "common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName": "1 年ごと", - "common.ui.aggTypes.buckets.ipRangeLabel": "{fieldName} IP 範囲", - "common.ui.aggTypes.buckets.ipRangeTitle": "IPv4 範囲", - "common.ui.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} と {lt} {to}", - "common.ui.aggTypes.buckets.rangesLabel": "{fieldName} の範囲", - "common.ui.aggTypes.buckets.rangeTitle": "範囲", - "common.ui.aggTypes.buckets.significantTerms.excludeLabel": "除外", - "common.ui.aggTypes.buckets.significantTerms.includeLabel": "含める", - "common.ui.aggTypes.buckets.significantTermsLabel": "{fieldName} のトップ {size} の珍しいアイテム", - "common.ui.aggTypes.buckets.significantTermsTitle": "重要な用語", - "common.ui.aggTypes.buckets.terms.excludeLabel": "除外", - "common.ui.aggTypes.buckets.terms.includeLabel": "含める", - "common.ui.aggTypes.buckets.terms.missingBucketLabel": "欠測値", - "common.ui.aggTypes.buckets.terms.orderAscendingTitle": "昇順", - "common.ui.aggTypes.buckets.terms.orderDescendingTitle": "降順", - "common.ui.aggTypes.buckets.terms.otherBucketDescription": "このリクエストは、データバケットの基準外のドキュメントの数をカウントします。", - "common.ui.aggTypes.buckets.terms.otherBucketLabel": "その他", - "common.ui.aggTypes.buckets.terms.otherBucketTitle": "他のバケット", - "common.ui.aggTypes.buckets.termsTitle": "用語", - "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", - "common.ui.aggTypes.metrics.averageBucketTitle": "平均バケット", - "common.ui.aggTypes.metrics.averageLabel": "平均 {field}", - "common.ui.aggTypes.metrics.averageTitle": "平均", - "common.ui.aggTypes.metrics.bucketAggTitle": "バケット集約", - "common.ui.aggTypes.metrics.countLabel": "カウント", - "common.ui.aggTypes.metrics.countTitle": "カウント", - "common.ui.aggTypes.metrics.cumulativeSumLabel": "累積合計", - "common.ui.aggTypes.metrics.cumulativeSumTitle": "累積合計", - "common.ui.aggTypes.metrics.derivativeLabel": "派生", - "common.ui.aggTypes.metrics.derivativeTitle": "派生", - "common.ui.aggTypes.metrics.geoBoundsLabel": "境界", - "common.ui.aggTypes.metrics.geoBoundsTitle": "境界", - "common.ui.aggTypes.metrics.geoCentroidLabel": "ジオセントロイド", - "common.ui.aggTypes.metrics.geoCentroidTitle": "ジオセントロイド", - "common.ui.aggTypes.metrics.maxBucketTitle": "最高バケット", - "common.ui.aggTypes.metrics.maxLabel": "最高 {field}", - "common.ui.aggTypes.metrics.maxTitle": "最高", - "common.ui.aggTypes.metrics.medianLabel": "中央 {field}", - "common.ui.aggTypes.metrics.medianTitle": "中央", - "common.ui.aggTypes.metrics.metricAggregationsSubtypeTitle": "メトリック集約", - "common.ui.aggTypes.metrics.metricAggTitle": "メトリック集約", - "common.ui.aggTypes.metrics.minBucketTitle": "最低バケット", - "common.ui.aggTypes.metrics.minLabel": "最低 {field}", - "common.ui.aggTypes.metrics.minTitle": "最低", - "common.ui.aggTypes.metrics.movingAvgLabel": "移動平均", - "common.ui.aggTypes.metrics.movingAvgTitle": "移動平均", - "common.ui.aggTypes.metrics.overallAverageLabel": "全体平均", - "common.ui.aggTypes.metrics.overallMaxLabel": "全体最高", - "common.ui.aggTypes.metrics.overallMinLabel": "全体最低", - "common.ui.aggTypes.metrics.overallSumLabel": "全体合計", - "common.ui.aggTypes.metrics.parentPipelineAggregationsSubtypeTitle": "親パイプライン集約", - "common.ui.aggTypes.metrics.percentileRanks.valuePropsLabel": "「{label}」の {format} のパーセンタイル順位", - "common.ui.aggTypes.metrics.percentileRanksLabel": "{field} のパーセンタイル順位", - "common.ui.aggTypes.metrics.percentileRanksTitle": "パーセンタイル順位", - "common.ui.aggTypes.metrics.percentiles.valuePropsLabel": "{label} の {percentile} パーセンタイル", - "common.ui.aggTypes.metrics.percentilesLabel": "{field} のパーセンタイル", - "common.ui.aggTypes.metrics.percentilesTitle": "パーセンタイル", - "common.ui.aggTypes.metrics.serialDiffLabel": "差分の推移", - "common.ui.aggTypes.metrics.serialDiffTitle": "差分の推移", - "common.ui.aggTypes.metrics.siblingPipelineAggregationsSubtypeTitle": "シブリングパイプラインアグリゲーション", - "common.ui.aggTypes.metrics.standardDeviation.keyDetailsLabel": "{fieldDisplayName} の標準偏差", - "common.ui.aggTypes.metrics.standardDeviation.lowerKeyDetailsTitle": "下の{label}", - "common.ui.aggTypes.metrics.standardDeviation.upperKeyDetailsTitle": "上の{label}", - "common.ui.aggTypes.metrics.standardDeviationLabel": "{field} の標準偏差", - "common.ui.aggTypes.metrics.standardDeviationTitle": "標準偏差", - "common.ui.aggTypes.metrics.sumBucketTitle": "合計バケット", - "common.ui.aggTypes.metrics.sumLabel": "{field} の合計", - "common.ui.aggTypes.metrics.sumTitle": "合計", - "common.ui.aggTypes.metrics.topHit.ascendingLabel": "昇順", - "common.ui.aggTypes.metrics.topHit.averageLabel": "平均", - "common.ui.aggTypes.metrics.topHit.concatenateLabel": "連結", - "common.ui.aggTypes.metrics.topHit.descendingLabel": "降順", - "common.ui.aggTypes.metrics.topHit.firstPrefixLabel": "最初", - "common.ui.aggTypes.metrics.topHit.lastPrefixLabel": "最後", - "common.ui.aggTypes.metrics.topHit.maxLabel": "最高", - "common.ui.aggTypes.metrics.topHit.minLabel": "最低", - "common.ui.aggTypes.metrics.topHit.sumLabel": "合計", - "common.ui.aggTypes.metrics.topHitTitle": "トップヒット", - "common.ui.aggTypes.metrics.uniqueCountLabel": "{field} のユニークカウント", - "common.ui.aggTypes.metrics.uniqueCountTitle": "ユニークカウント", - "common.ui.aggTypes.otherBucket.labelForMissingValuesLabel": "欠測値のラベル", - "common.ui.aggTypes.otherBucket.labelForOtherBucketLabel": "他のバケットのラベル", - "common.ui.aggTypes.paramTypes.field.invalidSavedFieldParameterErrorMessage": "保存された {fieldParameter} パラメーターが無効になりました。新しいフィールドを選択してください。", - "common.ui.aggTypes.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} は必須パラメーターです", - "common.ui.aggTypes.string.customLabel": "カスタムラベル", + "common.ui.aggTypes.rangesFormatMessage": "{gte} {from} と {lt} {to}", + "data.search.aggs.aggGroups.bucketsText": "バケット", + "data.search.aggs.aggGroups.metricsText": "メトリック", + "data.search.aggs.buckets.dateHistogramLabel": "{intervalDescription}ごとの {fieldName}", + "data.search.aggs.buckets.dateHistogramTitle": "日付ヒストグラム", + "data.search.aggs.buckets.dateRangeTitle": "日付範囲", + "data.search.aggs.buckets.filtersTitle": "フィルター", + "data.search.aggs.buckets.filterTitle": "フィルター", + "data.search.aggs.buckets.geohashGridTitle": "ジオハッシュ", + "data.search.aggs.buckets.geotileGridTitle": "ジオタイル", + "data.search.aggs.buckets.histogramTitle": "ヒストグラム", + "data.search.aggs.buckets.intervalOptions.autoDisplayName": "自動", + "data.search.aggs.buckets.intervalOptions.dailyDisplayName": "日ごと", + "data.search.aggs.buckets.intervalOptions.hourlyDisplayName": "1 時間ごと", + "data.search.aggs.buckets.intervalOptions.millisecondDisplayName": "ミリ秒", + "data.search.aggs.buckets.intervalOptions.minuteDisplayName": "分", + "data.search.aggs.buckets.intervalOptions.monthlyDisplayName": "月ごと", + "data.search.aggs.buckets.intervalOptions.secondDisplayName": "秒", + "data.search.aggs.buckets.intervalOptions.weeklyDisplayName": "週ごと", + "data.search.aggs.buckets.intervalOptions.yearlyDisplayName": "1 年ごと", + "data.search.aggs.buckets.ipRangeLabel": "{fieldName} IP 範囲", + "data.search.aggs.buckets.ipRangeTitle": "IPv4 範囲", + "data.search.aggs.aggTypes.rangesFormatMessage": "{gte} {from} と {lt} {to}", + "data.search.aggs.aggTypesLabel": "{fieldName} の範囲", + "data.search.aggs.buckets.rangeTitle": "範囲", + "data.search.aggs.buckets.significantTerms.excludeLabel": "除外", + "data.search.aggs.buckets.significantTerms.includeLabel": "含める", + "data.search.aggs.buckets.significantTermsLabel": "{fieldName} のトップ {size} の珍しいアイテム", + "data.search.aggs.buckets.significantTermsTitle": "重要な用語", + "data.search.aggs.buckets.terms.excludeLabel": "除外", + "data.search.aggs.buckets.terms.includeLabel": "含める", + "data.search.aggs.buckets.terms.missingBucketLabel": "欠測値", + "data.search.aggs.buckets.terms.orderAscendingTitle": "昇順", + "data.search.aggs.buckets.terms.orderDescendingTitle": "降順", + "data.search.aggs.buckets.terms.otherBucketDescription": "このリクエストは、データバケットの基準外のドキュメントの数をカウントします。", + "data.search.aggs.buckets.terms.otherBucketLabel": "その他", + "data.search.aggs.buckets.terms.otherBucketTitle": "他のバケット", + "data.search.aggs.buckets.termsTitle": "用語", + "data.search.aggs.histogram.missingMaxMinValuesWarning": "自動スケールヒストグラムバケットから最高値と最低値を取得できません。これによりビジュアライゼーションのパフォーマンスが低下する可能性があります。", + "data.search.aggs.metrics.averageBucketTitle": "平均バケット", + "data.search.aggs.metrics.averageLabel": "平均 {field}", + "data.search.aggs.metrics.averageTitle": "平均", + "data.search.aggs.metrics.bucketAggTitle": "バケット集約", + "data.search.aggs.metrics.countLabel": "カウント", + "data.search.aggs.metrics.countTitle": "カウント", + "data.search.aggs.metrics.cumulativeSumLabel": "累積合計", + "data.search.aggs.metrics.cumulativeSumTitle": "累積合計", + "data.search.aggs.metrics.derivativeLabel": "派生", + "data.search.aggs.metrics.derivativeTitle": "派生", + "data.search.aggs.metrics.geoBoundsLabel": "境界", + "data.search.aggs.metrics.geoBoundsTitle": "境界", + "data.search.aggs.metrics.geoCentroidLabel": "ジオセントロイド", + "data.search.aggs.metrics.geoCentroidTitle": "ジオセントロイド", + "data.search.aggs.metrics.maxBucketTitle": "最高バケット", + "data.search.aggs.metrics.maxLabel": "最高 {field}", + "data.search.aggs.metrics.maxTitle": "最高", + "data.search.aggs.metrics.medianLabel": "中央 {field}", + "data.search.aggs.metrics.medianTitle": "中央", + "data.search.aggs.metrics.metricAggregationsSubtypeTitle": "メトリック集約", + "data.search.aggs.metrics.metricAggTitle": "メトリック集約", + "data.search.aggs.metrics.minBucketTitle": "最低バケット", + "data.search.aggs.metrics.minLabel": "最低 {field}", + "data.search.aggs.metrics.minTitle": "最低", + "data.search.aggs.metrics.movingAvgLabel": "移動平均", + "data.search.aggs.metrics.movingAvgTitle": "移動平均", + "data.search.aggs.metrics.overallAverageLabel": "全体平均", + "data.search.aggs.metrics.overallMaxLabel": "全体最高", + "data.search.aggs.metrics.overallMinLabel": "全体最低", + "data.search.aggs.metrics.overallSumLabel": "全体合計", + "data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle": "親パイプライン集約", + "data.search.aggs.metrics.percentileRanks.valuePropsLabel": "「{label}」の {format} のパーセンタイル順位", + "data.search.aggs.metrics.percentileRanksLabel": "{field} のパーセンタイル順位", + "data.search.aggs.metrics.percentileRanksTitle": "パーセンタイル順位", + "data.search.aggs.metrics.percentiles.valuePropsLabel": "{label} の {percentile} パーセンタイル", + "data.search.aggs.metrics.percentilesLabel": "{field} のパーセンタイル", + "data.search.aggs.metrics.percentilesTitle": "パーセンタイル", + "data.search.aggs.metrics.serialDiffLabel": "差分の推移", + "data.search.aggs.metrics.serialDiffTitle": "差分の推移", + "data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle": "シブリングパイプラインアグリゲーション", + "data.search.aggs.metrics.standardDeviation.keyDetailsLabel": "{fieldDisplayName} の標準偏差", + "data.search.aggs.metrics.standardDeviation.lowerKeyDetailsTitle": "下の{label}", + "data.search.aggs.metrics.standardDeviation.upperKeyDetailsTitle": "上の{label}", + "data.search.aggs.metrics.standardDeviationLabel": "{field} の標準偏差", + "data.search.aggs.metrics.standardDeviationTitle": "標準偏差", + "data.search.aggs.metrics.sumBucketTitle": "合計バケット", + "data.search.aggs.metrics.sumLabel": "{field} の合計", + "data.search.aggs.metrics.sumTitle": "合計", + "data.search.aggs.metrics.topHit.ascendingLabel": "昇順", + "data.search.aggs.metrics.topHit.averageLabel": "平均", + "data.search.aggs.metrics.topHit.concatenateLabel": "連結", + "data.search.aggs.metrics.topHit.descendingLabel": "降順", + "data.search.aggs.metrics.topHit.firstPrefixLabel": "最初", + "data.search.aggs.metrics.topHit.lastPrefixLabel": "最後", + "data.search.aggs.metrics.topHit.maxLabel": "最高", + "data.search.aggs.metrics.topHit.minLabel": "最低", + "data.search.aggs.metrics.topHit.sumLabel": "合計", + "data.search.aggs.metrics.topHitTitle": "トップヒット", + "data.search.aggs.metrics.uniqueCountLabel": "{field} のユニークカウント", + "data.search.aggs.metrics.uniqueCountTitle": "ユニークカウント", + "data.search.aggs.otherBucket.labelForMissingValuesLabel": "欠測値のラベル", + "data.search.aggs.otherBucket.labelForOtherBucketLabel": "他のバケットのラベル", + "data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage": "保存された {fieldParameter} パラメーターが無効になりました。新しいフィールドを選択してください。", + "data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} は必須パラメーターです", + "data.search.aggs.string.customLabel": "カスタムラベル", "common.ui.chrome.bigUrlWarningNotificationMessage": "{advancedSettingsLink}で{storeInSessionStorageParam}オプションを有効にするか、オンスクリーンビジュアルを簡素化してください。", "common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高度な設定", "common.ui.chrome.bigUrlWarningNotificationTitle": "URLが大きく、Kibanaの動作が停止する可能性があります", @@ -375,7 +376,7 @@ "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", "common.ui.url.replacementFailedErrorMessage": "置換に失敗、未解決の表現式: {expr}", "common.ui.url.savedObjectIsMissingNotificationMessage": "保存されたオブジェクトがありません", - "common.ui.vis.aggConfig.percentageOfLabel": "{label} のパーセンテージ", + "data.search.aggs.percentageOfLabel": "{label} のパーセンテージ", "common.ui.vis.defaultFeedbackMessage": "フィードバックがありますか?{link} で問題を報告してください。", "common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "データバウンドを合わせる", "common.ui.vis.kibanaMap.zoomWarning": "ズームレベルが最大に達しました。完全にズームインするには、Elasticsearch と Kibana の {defaultDistribution} にアップグレードしてください。{ems} でより多くのズームレベルが利用できます。または、独自のマップサーバーを構成できます。詳細は { wms } または { configSettings} をご覧ください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 5d62e15be2b9f..066bafd990fe3 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -77,109 +77,110 @@ }, "messages": { "common.ui.aggResponse.allDocsTitle": "所有文档", - "common.ui.aggTypes.aggGroups.bucketsText": "存储桶", - "common.ui.aggTypes.aggGroups.metricsText": "指标", - "common.ui.aggTypes.buckets.dateHistogramLabel": "{fieldName}/{intervalDescription}", - "common.ui.aggTypes.buckets.dateHistogramTitle": "Date Histogram", - "common.ui.aggTypes.buckets.dateRangeTitle": "日期范围", - "common.ui.aggTypes.buckets.filtersTitle": "筛选", - "common.ui.aggTypes.buckets.filterTitle": "筛选", - "common.ui.aggTypes.buckets.geohashGridTitle": "Geohash", - "common.ui.aggTypes.buckets.geotileGridTitle": "地理磁贴", - "common.ui.aggTypes.buckets.histogramTitle": "Histogram", - "common.ui.aggTypes.buckets.intervalOptions.autoDisplayName": "自动", - "common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName": "每日", - "common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName": "每小时", - "common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName": "毫秒", - "common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName": "分钟", - "common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName": "每月", - "common.ui.aggTypes.buckets.intervalOptions.secondDisplayName": "秒", - "common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName": "每周", - "common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName": "每年", - "common.ui.aggTypes.buckets.ipRangeLabel": "{fieldName} IP 范围", - "common.ui.aggTypes.buckets.ipRangeTitle": "IPv4 范围", - "common.ui.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} 且 {lt} {to}", - "common.ui.aggTypes.buckets.rangesLabel": "{fieldName} 范围", - "common.ui.aggTypes.buckets.rangeTitle": "范围", - "common.ui.aggTypes.buckets.significantTerms.excludeLabel": "排除", - "common.ui.aggTypes.buckets.significantTerms.includeLabel": "包括", - "common.ui.aggTypes.buckets.significantTermsLabel": "{fieldName} 中排名前 {size} 的罕见词", - "common.ui.aggTypes.buckets.significantTermsTitle": "重要词", - "common.ui.aggTypes.buckets.terms.excludeLabel": "排除", - "common.ui.aggTypes.buckets.terms.includeLabel": "包括", - "common.ui.aggTypes.buckets.terms.missingBucketLabel": "缺失", - "common.ui.aggTypes.buckets.terms.orderAscendingTitle": "升序", - "common.ui.aggTypes.buckets.terms.orderDescendingTitle": "降序", - "common.ui.aggTypes.buckets.terms.otherBucketDescription": "此请求计数不符合数据存储桶条件的文档数目。", - "common.ui.aggTypes.buckets.terms.otherBucketLabel": "其他", - "common.ui.aggTypes.buckets.terms.otherBucketTitle": "其他存储桶", - "common.ui.aggTypes.buckets.termsTitle": "词", - "common.ui.aggTypes.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。", - "common.ui.aggTypes.metrics.averageBucketTitle": "平均存储桶", - "common.ui.aggTypes.metrics.averageLabel": "{field}平均值", - "common.ui.aggTypes.metrics.averageTitle": "平均值", - "common.ui.aggTypes.metrics.bucketAggTitle": "存储桶聚合", - "common.ui.aggTypes.metrics.countLabel": "计数", - "common.ui.aggTypes.metrics.countTitle": "计数", - "common.ui.aggTypes.metrics.cumulativeSumLabel": "累计和", - "common.ui.aggTypes.metrics.cumulativeSumTitle": "累计和", - "common.ui.aggTypes.metrics.derivativeLabel": "导数", - "common.ui.aggTypes.metrics.derivativeTitle": "导数", - "common.ui.aggTypes.metrics.geoBoundsLabel": "地理边界", - "common.ui.aggTypes.metrics.geoBoundsTitle": "地理边界", - "common.ui.aggTypes.metrics.geoCentroidLabel": "地理重心", - "common.ui.aggTypes.metrics.geoCentroidTitle": "地理重心", - "common.ui.aggTypes.metrics.maxBucketTitle": "最大存储桶", - "common.ui.aggTypes.metrics.maxLabel": "{field}最大值", - "common.ui.aggTypes.metrics.maxTitle": "最大值", - "common.ui.aggTypes.metrics.medianLabel": "{field}中值", - "common.ui.aggTypes.metrics.medianTitle": "中值", - "common.ui.aggTypes.metrics.metricAggregationsSubtypeTitle": "指标聚合", - "common.ui.aggTypes.metrics.metricAggTitle": "指标聚合", - "common.ui.aggTypes.metrics.minBucketTitle": "最小存储桶", - "common.ui.aggTypes.metrics.minLabel": "{field}最小值", - "common.ui.aggTypes.metrics.minTitle": "最小值", - "common.ui.aggTypes.metrics.movingAvgLabel": "移动平均值", - "common.ui.aggTypes.metrics.movingAvgTitle": "移动平均值", - "common.ui.aggTypes.metrics.overallAverageLabel": "总体平均值", - "common.ui.aggTypes.metrics.overallMaxLabel": "总体最大值", - "common.ui.aggTypes.metrics.overallMinLabel": "总体最大值", - "common.ui.aggTypes.metrics.overallSumLabel": "总和", - "common.ui.aggTypes.metrics.parentPipelineAggregationsSubtypeTitle": "父级管道聚合", - "common.ui.aggTypes.metrics.percentileRanks.valuePropsLabel": "“{label}” 的百分位数排名 {format}", - "common.ui.aggTypes.metrics.percentileRanksLabel": "“{field}” 的百分位数排名", - "common.ui.aggTypes.metrics.percentileRanksTitle": "百分位数排名", - "common.ui.aggTypes.metrics.percentiles.valuePropsLabel": "“{label}” 的 {percentile} 百分位数", - "common.ui.aggTypes.metrics.percentilesLabel": "“{field}” 的百分位数", - "common.ui.aggTypes.metrics.percentilesTitle": "百分位数", - "common.ui.aggTypes.metrics.serialDiffLabel": "序列差异", - "common.ui.aggTypes.metrics.serialDiffTitle": "序列差异", - "common.ui.aggTypes.metrics.siblingPipelineAggregationsSubtypeTitle": "同级管道聚合", - "common.ui.aggTypes.metrics.standardDeviation.keyDetailsLabel": "“{fieldDisplayName}” 的标准偏差", - "common.ui.aggTypes.metrics.standardDeviation.lowerKeyDetailsTitle": "下{label}", - "common.ui.aggTypes.metrics.standardDeviation.upperKeyDetailsTitle": "上{label}", - "common.ui.aggTypes.metrics.standardDeviationLabel": "“{field}” 的标准偏差", - "common.ui.aggTypes.metrics.standardDeviationTitle": "标准偏差", - "common.ui.aggTypes.metrics.sumBucketTitle": "求和存储桶", - "common.ui.aggTypes.metrics.sumLabel": "“{field}” 的和", - "common.ui.aggTypes.metrics.sumTitle": "和", - "common.ui.aggTypes.metrics.topHit.ascendingLabel": "升序", - "common.ui.aggTypes.metrics.topHit.averageLabel": "平均值", - "common.ui.aggTypes.metrics.topHit.concatenateLabel": "连接", - "common.ui.aggTypes.metrics.topHit.descendingLabel": "降序", - "common.ui.aggTypes.metrics.topHit.firstPrefixLabel": "第一", - "common.ui.aggTypes.metrics.topHit.lastPrefixLabel": "最后", - "common.ui.aggTypes.metrics.topHit.maxLabel": "最大值", - "common.ui.aggTypes.metrics.topHit.minLabel": "最小值", - "common.ui.aggTypes.metrics.topHit.sumLabel": "和", - "common.ui.aggTypes.metrics.topHitTitle": "最高命中结果", - "common.ui.aggTypes.metrics.uniqueCountLabel": "“{field}” 的唯一计数", - "common.ui.aggTypes.metrics.uniqueCountTitle": "唯一计数", - "common.ui.aggTypes.otherBucket.labelForMissingValuesLabel": "缺失值的标签", - "common.ui.aggTypes.otherBucket.labelForOtherBucketLabel": "其他存储桶的标签", - "common.ui.aggTypes.paramTypes.field.invalidSavedFieldParameterErrorMessage": "已保存的 {fieldParameter} 参数现在无效。请选择新字段。", - "common.ui.aggTypes.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} 是必需字段", - "common.ui.aggTypes.string.customLabel": "定制标签", + "common.ui.aggTypes.rangesFormatMessage": "{gte} {from} 且 {lt} {to}", + "data.search.aggs.aggGroups.bucketsText": "存储桶", + "data.search.aggs.aggGroups.metricsText": "指标", + "data.search.aggs.buckets.dateHistogramLabel": "{fieldName}/{intervalDescription}", + "data.search.aggs.buckets.dateHistogramTitle": "Date Histogram", + "data.search.aggs.buckets.dateRangeTitle": "日期范围", + "data.search.aggs.buckets.filtersTitle": "筛选", + "data.search.aggs.buckets.filterTitle": "筛选", + "data.search.aggs.buckets.geohashGridTitle": "Geohash", + "data.search.aggs.buckets.geotileGridTitle": "地理磁贴", + "data.search.aggs.buckets.histogramTitle": "Histogram", + "data.search.aggs.buckets.intervalOptions.autoDisplayName": "自动", + "data.search.aggs.buckets.intervalOptions.dailyDisplayName": "每日", + "data.search.aggs.buckets.intervalOptions.hourlyDisplayName": "每小时", + "data.search.aggs.buckets.intervalOptions.millisecondDisplayName": "毫秒", + "data.search.aggs.buckets.intervalOptions.minuteDisplayName": "分钟", + "data.search.aggs.buckets.intervalOptions.monthlyDisplayName": "每月", + "data.search.aggs.buckets.intervalOptions.secondDisplayName": "秒", + "data.search.aggs.buckets.intervalOptions.weeklyDisplayName": "每周", + "data.search.aggs.buckets.intervalOptions.yearlyDisplayName": "每年", + "data.search.aggs.buckets.ipRangeLabel": "{fieldName} IP 范围", + "data.search.aggs.buckets.ipRangeTitle": "IPv4 范围", + "data.search.aggs.aggTypes.rangesFormatMessage": "{gte} {from} 且 {lt} {to}", + "data.search.aggs.aggTypesLabel": "{fieldName} 范围", + "data.search.aggs.buckets.rangeTitle": "范围", + "data.search.aggs.buckets.significantTerms.excludeLabel": "排除", + "data.search.aggs.buckets.significantTerms.includeLabel": "包括", + "data.search.aggs.buckets.significantTermsLabel": "{fieldName} 中排名前 {size} 的罕见词", + "data.search.aggs.buckets.significantTermsTitle": "重要词", + "data.search.aggs.buckets.terms.excludeLabel": "排除", + "data.search.aggs.buckets.terms.includeLabel": "包括", + "data.search.aggs.buckets.terms.missingBucketLabel": "缺失", + "data.search.aggs.buckets.terms.orderAscendingTitle": "升序", + "data.search.aggs.buckets.terms.orderDescendingTitle": "降序", + "data.search.aggs.buckets.terms.otherBucketDescription": "此请求计数不符合数据存储桶条件的文档数目。", + "data.search.aggs.buckets.terms.otherBucketLabel": "其他", + "data.search.aggs.buckets.terms.otherBucketTitle": "其他存储桶", + "data.search.aggs.buckets.termsTitle": "词", + "data.search.aggs.histogram.missingMaxMinValuesWarning": "无法检索最大值和最小值以自动缩放直方图存储桶。这可能会导致可视化性能低下。", + "data.search.aggs.metrics.averageBucketTitle": "平均存储桶", + "data.search.aggs.metrics.averageLabel": "{field}平均值", + "data.search.aggs.metrics.averageTitle": "平均值", + "data.search.aggs.metrics.bucketAggTitle": "存储桶聚合", + "data.search.aggs.metrics.countLabel": "计数", + "data.search.aggs.metrics.countTitle": "计数", + "data.search.aggs.metrics.cumulativeSumLabel": "累计和", + "data.search.aggs.metrics.cumulativeSumTitle": "累计和", + "data.search.aggs.metrics.derivativeLabel": "导数", + "data.search.aggs.metrics.derivativeTitle": "导数", + "data.search.aggs.metrics.geoBoundsLabel": "地理边界", + "data.search.aggs.metrics.geoBoundsTitle": "地理边界", + "data.search.aggs.metrics.geoCentroidLabel": "地理重心", + "data.search.aggs.metrics.geoCentroidTitle": "地理重心", + "data.search.aggs.metrics.maxBucketTitle": "最大存储桶", + "data.search.aggs.metrics.maxLabel": "{field}最大值", + "data.search.aggs.metrics.maxTitle": "最大值", + "data.search.aggs.metrics.medianLabel": "{field}中值", + "data.search.aggs.metrics.medianTitle": "中值", + "data.search.aggs.metrics.metricAggregationsSubtypeTitle": "指标聚合", + "data.search.aggs.metrics.metricAggTitle": "指标聚合", + "data.search.aggs.metrics.minBucketTitle": "最小存储桶", + "data.search.aggs.metrics.minLabel": "{field}最小值", + "data.search.aggs.metrics.minTitle": "最小值", + "data.search.aggs.metrics.movingAvgLabel": "移动平均值", + "data.search.aggs.metrics.movingAvgTitle": "移动平均值", + "data.search.aggs.metrics.overallAverageLabel": "总体平均值", + "data.search.aggs.metrics.overallMaxLabel": "总体最大值", + "data.search.aggs.metrics.overallMinLabel": "总体最大值", + "data.search.aggs.metrics.overallSumLabel": "总和", + "data.search.aggs.metrics.parentPipelineAggregationsSubtypeTitle": "父级管道聚合", + "data.search.aggs.metrics.percentileRanks.valuePropsLabel": "“{label}” 的百分位数排名 {format}", + "data.search.aggs.metrics.percentileRanksLabel": "“{field}” 的百分位数排名", + "data.search.aggs.metrics.percentileRanksTitle": "百分位数排名", + "data.search.aggs.metrics.percentiles.valuePropsLabel": "“{label}” 的 {percentile} 百分位数", + "data.search.aggs.metrics.percentilesLabel": "“{field}” 的百分位数", + "data.search.aggs.metrics.percentilesTitle": "百分位数", + "data.search.aggs.metrics.serialDiffLabel": "序列差异", + "data.search.aggs.metrics.serialDiffTitle": "序列差异", + "data.search.aggs.metrics.siblingPipelineAggregationsSubtypeTitle": "同级管道聚合", + "data.search.aggs.metrics.standardDeviation.keyDetailsLabel": "“{fieldDisplayName}” 的标准偏差", + "data.search.aggs.metrics.standardDeviation.lowerKeyDetailsTitle": "下{label}", + "data.search.aggs.metrics.standardDeviation.upperKeyDetailsTitle": "上{label}", + "data.search.aggs.metrics.standardDeviationLabel": "“{field}” 的标准偏差", + "data.search.aggs.metrics.standardDeviationTitle": "标准偏差", + "data.search.aggs.metrics.sumBucketTitle": "求和存储桶", + "data.search.aggs.metrics.sumLabel": "“{field}” 的和", + "data.search.aggs.metrics.sumTitle": "和", + "data.search.aggs.metrics.topHit.ascendingLabel": "升序", + "data.search.aggs.metrics.topHit.averageLabel": "平均值", + "data.search.aggs.metrics.topHit.concatenateLabel": "连接", + "data.search.aggs.metrics.topHit.descendingLabel": "降序", + "data.search.aggs.metrics.topHit.firstPrefixLabel": "第一", + "data.search.aggs.metrics.topHit.lastPrefixLabel": "最后", + "data.search.aggs.metrics.topHit.maxLabel": "最大值", + "data.search.aggs.metrics.topHit.minLabel": "最小值", + "data.search.aggs.metrics.topHit.sumLabel": "和", + "data.search.aggs.metrics.topHitTitle": "最高命中结果", + "data.search.aggs.metrics.uniqueCountLabel": "“{field}” 的唯一计数", + "data.search.aggs.metrics.uniqueCountTitle": "唯一计数", + "data.search.aggs.otherBucket.labelForMissingValuesLabel": "缺失值的标签", + "data.search.aggs.otherBucket.labelForOtherBucketLabel": "其他存储桶的标签", + "data.search.aggs.paramTypes.field.invalidSavedFieldParameterErrorMessage": "已保存的 {fieldParameter} 参数现在无效。请选择新字段。", + "data.search.aggs.paramTypes.field.requiredFieldParameterErrorMessage": "{fieldParameter} 是必需字段", + "data.search.aggs.string.customLabel": "定制标签", "common.ui.chrome.bigUrlWarningNotificationMessage": "在{advancedSettingsLink}中启用“{storeInSessionStorageParam}”选项或简化屏幕视觉效果。", "common.ui.chrome.bigUrlWarningNotificationMessage.advancedSettingsLinkText": "高级设置", "common.ui.chrome.bigUrlWarningNotificationTitle": "URL 过长,Kibana 可能无法工作", @@ -375,7 +376,7 @@ "common.ui.stateManagement.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", "common.ui.url.replacementFailedErrorMessage": "替换失败,未解析的表达式:{expr}", "common.ui.url.savedObjectIsMissingNotificationMessage": "已保存对象缺失", - "common.ui.vis.aggConfig.percentageOfLabel": "{label} 的百分比", + "data.search.aggs.percentageOfLabel": "{label} 的百分比", "common.ui.vis.defaultFeedbackMessage": "想反馈?请在“{link}中创建问题。", "common.ui.vis.kibanaMap.leaflet.fitDataBoundsAriaLabel": "适应数据边界", "common.ui.vis.kibanaMap.zoomWarning": "已达到缩放级别最大数目。要一直放大,请升级到 Elasticsearch 和 Kibana 的 {defaultDistribution}。您可以通过 {ems} 免费使用其他缩放级别。或者,您可以配置自己的地图服务器。请前往 { wms } 或 { configSettings} 以获取详细信息。", diff --git a/x-pack/plugins/watcher/public/legacy/time_buckets.js b/x-pack/plugins/watcher/public/legacy/time_buckets.js index 8b7e4be784fe7..5d5e9e8dcb1a4 100644 --- a/x-pack/plugins/watcher/public/legacy/time_buckets.js +++ b/x-pack/plugins/watcher/public/legacy/time_buckets.js @@ -130,7 +130,7 @@ TimeBuckets.prototype.getDuration = function() { * generated. * * Input can be one of the following: - * - Any object from src/legacy/ui/agg_types/buckets/_interval_options.js + * - Any object from src/plugins/data/public/search/aggs/buckets/_interval_options.ts * - "auto" * - Pass a valid moment unit * - a moment.duration object. From 8a90e6748969569548250bda4b91f5c6724e1620 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad <frank.hassanabad@elastic.co> Date: Tue, 4 Feb 2020 18:10:05 -0700 Subject: [PATCH 57/60] [SIEM][Detection Engine] critical blocker, wrong ilm policy, need to match beats ilm policy ## Summary Need to match ILM policy of auditbeat and we almost forgot this and it would be bad for shipping if we did not have best practices for ILM. ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ ~~- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~~ ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ ~~- [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. - [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) - [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) --- .../lib/detection_engine/routes/index/signals_policy.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_policy.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_policy.json index 640d8e14190cd..4fc7eebd4373c 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_policy.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_policy.json @@ -5,8 +5,8 @@ "min_age": "0ms", "actions": { "rollover": { - "max_size": "10gb", - "max_age": "7d" + "max_size": "50gb", + "max_age": "30d" } } } From fac68730548685d87364db696ae6c3e16fc94f43 Mon Sep 17 00:00:00 2001 From: Frank Hassanabad <frank.hassanabad@elastic.co> Date: Tue, 4 Feb 2020 18:19:36 -0700 Subject: [PATCH 58/60] [SIEM][Detection Engine] Final final rule changes (#56806) ## Summary * Final, final, Rule changes ### Checklist Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. ~~- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)~~ ~~- [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)~~ ~~- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials~~ ~~- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios~~ ~~- [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~~ ### For maintainers ~~- [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ ~~- [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~~ --- NOTICE.txt | 2 - .../403_response_to_a_post.json | 6 +- .../405_response_method_not_allowed.json | 6 +- ..._security_adversary_behavior_detected.json | 6 +- ...dpoint_security_cred_dumping_detected.json | 8 +- ...point_security_cred_dumping_prevented.json | 8 +- ...t_security_cred_manipulation_detected.json | 8 +- ..._security_cred_manipulation_prevented.json | 11 +- ...ic_endpoint_security_exploit_detected.json | 6 +- ...c_endpoint_security_exploit_prevented.json | 6 +- ...ic_endpoint_security_malware_detected.json | 6 +- ...c_endpoint_security_malware_prevented.json | 6 +- ...nt_security_permission_theft_detected.json | 6 +- ...t_security_permission_theft_prevented.json | 6 +- ...t_security_process_injection_detected.json | 6 +- ..._security_process_injection_prevented.json | 6 +- ...endpoint_security_ransomware_detected.json | 6 +- ...ndpoint_security_ransomware_prevented.json | 6 +- ...den_file_attribute_with_via_attribexe.json | 4 +- .../eql_adobe_hijack_persistence.json | 2 +- .../eql_audio_capture_via_powershell.json | 36 ---- .../eql_audio_capture_via_soundrecorder.json | 36 ---- .../eql_bypass_uac_event_viewer.json | 36 ---- .../eql_bypass_uac_via_cmstp.json | 36 ---- .../eql_bypass_uac_via_sdclt.json | 36 ---- .../eql_clearing_windows_event_logs.json | 4 +- ...delete_volume_usn_journal_with_fsutil.json | 2 +- ...deleting_backup_catalogs_with_wbadmin.json | 2 +- .../eql_direct_outbound_smb_connection.json | 2 +- ...ble_windows_firewall_rules_with_netsh.json | 2 +- .../eql_dll_search_order_hijack.json | 51 ----- ...coding_or_decoding_files_via_certutil.json | 2 +- .../eql_local_scheduled_task_commands.json | 2 +- .../eql_local_service_commands.json | 2 +- ...ql_modification_of_boot_configuration.json | 36 ---- ...ql_msbuild_making_network_connections.json | 4 +- .../eql_mshta_making_network_connections.json | 4 +- .../eql_msxsl_making_network_connections.json | 36 ---- .../eql_psexec_lateral_movement_command.json | 4 +- ...ql_suspicious_ms_office_child_process.json | 2 +- ...l_suspicious_ms_outlook_child_process.json | 2 +- ...l_suspicious_pdf_reader_child_process.json | 36 ---- .../eql_system_shells_via_services.json | 2 +- ...usual_network_connection_via_rundll32.json | 2 +- .../eql_unusual_parentchild_relationship.json | 2 +- ...ql_unusual_process_network_connection.json | 2 +- .../eql_user_account_creation.json | 2 +- ...eql_user_added_to_administrator_group.json | 36 ---- ...ume_shadow_copy_deletion_via_vssadmin.json | 2 +- ..._volume_shadow_copy_deletion_via_wmic.json | 4 +- ...l_windows_script_executing_powershell.json | 2 +- .../eql_wmic_command_lateral_movement.json | 39 ---- .../rules/prepackaged_rules/index.ts | 186 +++++++----------- .../linux_hping_activity.json | 6 +- .../linux_iodine_activity.json | 4 +- .../linux_kernel_module_activity.json | 4 +- .../linux_ldso_process_activity.json | 22 --- .../linux_mknod_activity.json | 6 +- .../linux_netcat_network_connection.json | 6 +- .../linux_nmap_activity.json | 6 +- .../linux_nping_activity.json | 6 +- ...nux_process_started_in_temp_directory.json | 4 +- .../linux_shell_activity_by_web_server.json | 4 +- .../linux_socat_activity.json | 6 +- .../linux_strace_activity.json | 2 +- .../linux_tcpdump_activity.json | 4 +- .../linux_whoami_commmand.json | 4 +- .../network_dns_directly_to_the_internet.json | 8 +- ...fer_protocol_activity_to_the_internet.json | 8 +- ...hat_protocol_activity_to_the_internet.json | 8 +- .../network_nat_traversal_port_activity.json | 8 +- .../network_port_26_activity.json | 10 +- ...rk_port_8000_activity_to_the_internet.json | 6 +- ..._to_point_tunneling_protocol_activity.json | 10 +- ...k_proxy_port_activity_to_the_internet.json | 8 +- ...te_desktop_protocol_from_the_internet.json | 8 +- ...mote_desktop_protocol_to_the_internet.json | 8 +- ...mote_procedure_call_from_the_internet.json | 6 +- ...remote_procedure_call_to_the_internet.json | 6 +- ...file_sharing_activity_to_the_internet.json | 6 +- .../network_smtp_to_the_internet.json | 8 +- ..._server_port_activity_to_the_internet.json | 8 +- ...rk_ssh_secure_shell_from_the_internet.json | 8 +- ...work_ssh_secure_shell_to_the_internet.json | 8 +- .../network_telnet_port_activity.json | 8 +- .../network_tor_activity_to_the_internet.json | 8 +- ...l_network_computing_from_the_internet.json | 8 +- ...ual_network_computing_to_the_internet.json | 8 +- .../rules/prepackaged_rules/notice.ts | 2 - .../prepackaged_rules/null_user_agent.json | 4 +- .../prepackaged_rules/sqlmap_user_agent.json | 5 +- ...rvice_bits_connecting_to_the_internet.json | 51 ----- ...s_certutil_connecting_to_the_internet.json | 36 ---- ...and_prompt_connecting_to_the_internet.json | 2 +- ...nd_shell_started_by_internet_explorer.json | 36 ---- ...s_command_shell_started_by_powershell.json | 4 +- ...dows_command_shell_started_by_svchost.json | 4 +- ...ws_defense_evasion_via_filter_manager.json | 5 +- ...dows_execution_via_compiled_html_file.json | 2 +- ...dows_execution_via_connection_manager.json | 37 ---- ...dows_execution_via_net_com_assemblies.json | 40 ---- .../windows_execution_via_regsvr32.json | 2 +- ...ution_via_trusted_developer_utilities.json | 3 +- ...le_program_connecting_to_the_internet.json | 2 +- ...isc_lolbin_connecting_to_the_internet.json | 2 +- ...ommand_activity_by_the_system_account.json | 36 ---- ..._persistence_via_application_shimming.json | 3 +- ...escalation_via_accessibility_features.json | 3 +- ...rocess_discovery_via_tasklist_command.json | 3 +- .../windows_process_execution_via_wmi.json | 39 ---- ...er_program_connecting_to_the_internet.json | 2 +- ...windows_signed_binary_proxy_execution.json | 2 +- ...igned_binary_proxy_execution_download.json | 54 ----- ...uspicious_process_started_by_a_script.json | 4 +- .../windows_whoami_command_activity.json | 2 +- 115 files changed, 312 insertions(+), 1094 deletions(-) delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_ldso_process_activity.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_connecting_to_the_internet.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_internet_explorer.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_connection_manager.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_activity_by_the_system_account.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_execution_via_wmi.json delete mode 100644 x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution_download.json diff --git a/NOTICE.txt b/NOTICE.txt index e0c5d94eff6b3..69be6db72cff2 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -162,8 +162,6 @@ which is available under a "MIT" license. The files based on this license are: - windows_priv_escalation_via_accessibility_features.json - windows_persistence_via_application_shimming.json - windows_execution_via_trusted_developer_utilities.json -- windows_execution_via_net_com_assemblies.json -- windows_execution_via_connection_manager.json MIT License diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json index c685d96cdf57b..fd46a09d4ced0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/403_response_to_a_post.json @@ -1,13 +1,13 @@ { - "description": "A POST request to web application returned a 403 response which indicates the web application declined to process the request because the action requested was disallowed.", + "description": "A POST request to web application returned a 403 response, which indicates the web application declined to process the request because the action requested was not allowed", "false_positives": [ - "Security scans and tests may result in these errors. Misconfigured or buggy applications may produce large numbers of these errors. If the source is unexpected, or the user is unauthorized, or the request is unusual, these may be suspicious or malicious activity." + "Security scans and tests may result in these errors. Misconfigured or buggy applications may produce large numbers of these errors. If the source is unexpected, the user unauthorized, or the request unusual, these may indicate suspicious or malicious activity." ], "index": [ "apm-*-transaction*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Web Application Suspicious Activity: POST Request Declined", "query": "http.response.status_code:403 and http.request.method:post", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json index 64264452d468b..a6235c889902b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/405_response_method_not_allowed.json @@ -1,13 +1,13 @@ { - "description": "A request to web application returned a 405 response which indicates the web application declined to process the request because the HTTP method was not allowed for the resource.", + "description": "A request to web application returned a 405 response which indicates the web application declined to process the request because the HTTP method is not allowed for the resource", "false_positives": [ - "Security scans and tests may result in these errors. Misconfigured or buggy applications may produce large numbers of these errors. If the source is unexpected, or the user is unauthorized, or the request is unusual, these may be suspicious or malicious activity." + "Security scans and tests may result in these errors. Misconfigured or buggy applications may produce large numbers of these errors. If the source is unexpected, the user unauthorized, or the request unusual, these may indicate suspicious or malicious activity." ], "index": [ "apm-*-transaction*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Web Application Suspicious Activity: Unauthorized Method", "query": "http.response.status_code:405", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json index 56d142fdf3ef8..397db1367f402 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_adversary_behavior_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Adversary behavior detected.", + "description": "Elastic Endpoint detected an Adversary Behavior. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Adversary Behavior - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:rules_engine_event", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json index 6805696ce6bc9..fdd875e95b3d3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_detected.json @@ -1,11 +1,13 @@ { - "description": "Elastic Endpoint Security Alert - Credential dumping detected.", + "description": "Elastic Endpoint detected Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, - "name": "Cred Dumping - Detected - Elastic Endpoint", + "max_signals": 100, + "name": "Credential Dumping - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:detection", "risk_score": 73, "rule_id": "571afc56-5ed9-465d-a2a9-045f099f6e7e", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json index 68c0f5cad8252..8ed63c55ef213 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_dumping_prevented.json @@ -1,11 +1,13 @@ { - "description": "Elastic Endpoint Security Alert - Credential dumping prevented.", + "description": "Elastic Endpoint prevented Credential Dumping. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, - "name": "Cred Dumping - Prevented - Elastic Endpoint", + "max_signals": 100, + "name": "Credential Dumping - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:cred_theft_event and endgame.metadata.type:prevention", "risk_score": 47, "rule_id": "db8c33a8-03cd-4988-9e2c-d0a4863adb13", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json index 0d0d9c71a2ec1..98c4e5341d9e8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_detected.json @@ -1,11 +1,13 @@ { - "description": "Elastic Endpoint Security Alert - Credential manipulation detected.", + "description": "Elastic Endpoint detected Credential Manipulation. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, - "name": "Cred Manipulation - Detected - Elastic Endpoint", + "max_signals": 100, + "name": "Credential Manipulation - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:detection", "risk_score": 73, "rule_id": "c0be5f31-e180-48ed-aa08-96b36899d48f", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json index df49c80e3097b..4234e3d955794 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_cred_manipulation_prevented.json @@ -1,17 +1,20 @@ { - "description": "Elastic Endpoint Security Alert - Credential manipulation prevented.", + "description": "Elastic Endpoint prevented Credential Manipulation. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, - "name": "Cred Manipulation - Prevented - Elastic Endpoint", + "max_signals": 100, + "name": "Credential Manipulation - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:token_manipulation_event and endgame.metadata.type:prevention", "risk_score": 47, "rule_id": "c9e38e64-3f4c-4bf3-ad48-0e61a60ea1fa", "severity": "medium", "tags": [ - "Elastic" + "Elastic", + "Endpoint" ], "type": "query", "version": 1 diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json index 9c3896a70b3a0..9971075d7e617 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Exploit detected.", + "description": "Elastic Endpoint detected an Exploit. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Exploit - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:detection", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json index 4632ae6a1487b..233552fc1de13 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_exploit_prevented.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Exploit prevented.", + "description": "Elastic Endpoint prevented an Exploit. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Exploit - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:exploit_event and endgame.metadata.type:prevention", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json index 68831392942d4..64d686fb984c9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Malware detected.", + "description": "Elastic Endpoint detected Malware. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Malware - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:detection", "risk_score": 99, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json index 56b41df2a3349..72f2134f23ab2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_malware_prevented.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Malware prevented.", + "description": "Elastic Endpoint prevented Malware. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Malware - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:file_classification_event and endgame.metadata.type:prevention", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json index 268dc9cf89121..3755dd4cd5dac 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Permission theft detected.", + "description": "Elastic Endpoint detected Permission Theft. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Permission Theft - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:detection", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json index 6deda3d0453b2..d869407586372 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_permission_theft_prevented.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Permission theft prevented.", + "description": "Elastic Endpoint prevented Permission Theft. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Permission Theft - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:token_protection_event and endgame.metadata.type:prevention", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json index 25a03e611fe3e..1078cf69394e2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Process injection detected.", + "description": "Elastic Endpoint detected Process Injection. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Process Injection - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:detection", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json index 6c549d70a9d41..8b68fc6925f9b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_process_injection_prevented.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Process injection prevented.", + "description": "Elastic Endpoint prevented Process Injection. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Process Injection - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:kernel_shellcode_event and endgame.metadata.type:prevention", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json index 4a118cf8ab861..a332c7011e94b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_detected.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Ransomware detected.", + "description": "Elastic Endpoint detected Ransomware. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Ransomware - Detected - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:detection", "risk_score": 99, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json index 8b48e8f4c1758..087c91d2105ce 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/elastic_endpoint_security_ransomware_prevented.json @@ -1,10 +1,12 @@ { - "description": "Elastic Endpoint Security Alert - Ransomware prevented.", + "description": "Elastic Endpoint prevented Ransomware. Click the Elastic Endpoint icon in the event.module column or the link in the rule.reference column in the External Alerts tab of the SIEM Detections page for additional information.", + "from": "now-660s", "index": [ "endgame-*" ], + "interval": "10m", "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Ransomware - Prevented - Elastic Endpoint", "query": "event.kind:alert and event.module:endgame and event.action:ransomware_event and endgame.metadata.type:prevention", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json index 374691f670b74..43aead33925c1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adding_the_hidden_file_attribute_with_via_attribexe.json @@ -1,10 +1,10 @@ { - "description": "Adversaries can add the 'hidden' attribute to files to hide them from the user in an attempt to evade detection", + "description": "Adversaries can add the 'hidden' attribute to files to hide them from the user in an attempt to evade detection.", "index": [ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Adding Hidden File Attribute via Attrib", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"attrib.exe\" and process.args:\"+h\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json index 47f171dd7be0e..387726168cb10 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_adobe_hijack_persistence.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Adobe Hijack Persistence", "query": "file.path:(\"C:\\Program Files (x86)\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\" or \"C:\\Program Files\\Adobe\\Acrobat Reader DC\\Reader\\AcroCEF\\RdrCEF.exe\") and event.action:\"File created (rule: FileCreate)\" and not process.name:msiexeec.exe", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json deleted file mode 100644 index 7ec960eea6302..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_powershell.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Audio Capture via PowerShell", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"powershell.exe\" and process.args:\"WindowsAudioDevice-Powershell-Cmdlet\"", - "risk_score": 21, - "rule_id": "b27b9f47-0a20-4807-8377-7f899b4fbada", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Collection", - "reference": "https://attack.mitre.org/tactics/TA0009/" - }, - "technique": [ - { - "id": "T1123", - "name": "Audio Capture", - "reference": "https://attack.mitre.org/techniques/T1123/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json deleted file mode 100644 index 87bdfc4980124..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_audio_capture_via_soundrecorder.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "An adversary can leverage a computer's peripheral devices or applications to capture audio recordings for the purpose of listening into sensitive conversations to gather information.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Audio Capture via SoundRecorder", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"SoundRecorder.exe\" and process.args:\"/FILE\"", - "risk_score": 21, - "rule_id": "f8e06892-ed10-4452-892e-2c5a38d552f1", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Collection", - "reference": "https://attack.mitre.org/tactics/TA0009/" - }, - "technique": [ - { - "id": "T1123", - "name": "Audio Capture", - "reference": "https://attack.mitre.org/techniques/T1123/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json deleted file mode 100644 index 2fa63fa51f7c1..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_event_viewer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies User Account Control (UAC) bypass via eventvwr.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Bypass UAC via Event Viewer", - "query": "process.parent.name:eventvwr.exe and event.action:\"Process Create (rule: ProcessCreate)\" and not process.executable:(\"C:\\Windows\\System32\\mmc.exe\" or \"C:\\Windows\\SysWOW64\\mmc.exe\")", - "risk_score": 21, - "rule_id": "59547add-a400-4baa-aa0c-66c72efdb77f", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1088", - "name": "Bypass User Account Control", - "reference": "https://attack.mitre.org/techniques/T1088/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json deleted file mode 100644 index fdc716dcb3ebe..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_cmstp.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies User Account Control (UAC) bypass via cmstp.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Bypass UAC via Cmstp", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"cmstp.exe\" and process.parent.args:(\"/s\" and \"/au\")", - "risk_score": 21, - "rule_id": "2f7403da-1a4c-46bb-8ecc-c1a596e10cd0", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1088", - "name": "Bypass User Account Control", - "reference": "https://attack.mitre.org/techniques/T1088/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json deleted file mode 100644 index 484a01e0211ab..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_bypass_uac_via_sdclt.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies User Account Control (UAC) bypass via sdclt.exe. Attackers bypass UAC to stealthily execute code with elevated permissions.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Bypass UAC via Sdclt", - "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"sdclt.exe\" and process.args:\"/kickoffelev\" and not process.executable:(\"C:\\Windows\\System32\\sdclt.exe\" or \"C:\\Windows\\System32\\control.exe\" or \"C:\\Windows\\SysWOW64\\sdclt.exe\" or \"C:\\Windows\\SysWOW64\\control.exe\")", - "risk_score": 21, - "rule_id": "f68d83a1-24cb-4b8d-825b-e8af400b9670", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1088", - "name": "Bypass User Account Control", - "reference": "https://attack.mitre.org/techniques/T1088/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json index e9729ff102619..135e81148475f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_clearing_windows_event_logs.json @@ -1,10 +1,10 @@ { - "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt evade detection or destroy forensic evidence on a system.", + "description": "Identifies attempts to clear Windows event log stores. This is often done by attackers in an attempt to evade detection or destroy forensic evidence on a system.", "index": [ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Clearing Windows Event Logs", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and (process.name:\"wevtutil.exe\" and process.args:\"cl\") or (process.name:\"powershell.exe\" and process.args:\"Clear-EventLog\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json index 479bb4a2a6d7c..815e2abd0fc96 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_delete_volume_usn_journal_with_fsutil.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Delete Volume USN Journal with Fsutil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"fsutil.exe\" and process.args:(\"usn\" and \"deletejournal\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json index 204925e4b677b..d990e071b2123 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_deleting_backup_catalogs_with_wbadmin.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Deleting Backup Catalogs with Wbadmin", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wbadmin.exe\" and process.args:(\"delete\" and \"catalog\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json index b6398a9985e7e..9d1cebb32c865 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_direct_outbound_smb_connection.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Direct Outbound SMB Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and destination.port:445 and not process.pid:4 and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json index 32b43cc24e91b..7ead979f27bb1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_disable_windows_firewall_rules_with_netsh.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Disable Windows Firewall Rules via Netsh", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"netsh.exe\" and process.args:(\"firewall\" and \"set\" and \"disable\") or process.args:(\"advfirewall\" and \"state\" and \"off\")", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json deleted file mode 100644 index 5740453b6ae6d..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_dll_search_order_hijack.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "description": "Detects writing DLL files to known locations associated with Windows files vulnerable to DLL search order hijacking.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "DLL Search Order Hijack", - "query": " event.action:\"File created (rule: FileCreate)\" and not winlog.user.identifier:(\"S-1-5-18\" or \"S-1-5-19\" or \"S-1-5-20\") and file.path:(\"C\\Windows\\ehome\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptbase.dll\" or \"C\\Windows\\System32\\Sysprep\\cryptsp.dll\" or \"C\\Windows\\System32\\Sysprep\\rpcrtremote.dll\" or \"C\\Windows\\System32\\Sysprep\\uxtheme.dll\" or \"C\\Windows\\System32\\Sysprep\\dwmapi.dll\" or \"C\\Windows\\System32\\Sysprep\\shcore.dll\" or \"C\\Windows\\System32\\Sysprep\\oleacc.dll\" or \"C\\Windows\\System32\\ntwdblib.dll\") ", - "risk_score": 47, - "rule_id": "73fbc44c-c3cd-48a8-a473-f4eb2065c716", - "severity": "medium", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0004", - "name": "Privilege Escalation", - "reference": "https://attack.mitre.org/tactics/TA0004/" - }, - "technique": [ - { - "id": "T1088", - "name": "Bypass User Account Control", - "reference": "https://attack.mitre.org/techniques/T1088/" - } - ] - }, - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" - }, - "technique": [ - { - "id": "T1088", - "name": "Bypass User Account Control", - "reference": "https://attack.mitre.org/techniques/T1088/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json index 37e1c26885a15..2cb92f0a26c90 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_encoding_or_decoding_files_via_certutil.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Encoding or Decoding Files via CertUtil", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"certutil.exe\" and process.args:(\"-encode\" or \"/encode\" or \"-decode\" or \"/decode\")", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json index dc4991f86a0f5..e7f46b46c2ce2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_scheduled_task_commands.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Local Scheduled Task Commands", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:schtasks.exe and process.args:(\"/create\" or \"-create\" or \"/S\" or \"-s\" or \"/run\" or \"-run\" or \"/change\" or \"-change\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json index eb6f2377376f2..b018435ea0214 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_local_service_commands.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Local Service Commands", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:sc.exe and process.args:(\"create\" or \"config\" or \"failure\" or \"start\")", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json deleted file mode 100644 index 26bd65b897c63..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_modification_of_boot_configuration.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies use of bcdedit.exe to delete boot configuration data. This tactic is sometimes used as by malware or an attacker as a destructive technique.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Modification of Boot Configuration", - "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"bcdedit.exe\" and process.args:\"set\" and process.args:( (\"bootstatuspolicy\" and \"ignoreallfailures\") or (\"recoveryenabled\" and \"no\") ) ", - "risk_score": 73, - "rule_id": "b9ab2f7f-f719-4417-9599-e0252fffe2d8", - "severity": "high", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" - }, - "technique": [ - { - "id": "T1107", - "name": "File Deletion", - "reference": "https://attack.mitre.org/techniques/T1107/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json index d40ffed523c6a..7d84e0bda06e5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msbuild_making_network_connections.json @@ -4,9 +4,9 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "MsBuild Making Network Connections", - "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:msbuild.exe and not destination.ip:(\"127.0.0.1\" or \"::1\")", + "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:MSBuild.exe and not destination.ip:(\"127.0.0.1\" or \"::1\")", "risk_score": 47, "rule_id": "0e79980b-4250-4a50-a509-69294c14e84b", "severity": "medium", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json index 7905d80c6e8c2..44141b08fb8f0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_mshta_making_network_connections.json @@ -4,9 +4,9 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Network Connection via Mshta", - "query": "event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:\"mshta.exe\" and not process.name:\"mshta.exe\"", + "query": "event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:\"mshta.exe\"", "references": [ "https://www.fireeye.com/blog/threat-research/2017/05/cyber-espionage-apt32.html" ], diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json deleted file mode 100644 index 16ef15589f48f..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_msxsl_making_network_connections.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies msxsl.exe making a network connection. This may indicate adversarial activity as msxsl.exe is often leveraged by adversaries to execute malicious scripts and evade detection.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Network Connection via MsXsl", - "query": "process.name:msxsl.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 47, - "rule_id": "d7351b03-135d-43ba-8b36-cc9b07854525", - "severity": "medium", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1220", - "name": "XSL Script Processing", - "reference": "https://attack.mitre.org/techniques/T1220/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json index fd210005118b8..580f73c25a4a2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_psexec_lateral_movement_command.json @@ -7,9 +7,9 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "PsExec Network Connection", - "query": "process.name:psexec.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" ", + "query": "process.name:PsExec.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" ", "risk_score": 21, "rule_id": "55d551c6-333b-4665-ab7e-5d14a59715ce", "severity": "low", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json index a5d71e23a1215..95aabc49b5302 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_office_child_process.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Suspicious MS Office Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"winword.exe\" or \"excel.exe\" or \"powerpnt.exe\" or \"eqnedt32.exe\" or \"fltldr.exe\" or \"mspub.exe\" or \"msaccess.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json index 86716d6608049..f31228d0130f8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_ms_outlook_child_process.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Suspicious MS Outlook Child Process", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"outlook.exe\" and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json deleted file mode 100644 index b0fbccf1b67a7..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_suspicious_pdf_reader_child_process.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies suspicious child processes of PDF reader applications. These child processes are often launched via exploitation of PDF applications or social engineering.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Suspicious PDF Reader Child Process", - "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"acrord32.exe\" or \"rdrcef.exe\" or \"foxitphantomPDF.exe\" or \"foxitreader.exe\") and process.name:(\"arp.exe\" or \"dsquery.exe\" or \"dsget.exe\" or \"gpresult.exe\" or \"hostname.exe\" or \"ipconfig.exe\" or \"nbtstat.exe\" or \"net.exe\" or \"net1.exe\" or \"netsh.exe\" or \"netstat.exe\" or \"nltest.exe\" or \"ping.exe\" or \"qprocess.exe\" or \"quser.exe\" or \"qwinsta.exe\" or \"reg.exe\" or \"sc.exe\" or \"systeminfo.exe\" or \"tasklist.exe\" or \"tracert.exe\" or \"whoami.exe\" or \"bginfo.exe\" or \"cdb.exe\" or \"cmstp.exe\" or \"csi.exe\" or \"dnx.exe\" or \"fsi.exe\" or \"ieexec.exe\" or \"iexpress.exe\" or \"installutil.exe\" or \"Microsoft.Workflow.Compiler.exe\" or \"msbuild.exe\" or \"mshta.exe\" or \"msxsl.exe\" or \"odbcconf.exe\" or \"rcsi.exe\" or \"regsvr32.exe\" or \"xwizard.exe\" or \"atbroker.exe\" or \"forfiles.exe\" or \"schtasks.exe\" or \"regasm.exe\" or \"regsvcs.exe\" or \"cmd.exe\" or \"cscript.exe\" or \"powershell.exe\" or \"pwsh.exe\" or \"wmic.exe\" or \"wscript.exe\" or \"bitsadmin.exe\" or \"certutil.exe\" or \"ftp.exe\") ", - "risk_score": 73, - "rule_id": "afcac7b1-d092-43ff-a136-aa7accbda38f", - "severity": "high", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1193", - "name": "Spearphishing Attachment", - "reference": "https://attack.mitre.org/techniques/T1193/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json index 984b522596c1e..a38232f37843e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_system_shells_via_services.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "System Shells via Services", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:\"services.exe\" and process.name:(\"cmd.exe\" or \"powershell.exe\")", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json index 03b9bebb655c3..820c69cb3c809 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_network_connection_via_rundll32.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Unusual Network Connection via RunDLL32", "query": "process.name:rundll32.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json index 72eb17863e0d3..21d3d2741378a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_parentchild_relationship.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Unusual Parent-Child Relationship ", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.executable:* and ( (process.name:\"smss.exe\" and not process.parent.name:(\"System\" or \"smss.exe\")) or (process.name:\"csrss.exe\" and not process.parent.name:(\"smss.exe\" or \"svchost.exe\")) or (process.name:\"wininit.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"winlogon.exe\" and not process.parent.name:\"smss.exe\") or (process.name:\"lsass.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"LogonUI.exe\" and not process.parent.name:(\"winlogon.exe\" or \"wininit.exe\")) or (process.name:\"services.exe\" and not process.parent.name:\"wininit.exe\") or (process.name:\"svchost.exe\" and not process.parent.name:(\"services.exe\" or \"MsMpEng.exe\")) or (process.name:\"spoolsv.exe\" and not process.parent.name:\"services.exe\") or (process.name:\"taskhost.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"taskhostw.exe\" and not process.parent.name:(\"services.exe\" or \"svchost.exe\")) or (process.name:\"userinit.exe\" and not process.parent.name:(\"dwm.exe\" or \"winlogon.exe\")) )", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json index 8ca16198ff175..ee861e19341af 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_unusual_process_network_connection.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Unusual Process Network Connection", "query": " event.action:\"Network connection detected (rule: NetworkConnect)\" and process.name:(bginfo.exe or cdb.exe or cmstp.exe or csi.exe or dnx.exe or fsi.exe or ieexec.exe or iexpress.exe or Microsoft.Workflow.Compiler.exe or odbcconf.exe or rcsi.exe or xwizard.exe)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json index dee3d18bd5eda..5a7aeab224548 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_account_creation.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "User Account Creation", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"user\" and (\"/add\" or \"/ad\")) ", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json deleted file mode 100644 index 4ed6a06b18d3b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_user_added_to_administrator_group.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies attempts to add a user to an administrative group with the \"net.exe\" command. This is sometimes done by attackers to increase access of a compromised account or create new account.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "User Added to Administrator Group", - "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:(\"net.exe\" or \"net1.exe\") and not process.parent.name:\"net.exe\" and process.args:(\"group\" and \"admin\" and \"/add\") ", - "risk_score": 47, - "rule_id": "4426de6f-6103-44aa-a77e-49d672836c27", - "severity": "medium", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [ - { - "id": "T1098", - "name": "Account Manipulation", - "reference": "https://attack.mitre.org/techniques/T1098/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json index cdeeb1563dfde..80c0dd962c099 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_vssadmin.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Volume Shadow Copy Deletion via VssAdmin", "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"vssadmin.exe\" and process.args:(\"delete\" and \"shadows\") ", "risk_score": 73, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json index 9465cf84d73f4..d90aca1e2eaf4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_volume_shadow_copy_deletion_via_wmic.json @@ -4,9 +4,9 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Volume Shadow Copy Deletion via WMIC", - "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"shadowcopy\" and \"delete\")", + "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"WMIC.exe\" and process.args:(\"shadowcopy\" and \"delete\")", "risk_score": 73, "rule_id": "dc9c1f74-dac3-48e3-b47f-eb79db358f57", "severity": "high", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json index f3df1276de53d..8f6e97cdd7bdb 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_windows_script_executing_powershell.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Windows Script Executing PowerShell", "query": "event.action:\"Process Create (rule: ProcessCreate)\" and process.parent.name:(\"wscript.exe\" or \"cscript.exe\") and process.name:\"powershell.exe\"", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json deleted file mode 100644 index a50d9e64f2e2b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/eql_wmic_command_lateral_movement.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "description": "Identifies use of wmic.exe to run commands on remote hosts. This could be indicative of adversary lateral movement but will be noisy if commonly done by admins.", - "false_positives": [ - "The WMIC utility provides a command-line interface for WMI, which can be used for an array of administrative capabilities. It's important to baseline your environment to determine any abnormal use of this tool." - ], - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "WMIC Command Lateral Movement", - "query": " event.action:\"Process Create (rule: ProcessCreate)\" and process.name:\"wmic.exe\" and process.args:(\"/node\" or \"-node\") and process.args:(\"call\" or \"set\")", - "risk_score": 21, - "rule_id": "9616587f-6396-42d0-bd31-ef8dbd806210", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0008", - "name": "Lateral Movement", - "reference": "https://attack.mitre.org/tactics/TA0008/" - }, - "technique": [ - { - "id": "T1047", - "name": "Windows Management Instrumentation", - "reference": "https://attack.mitre.org/techniques/T1047/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index b454501e9f563..d9841948f35a5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -26,99 +26,79 @@ import rule16 from './elastic_endpoint_security_ransomware_detected.json'; import rule17 from './elastic_endpoint_security_ransomware_prevented.json'; import rule18 from './eql_adding_the_hidden_file_attribute_with_via_attribexe.json'; import rule19 from './eql_adobe_hijack_persistence.json'; -import rule20 from './eql_audio_capture_via_powershell.json'; -import rule21 from './eql_audio_capture_via_soundrecorder.json'; -import rule22 from './eql_bypass_uac_event_viewer.json'; -import rule23 from './eql_bypass_uac_via_cmstp.json'; -import rule24 from './eql_bypass_uac_via_sdclt.json'; -import rule25 from './eql_clearing_windows_event_logs.json'; -import rule26 from './eql_delete_volume_usn_journal_with_fsutil.json'; -import rule27 from './eql_deleting_backup_catalogs_with_wbadmin.json'; -import rule28 from './eql_direct_outbound_smb_connection.json'; -import rule29 from './eql_disable_windows_firewall_rules_with_netsh.json'; -import rule30 from './eql_dll_search_order_hijack.json'; -import rule31 from './eql_encoding_or_decoding_files_via_certutil.json'; -import rule32 from './eql_local_scheduled_task_commands.json'; -import rule33 from './eql_local_service_commands.json'; -import rule34 from './eql_modification_of_boot_configuration.json'; -import rule35 from './eql_msbuild_making_network_connections.json'; -import rule36 from './eql_mshta_making_network_connections.json'; -import rule37 from './eql_msxsl_making_network_connections.json'; -import rule38 from './eql_psexec_lateral_movement_command.json'; -import rule39 from './eql_suspicious_ms_office_child_process.json'; -import rule40 from './eql_suspicious_ms_outlook_child_process.json'; -import rule41 from './eql_suspicious_pdf_reader_child_process.json'; -import rule42 from './eql_system_shells_via_services.json'; -import rule43 from './eql_unusual_network_connection_via_rundll32.json'; -import rule44 from './eql_unusual_parentchild_relationship.json'; -import rule45 from './eql_unusual_process_network_connection.json'; -import rule46 from './eql_user_account_creation.json'; -import rule47 from './eql_user_added_to_administrator_group.json'; -import rule48 from './eql_volume_shadow_copy_deletion_via_vssadmin.json'; -import rule49 from './eql_volume_shadow_copy_deletion_via_wmic.json'; -import rule50 from './eql_windows_script_executing_powershell.json'; -import rule51 from './eql_wmic_command_lateral_movement.json'; -import rule52 from './linux_hping_activity.json'; -import rule53 from './linux_iodine_activity.json'; -import rule54 from './linux_kernel_module_activity.json'; -import rule55 from './linux_ldso_process_activity.json'; -import rule56 from './linux_mknod_activity.json'; -import rule57 from './linux_netcat_network_connection.json'; -import rule58 from './linux_nmap_activity.json'; -import rule59 from './linux_nping_activity.json'; -import rule60 from './linux_process_started_in_temp_directory.json'; -import rule61 from './linux_shell_activity_by_web_server.json'; -import rule62 from './linux_socat_activity.json'; -import rule63 from './linux_strace_activity.json'; -import rule64 from './linux_tcpdump_activity.json'; -import rule65 from './linux_whoami_commmand.json'; -import rule66 from './network_dns_directly_to_the_internet.json'; -import rule67 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; -import rule68 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; -import rule69 from './network_nat_traversal_port_activity.json'; -import rule70 from './network_port_26_activity.json'; -import rule71 from './network_port_8000_activity_to_the_internet.json'; -import rule72 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; -import rule73 from './network_proxy_port_activity_to_the_internet.json'; -import rule74 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; -import rule75 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; -import rule76 from './network_rpc_remote_procedure_call_from_the_internet.json'; -import rule77 from './network_rpc_remote_procedure_call_to_the_internet.json'; -import rule78 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; -import rule79 from './network_smtp_to_the_internet.json'; -import rule80 from './network_sql_server_port_activity_to_the_internet.json'; -import rule81 from './network_ssh_secure_shell_from_the_internet.json'; -import rule82 from './network_ssh_secure_shell_to_the_internet.json'; -import rule83 from './network_telnet_port_activity.json'; -import rule84 from './network_tor_activity_to_the_internet.json'; -import rule85 from './network_vnc_virtual_network_computing_from_the_internet.json'; -import rule86 from './network_vnc_virtual_network_computing_to_the_internet.json'; -import rule87 from './null_user_agent.json'; -import rule88 from './sqlmap_user_agent.json'; -import rule89 from './windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json'; -import rule90 from './windows_certutil_connecting_to_the_internet.json'; -import rule91 from './windows_command_prompt_connecting_to_the_internet.json'; -import rule92 from './windows_command_shell_started_by_internet_explorer.json'; -import rule93 from './windows_command_shell_started_by_powershell.json'; -import rule94 from './windows_command_shell_started_by_svchost.json'; -import rule95 from './windows_defense_evasion_via_filter_manager.json'; -import rule96 from './windows_execution_via_compiled_html_file.json'; -import rule97 from './windows_execution_via_connection_manager.json'; -import rule98 from './windows_execution_via_net_com_assemblies.json'; -import rule99 from './windows_execution_via_regsvr32.json'; -import rule100 from './windows_execution_via_trusted_developer_utilities.json'; -import rule101 from './windows_html_help_executable_program_connecting_to_the_internet.json'; -import rule102 from './windows_misc_lolbin_connecting_to_the_internet.json'; -import rule103 from './windows_net_command_activity_by_the_system_account.json'; -import rule104 from './windows_persistence_via_application_shimming.json'; -import rule105 from './windows_priv_escalation_via_accessibility_features.json'; -import rule106 from './windows_process_discovery_via_tasklist_command.json'; -import rule107 from './windows_process_execution_via_wmi.json'; -import rule108 from './windows_register_server_program_connecting_to_the_internet.json'; -import rule109 from './windows_signed_binary_proxy_execution.json'; -import rule110 from './windows_signed_binary_proxy_execution_download.json'; -import rule111 from './windows_suspicious_process_started_by_a_script.json'; -import rule112 from './windows_whoami_command_activity.json'; +import rule20 from './eql_clearing_windows_event_logs.json'; +import rule21 from './eql_delete_volume_usn_journal_with_fsutil.json'; +import rule22 from './eql_deleting_backup_catalogs_with_wbadmin.json'; +import rule23 from './eql_direct_outbound_smb_connection.json'; +import rule24 from './eql_disable_windows_firewall_rules_with_netsh.json'; +import rule25 from './eql_encoding_or_decoding_files_via_certutil.json'; +import rule26 from './eql_local_scheduled_task_commands.json'; +import rule27 from './eql_local_service_commands.json'; +import rule28 from './eql_msbuild_making_network_connections.json'; +import rule29 from './eql_mshta_making_network_connections.json'; +import rule30 from './eql_psexec_lateral_movement_command.json'; +import rule31 from './eql_suspicious_ms_office_child_process.json'; +import rule32 from './eql_suspicious_ms_outlook_child_process.json'; +import rule33 from './eql_system_shells_via_services.json'; +import rule34 from './eql_unusual_network_connection_via_rundll32.json'; +import rule35 from './eql_unusual_parentchild_relationship.json'; +import rule36 from './eql_unusual_process_network_connection.json'; +import rule37 from './eql_user_account_creation.json'; +import rule38 from './eql_volume_shadow_copy_deletion_via_vssadmin.json'; +import rule39 from './eql_volume_shadow_copy_deletion_via_wmic.json'; +import rule40 from './eql_windows_script_executing_powershell.json'; +import rule41 from './linux_hping_activity.json'; +import rule42 from './linux_iodine_activity.json'; +import rule43 from './linux_kernel_module_activity.json'; +import rule44 from './linux_mknod_activity.json'; +import rule45 from './linux_netcat_network_connection.json'; +import rule46 from './linux_nmap_activity.json'; +import rule47 from './linux_nping_activity.json'; +import rule48 from './linux_process_started_in_temp_directory.json'; +import rule49 from './linux_shell_activity_by_web_server.json'; +import rule50 from './linux_socat_activity.json'; +import rule51 from './linux_strace_activity.json'; +import rule52 from './linux_tcpdump_activity.json'; +import rule53 from './linux_whoami_commmand.json'; +import rule54 from './network_dns_directly_to_the_internet.json'; +import rule55 from './network_ftp_file_transfer_protocol_activity_to_the_internet.json'; +import rule56 from './network_irc_internet_relay_chat_protocol_activity_to_the_internet.json'; +import rule57 from './network_nat_traversal_port_activity.json'; +import rule58 from './network_port_26_activity.json'; +import rule59 from './network_port_8000_activity_to_the_internet.json'; +import rule60 from './network_pptp_point_to_point_tunneling_protocol_activity.json'; +import rule61 from './network_proxy_port_activity_to_the_internet.json'; +import rule62 from './network_rdp_remote_desktop_protocol_from_the_internet.json'; +import rule63 from './network_rdp_remote_desktop_protocol_to_the_internet.json'; +import rule64 from './network_rpc_remote_procedure_call_from_the_internet.json'; +import rule65 from './network_rpc_remote_procedure_call_to_the_internet.json'; +import rule66 from './network_smb_windows_file_sharing_activity_to_the_internet.json'; +import rule67 from './network_smtp_to_the_internet.json'; +import rule68 from './network_sql_server_port_activity_to_the_internet.json'; +import rule69 from './network_ssh_secure_shell_from_the_internet.json'; +import rule70 from './network_ssh_secure_shell_to_the_internet.json'; +import rule71 from './network_telnet_port_activity.json'; +import rule72 from './network_tor_activity_to_the_internet.json'; +import rule73 from './network_vnc_virtual_network_computing_from_the_internet.json'; +import rule74 from './network_vnc_virtual_network_computing_to_the_internet.json'; +import rule75 from './null_user_agent.json'; +import rule76 from './sqlmap_user_agent.json'; +import rule77 from './windows_command_prompt_connecting_to_the_internet.json'; +import rule78 from './windows_command_shell_started_by_powershell.json'; +import rule79 from './windows_command_shell_started_by_svchost.json'; +import rule80 from './windows_defense_evasion_via_filter_manager.json'; +import rule81 from './windows_execution_via_compiled_html_file.json'; +import rule82 from './windows_execution_via_regsvr32.json'; +import rule83 from './windows_execution_via_trusted_developer_utilities.json'; +import rule84 from './windows_html_help_executable_program_connecting_to_the_internet.json'; +import rule85 from './windows_misc_lolbin_connecting_to_the_internet.json'; +import rule86 from './windows_persistence_via_application_shimming.json'; +import rule87 from './windows_priv_escalation_via_accessibility_features.json'; +import rule88 from './windows_process_discovery_via_tasklist_command.json'; +import rule89 from './windows_register_server_program_connecting_to_the_internet.json'; +import rule90 from './windows_signed_binary_proxy_execution.json'; +import rule91 from './windows_suspicious_process_started_by_a_script.json'; +import rule92 from './windows_whoami_command_activity.json'; export const rawRules = [ rule1, rule2, @@ -212,24 +192,4 @@ export const rawRules = [ rule90, rule91, rule92, - rule93, - rule94, - rule95, - rule96, - rule97, - rule98, - rule99, - rule100, - rule101, - rule102, - rule103, - rule104, - rule105, - rule106, - rule107, - rule108, - rule109, - rule110, - rule111, - rule112, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json index 517e16fb3d284..c4b14389c0f75 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_hping_activity.json @@ -1,5 +1,5 @@ { - "description": "Hping ran on a Linux host. Hping is FOSS command-line packet analyzer and has the ability to construct network packets for a wide variety of network security testing applications including scanning and firewall auditing.", + "description": "Hping ran on a Linux host. Hping is a FOSS command-line packet analyzer and has the ability to construct network packets for a wide variety of network security testing applications, including scanning and firewall auditing.", "false_positives": [ "Normal use of hping is uncommon apart from security testing and research. Use by non-security engineers is very uncommon." ], @@ -7,9 +7,9 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Hping Process Activity", - "query": "process.name: hping and event.action:executed", + "query": "process.name: (hping3 or hping2 or hping) and event.action:executed", "references": [ "https://en.wikipedia.org/wiki/Hping" ], diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json index 49f18ef9871a1..b8455a4d2b21d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_iodine_activity.json @@ -1,5 +1,5 @@ { - "description": "Iodine is a tool for tunneling Internet protocol version 4 (IPV4) traffic over the DNS protocol in order to circumvent firewalls, network security groups or network access lists while evading detection.", + "description": "Iodine is a tool for tunneling Internet protocol version 4 (IPV4) traffic over the DNS protocol to circumvent firewalls, network security groups, and network access lists while evading detection.", "false_positives": [ "Normal use of Iodine is uncommon apart from security testing and research. Use by non-security engineers is very uncommon." ], @@ -7,7 +7,7 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Potential DNS Tunneling via Iodine", "query": "process.name: (iodine or iodined) and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json index 8c94694ca4d04..f1d12de674488 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_kernel_module_activity.json @@ -1,5 +1,5 @@ { - "description": "Identifies loadable kernel module errors, often indicative of potential persistence attempts.", + "description": "Identifies loadable kernel module errors, which are often indicative of potential persistence attempts.", "false_positives": [ "Security tools and device drivers may run these programs in order to load legitimate kernel modules. Use of these programs by ordinary users is uncommon." ], @@ -7,7 +7,7 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Persistence via Kernel Module Modification", "query": "process.name: (insmod or kmod or modprobe or rmod) and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_ldso_process_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_ldso_process_activity.json deleted file mode 100644 index 82a2a16080160..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_ldso_process_activity.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "description": "The dynamic linker, ld.so, runs in a privileged context and can be used to escape restrictive environments by spawning a shell in order to elevate privileges or move laterally.", - "false_positives": [ - "ld.so is a dual-use tool that can be used for benign or malicious activity. Some normal use of this command may originate from developers or administrators. Use of ld.so by non-engineers or ordinary users is uncommon." - ], - "index": [ - "auditbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Ld.so Process Activity", - "query": "process.name:ld.so and event.action:executed", - "risk_score": 21, - "rule_id": "3f31a31c-f7cf-4268-a0df-ec1a98099e7f", - "severity": "low", - "tags": [ - "Elastic", - "Linux" - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json index 8f4e1f40fad12..b0d4c29dc00d3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_mknod_activity.json @@ -1,13 +1,13 @@ { - "description": "The Linux mknod program is sometimes used in the command payload of remote command injection (RCI) and other exploits to export a command shell when the traditional version of netcat is not available to the payload.", + "description": "The Linux mknod program is sometimes used in the command payload of a remote command injection (RCI) and other exploits. It is used to export a command shell when the traditional version of netcat is not available to the payload.", "false_positives": [ - "Mknod is a Linux system program. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools and frameworks. Usage by web servers is more likely to be suspicious." + "Mknod is a Linux system program. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools, and frameworks. Usage by web servers is more likely to be suspicious." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Mknod Process Activity", "query": "process.name: mknod and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json index b06a342d24977..6ab1c1285c0d7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_netcat_network_connection.json @@ -1,13 +1,13 @@ { - "description": "A netcat process is engaging in network activity on a Linux host. Netcat is often used as a persistence mechanism by exporting a reverse shell or by serving a shell on a listening port. Netcat is also sometimes used for data exfiltration. ", + "description": "A netcat process is engaging in network activity on a Linux host. Netcat is often used as a persistence mechanism by exporting a reverse shell or by serving a shell on a listening port. Netcat is also sometimes used for data exfiltration.", "false_positives": [ - "Netcat is a dual-use tool that can be used for benign or malicious activity. Netcat is included in some Linux distributions so its presence is not necessarily suspicious. Some normal use of this program, while uncommon, may originate from scripts, automation tools and frameworks." + "Netcat is a dual-use tool that can be used for benign or malicious activity. Netcat is included in some Linux distributions so its presence is not necessarily suspicious. Some normal use of this program, while uncommon, may originate from scripts, automation tools, and frameworks." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Netcat Network Activity", "query": "process.name: (nc or ncat or netcat or netcat.openbsd or netcat.traditional) and event.action: (connected-to or bound-socket or socket_opened)", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json index 406cd8e026e7a..5d7169219a6f1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nmap_activity.json @@ -1,13 +1,13 @@ { - "description": "Nmap was executed on a Linux host. Nmap is a FOSS tool for network scanning and security testing. It can map and discover networks, identify listening services and operating systems. It is sometimes used to gather information in support of exploitation, execution or lateral movement.", + "description": "Nmap was executed on a Linux host. Nmap is a FOSS tool for network scanning and security testing. It can map and discover networks, and identify listening services and operating systems. It is sometimes used to gather information in support of exploitation, execution or lateral movement.", "false_positives": [ - "Security testing tools and frameworks may run nmap in the course of security auditing. Some normal use of this command may originate from security engineers and network or server administrators. Use of nmap by ordinary users is uncommon." + "Security testing tools and frameworks may run `Nmap` in the course of security auditing. Some normal use of this command may originate from security engineers and network or server administrators. Use of nmap by ordinary users is uncommon." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Nmap Process Activity", "query": "process.name: nmap", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json index de53e05e70fa3..6a713d22e3219 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_nping_activity.json @@ -1,13 +1,13 @@ { - "description": "Nping ran on a Linux host. Nping is part of the Nmap tool suite and has the ability to construct raw packets for a wide variety of security testing applications including denial of service testing.", + "description": "Nping ran on a Linux host. Nping is part of the Nmap tool suite and has the ability to construct raw packets for a wide variety of security testing applications, including denial of service testing.", "false_positives": [ - "Some normal use of this command may originate from security engineers and network or server administrators but this is usually not routine or unannounced. Use of nping by non-engineers or ordinary users is uncommon." + "Some normal use of this command may originate from security engineers and network or server administrators, but this is usually not routine or unannounced. Use of `Nping` by non-engineers or ordinary users is uncommon." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Nping Process Activity", "query": "process.name: nping and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json index 4ed021a4c864d..c80bb4eb41615 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_process_started_in_temp_directory.json @@ -1,13 +1,13 @@ { "description": "Identifies processes running in a temporary folder. This is sometimes done by adversaries to hide malware.", "false_positives": [ - "Build systems like Jenkins may start processes in the /tmp directory. These can be exempted by name or by username." + "Build systems, like Jenkins, may start processes in the `/tmp` directory. These can be exempted by name or by username." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Unusual Process Execution - Temp", "query": "process.working_directory: /tmp and event.action:executed", "risk_score": 47, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json index ac817762fdb71..eff3dd0ab1400 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_shell_activity_by_web_server.json @@ -7,9 +7,9 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Potential Shell via Web Server", - "query": "process.name: bash and (user.name: apache or www) and event.action:executed", + "query": "process.name: bash and user.name: (apache or www or \"wwww-data\") and event.action:executed", "references": [ "https://pentestlab.blog/tag/web-shell/" ], diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json index 481a99518d4ed..56fb41dc5f78f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_socat_activity.json @@ -1,13 +1,13 @@ { - "description": "A Socat process is running on a Linux host. Socat is often used as a persistence mechanism by exporting a reverse shell or by serving a shell on a listening port. Socat is also sometimes used for lateral movement. ", + "description": "A Socat process is running on a Linux host. Socat is often used as a persistence mechanism by exporting a reverse shell, or by serving a shell on a listening port. Socat is also sometimes used for lateral movement.", "false_positives": [ - "Socat is a dual-use tool that can be used for benign or malicious activity. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools and frameworks. Usage by web servers is more likely to be suspicious." + "Socat is a dual-use tool that can be used for benign or malicious activity. Some normal use of this program, at varying levels of frequency, may originate from scripts, automation tools, and frameworks. Usage by web servers is more likely to be suspicious." ], "index": [ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Socat Process Activity", "query": "process.name:socat and not process.args:\"-V\" and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json index f5488ae49d0fb..fdf52e7c728c6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_strace_activity.json @@ -7,7 +7,7 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Strace Process Activity", "query": "process.name: strace and event.action:executed", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json index b6dc7f1689770..908e892026ee3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_tcpdump_activity.json @@ -1,5 +1,5 @@ { - "description": "The Tcpdump program ran on a Linux host. Tcpdump is a network monitoring or packet 'sniffing' tool that can be used to capture insecure credentials or data in motion. Sniffing can also be used to discover details of network services as a prelude to lateral movement or defense evasion.", + "description": "The Tcpdump program ran on a Linux host. Tcpdump is a network monitoring or packet sniffing tool that can be used to capture insecure credentials or data in motion. Sniffing can also be used to discover details of network services as a prelude to lateral movement or defense evasion.", "false_positives": [ "Some normal use of this command may originate from server or network administrators engaged in network troubleshooting." ], @@ -7,7 +7,7 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Network Sniffing via Tcpdump", "query": "process.name: tcpdump and event.action:executed", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json index 91c6d2bcc9f95..052ff34d15dcd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/linux_whoami_commmand.json @@ -1,5 +1,5 @@ { - "description": "The whoami application was executed on a Linux host. This is often used by tools and persistence mechanisms to test for privileged access.", + "description": "The whoami application was executed on a Linux host. This is often used by tools and persistence mechanisms to test for privileged access.", "false_positives": [ "Security testing tools and frameworks may run this command. Some normal use of this command may originate from automation tools and frameworks." ], @@ -7,7 +7,7 @@ "auditbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "User Discovery via Whoami", "query": "process.name: whoami and event.action:executed", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_dns_directly_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_dns_directly_to_the_internet.json index 3d1b07a267eca..72e2ca1490417 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_dns_directly_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_dns_directly_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects internal network client sending DNS traffic directly to the Internet.\nThis is atypical behavior for a managed network and can be indicative of malware,\nexfiltration, command and control or simply misconfiguration. This also impacts your\norganization's ability to provide enterprise monitoring and logging of DNS and opens\nyour network to a variety of abuses or malicious communications.\n", + "description": "This rule detects when an internal network client sends DNS traffic directly to the Internet.\nThis is atypical behavior for a managed network, and can be indicative of malware,\nexfiltration, command and control, or, simply, misconfiguration. This DNS activity also impacts your\norganization's ability to provide enterprise monitoring and logging of DNS, and opens\nyour network to a variety of abuses and malicious communications.\n", "false_positives": [ - "DNS servers should be excluded from this rule as this is expected behavior for them. Endpoints usually query local DNS servers defined in their DHCP scopes but this may be overridden if a user configures their endpoint to use a remote DNS server. This is uncommon in managed enterprise networks because it would tend to break intra-net name resolution when split horizon DNS is utilized. Some consumer VPN services and browser plug-ins may send DNS traffic to remote Internet destinations; in that case, such devices or networks can be excluded from this rule if this is expected behavior." + "Exclude DNS servers from this rule as this is expected behavior. Endpoints usually query local DNS servers defined in their DHCP scopes, but this may be overridden if a user configures their endpoint to use a remote DNS server. This is uncommon in managed enterprise networks because it could break intranet name resolution when split horizon DNS is utilized. Some consumer VPN services and browser plug-ins may send DNS traffic to remote Internet destinations. In that case, such devices or networks can be excluded from this rule when this is expected behavior." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "DNS Activity to the Internet", "query": "destination.port:53 and (\n network.direction: outbound or (\n source.ip:(10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip:( 169.254.169.254/32 or 127.0.0.53/32 or 10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16 or 224.0.0.251 or ff02\\:\\:fb or 255.255.255.255 )\n )\n)\n", "references": [ @@ -22,7 +22,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ftp_file_transfer_protocol_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ftp_file_transfer_protocol_activity_to_the_internet.json index ef7b39412c808..54daf8a2091a7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ftp_file_transfer_protocol_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ftp_file_transfer_protocol_activity_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that may indicate the use of FTP network connections to the Internet.\nThe File Transfer Protocol (FTP) has been around in its current form since the\n1980's. It can be an efficient and normal procedure on your network to send and\nreceive files. Because it is common and efficient, adversaries will also often\nuse this protocol to ex-filtrate data from your network or download new tools.\nAdditionally, FTP is a plain-text protocol which may expose your user name and\npassword, if intercepted. FTP activity involving servers subject to regulations or compliance standards may be unauthorized.\n", + "description": "This rule detects events that may indicate the use of FTP network connections to the Internet.\nThe File Transfer Protocol (FTP) has been around in its current form since the\n1980s. It can be a common and efficient procedure on your network to send and\nreceive files. Because of this, adversaries will also often use this protocol\nto exfiltrate data from your network or download new tools. Additionally, FTP\nis a plain-text protocol which, if intercepted, may expose usernames and\npasswords. FTP activity involving servers subject to regulations or compliance\nstandards may be unauthorized.\n", "false_positives": [ - "FTP servers should be excluded from this rule as this is expected behavior for them. Some business work-flows may use FTP for data exchange. These work-flows often have expected characteristics such as users, sources and destinations. FTP activity involving an unusual source or destination may be more suspicious. FTP activity involving a production server that has no known associated FTP work-flow or business requirement is often suspicious. NEW NEW" + "FTP servers should be excluded from this rule as this is expected behavior. Some business workflows may use FTP for data exchange. These workflows often have expected characteristics such as users, sources, and destinations. FTP activity involving an unusual source or destination may be more suspicious. FTP activity involving a production server that has no known associated FTP workflow or business requirement is often suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "FTP (File Transfer Protocol) Activity to the Internet", "query": "network.transport: tcp and destination.port: (20 or 21) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json index 2700eae977482..d01006a225886 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that use common ports for IRC to the Internet. IRC (Internet Relay Chat)\nis a common protocol that can be used chat and file transfer. This protocol\nalso makes a good candidate for remote control of malware and data transfer in\nand out of a network.\n", + "description": "This rule detects events that use common ports for Internet Relay Chat (IRC) to the Internet.\nIRC is a common protocol that can be used for chat and file transfers. This\nprotocol is also a good candidate for remote control of malware and data\ntransfers to and from a network.\n", "false_positives": [ - "IRC activity may be normal behavior for developers and engineers but is unusual for non-engineering end users. IRC activity involving an unusual source or destination may be more suspicious. IRC activity involving a production server is often suspicious. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATted web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired. Some legacy applications may use these ports but this is very uncommon and usually appears only in local traffic using private IPs which this rule does not match." + "IRC activity may be normal behavior for developers and engineers but is unusual for non-engineering end users. IRC activity involving an unusual source or destination may be more suspicious. IRC activity involving a production server is often suspicious. Because these ports are in the ephemeral range, this rule may false under certain conditions, such as when a NAT-ed web server replies to a client which has used a port in the range by coincidence. In this case, these servers can be excluded. Some legacy applications may use these ports, but this is very uncommon and usually only appears in local traffic using private IPs, which does not match this rule's conditions." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "IRC (Internet Relay Chat) Protocol Activity to the Internet", "query": "network.transport: tcp and destination.port:(6667 or 6697) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_nat_traversal_port_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_nat_traversal_port_activity.json index e87e296017a36..c66a9e9d77fe4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_nat_traversal_port_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_nat_traversal_port_activity.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that could be describing IPSEC NAT Traversal traffic.\nIPSEC is a VPN technology that allows one system to talk to another using\nencrypted tunnels. NAT Traversal enables these tunnels to communicate over\nthe Internet where one of the sides is behind a NAT router gateway. This may\nbe common on your network, but this technique is also used by threat actors\nto avoid detection.\n", + "description": "This rule detects events that could be describing IPSEC NAT Traversal traffic.\nIPSEC is a VPN technology that allows one system to talk to another using\nencrypted tunnels. NAT Traversal enables these tunnels to communicate over the\nInternet where one of the sides is behind a NAT router gateway. This may be\ncommon on your network, but this technique is also used by threat actors to\navoid detection.\n", "false_positives": [ - "Some networks may utilize these protocols but usage that is unfamiliar to local network administrators can be unexpected and suspicious. Because this port is in the ephemeral range, this rule may false under certain conditions such as when a application server with a public IP address replies to a client which has used a UDP port in the range by coincidence. This is uncommon but such servers can be excluded if desired." + "Some networks may utilize these protocols but usage that is unfamiliar to local network administrators can be unexpected and suspicious. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server with a public IP address replies to a client which has used a UDP port in the range by coincidence. This is uncommon but such servers can be excluded." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "IPSEC NAT Traversal Port Activity", "query": "network.transport: udp and destination.port: 4500", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_26_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_26_activity.json index 352fc5e44dc80..7b26141898532 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_26_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_26_activity.json @@ -1,18 +1,18 @@ { - "description": "This signal detects events that may indicate use of SMTP on TCP port 26. This\nport is commonly used by several popular mail transfer agents to deconflict\nwith the default SMTP port 25. This port has also been used by a malware family\ncalled BadPatch for command and control of Windows systems.\n", + "description": "This rule detects events that may indicate use of SMTP on TCP port 26. This\nport is commonly used by several popular mail transfer agents to deconflict\nwith the default SMTP port 25. This port has also been used by a malware family\ncalled BadPatch for command and control of Windows systems.\n", "false_positives": [ - "Servers that process email traffic may cause false positives and should be excluded from this rule as this is expected behavior for them." + "Servers that process email traffic may cause false positives and should be excluded from this rule as this is expected behavior." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SMTP on Port 26/TCP", - "query": "network.transport: tcp and destination.port: 26", + "query": "network.transport: tcp and destination.port: 26\n", "references": [ "https://unit42.paloaltonetworks.com/unit42-badpatch/", "https://isc.sans.edu/forums/diary/Next+up+whats+up+with+TCP+port+26/25564/" @@ -22,7 +22,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_8000_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_8000_activity_to_the_internet.json index 2b3d08a7c80d9..7551119cd5a84 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_8000_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_port_8000_activity_to_the_internet.json @@ -1,16 +1,16 @@ { "description": "TCP Port 8000 is commonly used for development environments of web server\nsoftware. It generally should not be exposed directly to the Internet. If you are\nrunning software like this on the Internet, you should consider placing it behind\na reverse proxy.\n", "false_positives": [ - "Because this port is in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired. Some applications may use this port but this is very uncommon and usually appears in local traffic using private IPs which this rule does not match. Some cloud environments, particularly development environments, may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet." + "Because this port is in the ephemeral range, this rule may false under certain conditions, such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded. Some applications may use this port but this is very uncommon and usually appears in local traffic using private IPs, which this rule does not match. Some cloud environments, particularly development environments, may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "TCP Port 8000 Activity to the Internet", "query": "network.transport: tcp and destination.port: 8000 and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_pptp_point_to_point_tunneling_protocol_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_pptp_point_to_point_tunneling_protocol_activity.json index b008ca2c2bee6..eeb38f756c67a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_pptp_point_to_point_tunneling_protocol_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_pptp_point_to_point_tunneling_protocol_activity.json @@ -1,24 +1,24 @@ { - "description": "This signal detects events that may indicate use of a PPTP VPN connection. Some threat actors use these types of connections to tunnel their traffic while avoiding detection.", + "description": "This rule detects events that may indicate use of a PPTP VPN connection. Some\nthreat actors use these types of connections to tunnel their traffic while\navoiding detection.\n", "false_positives": [ - "Some networks may utilize PPTP protocols but this is uncommon as more modern VPN technologies are available. Usage that is unfamiliar to local network administrators can be unexpected and suspicious. Torrenting applications may use this port. Because this port is in the ephemeral range, this rule may false under certain conditions such as when an application server with replies to a client which has used this port by coincidence. This is uncommon but such servers can be excluded if desired." + "Some networks may utilize PPTP protocols but this is uncommon as more modern VPN technologies are available. Usage that is unfamiliar to local network administrators can be unexpected and suspicious. Torrenting applications may use this port. Because this port is in the ephemeral range, this rule may false under certain conditions, such as when an application server replies to a client that used this port by coincidence. This is uncommon but such servers can be excluded." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "PPTP (Point to Point Tunneling Protocol) Activity", - "query": "network.transport: tcp and destination.port: 1723", + "query": "network.transport: tcp and destination.port: 1723\n", "risk_score": 21, "rule_id": "d2053495-8fe7-4168-b3df-dad844046be3", "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_proxy_port_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_proxy_port_activity_to_the_internet.json index f7c6ffddcaf9e..981a7bdffcfed 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_proxy_port_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_proxy_port_activity_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that may describe network events of proxy use to the\nInternet. It includes popular HTTP proxy ports and SOCKS proxy ports. Typically\nenvironments will use an internal IP address for a proxy server. It can also\nbe used to circumvent network controls and detection mechanisms.\n", + "description": "This rule detects events that may describe network events of proxy use to the\nInternet. It includes popular HTTP proxy ports and SOCKS proxy ports. Typically,\nenvironments will use an internal IP address for a proxy server. It can also\nbe used to circumvent network controls and detection mechanisms.\n", "false_positives": [ - "Some proxied applications may use these ports but this usually occurs in local traffic using private IPs which this rule does not match. Proxies are widely used as a security technology but in enterprise environments this is usually local traffic which this rule does not match. Internet proxy services using these ports can be white-listed if desired. Some screen recording applications may use these ports. Proxy port activity involving an unusual source or destination may be more suspicious. Some cloud environments may use this port when VPNs or direct connects are not in use and cloud instances are accessed across the Internet. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired." + "Some proxied applications may use these ports but this usually occurs in local traffic using private IPs\n which this rule does not match. Proxies are widely used as a security technology but in enterprise environments\n this is usually local traffic which this rule does not match. Internet proxy services using these ports can be\n white-listed if desired. Some screen recording applications may use these ports. Proxy port activity involving\n an unusual source or destination may be more suspicious. Some cloud environments may use this port when VPNs or\n direct connects are not in use and cloud instances are accessed across the Internet. Because these ports are in\n the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a\n client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "Proxy Port Activity to the Internet", "query": "network.transport: tcp and destination.port: (3128 or 8080 or 1080) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json index e3853c30e6ad9..504df93f2f8ed 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of RDP traffic\nfrom the Internet. RDP is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of RDP traffic\nfrom the Internet. RDP is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "false_positives": [ - "Some network security policies allow RDP directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. RDP services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only RDP gateways, bastions or jump servers may be expected expose RDP directly to the Internet and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." + " Some network security policies allow RDP directly from the Internet but usage that is unfamiliar to\n server or network owners can be unexpected and suspicious. RDP services may be exposed directly to the\n Internet in some networks such as cloud environments. In such cases, only RDP gateways, bastions or jump\n servers may be expected expose RDP directly to the Internet and can be exempted from this rule. RDP may\n be required by some work-flows such as remote access and support for specialized software products and\n servers. Such work-flows are usually known and not unexpected." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "RDP (Remote Desktop Protocol) from the Internet", "query": "network.transport: tcp and destination.port: 3389 and (\n network.direction: inbound or (\n not source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n and destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_to_the_internet.json index 55b9716af9346..2d9fa6ba06dfd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of RDP traffic\nto the Internet. RDP is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of RDP traffic\nto the Internet. RDP is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "false_positives": [ - "RDP connections may be made directly to Internet destinations in order to access Windows cloud server instances but such connections are usually made only by engineers. In such cases, only RDP gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. RDP may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." + "RDP connections may be made directly to Internet destinations in order to access\n Windows cloud server instances but such connections are usually made only by engineers.\n In such cases, only RDP gateways, bastions or jump servers may be expected Internet\n destinations and can be exempted from this rule. RDP may be required by some work-flows\n such as remote access and support for specialized software products and servers. Such\n work-flows are usually known and not unexpected. Usage that is unfamiliar to server or\n network owners can be unexpected and suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "RDP (Remote Desktop Protocol) to the Internet", "query": "network.transport: tcp and destination.port: 3389 and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_from_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_from_the_internet.json index 1570d3d155fea..d50c79db81ba5 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_from_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_from_the_internet.json @@ -1,13 +1,13 @@ { - "description": "This signal detects network events that may indicate the use of RPC traffic\nfrom the Internet. RPC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of RPC traffic\nfrom the Internet. RPC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "RPC (Remote Procedure Call) from the Internet", "query": "network.transport: tcp and destination.port: 135 and (\n network.direction: inbound or (\n not source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 73, @@ -15,7 +15,7 @@ "severity": "high", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_to_the_internet.json index 91db97dabdd46..ade7b661a7909 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rpc_remote_procedure_call_to_the_internet.json @@ -1,13 +1,13 @@ { - "description": "This signal detects network events that may indicate the use of RPC traffic\nto the Internet. RPC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of RPC traffic\nto the Internet. RPC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "RPC (Remote Procedure Call) to the Internet", "query": "network.transport: tcp and destination.port: 135 and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 73, @@ -15,7 +15,7 @@ "severity": "high", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smb_windows_file_sharing_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smb_windows_file_sharing_activity_to_the_internet.json index 991c626c11d33..62c2fafb7404f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smb_windows_file_sharing_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smb_windows_file_sharing_activity_to_the_internet.json @@ -1,13 +1,13 @@ { - "description": "This signal detects network events that may indicate the use of Windows\nfile sharing (also called SMB or CIFS) traffic to the Internet. SMB is commonly\nused within networks to share files, printers, and other system resources amongst\ntrusted systems. It should almost never be directly exposed to the Internet, as\nit is frequently targeted and exploited by threat actors as an initial access\nor back-door vector or for data exfiltration.\n", + "description": "This rule detects network events that may indicate the use of Windows\nfile sharing (also called SMB or CIFS) traffic to the Internet. SMB is commonly\nused within networks to share files, printers, and other system resources amongst\ntrusted systems. It should almost never be directly exposed to the Internet, as\nit is frequently targeted and exploited by threat actors as an initial access\nor back-door vector or for data exfiltration.\n", "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SMB (Windows File Sharing) Activity to the Internet", "query": "network.transport: tcp and destination.port: (139 or 445) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 73, @@ -15,7 +15,7 @@ "severity": "high", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smtp_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smtp_to_the_internet.json index 68daf71d9992a..02fca5603910e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smtp_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_smtp_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that may describe SMTP traffic from internal\nhosts to a host across the Internet. In an enterprise network, there is typically\na dedicate host that is internal that could perform this function. It is also\nfrequently abused by threat actors for command and control or data exfiltration.\n", + "description": "This rule detects events that may describe SMTP traffic from internal\nhosts to a host across the Internet. In an enterprise network, there is typically\na dedicated internal host that performs this function. It is also\nfrequently abused by threat actors for command and control, or data exfiltration.\n", "false_positives": [ - "NATed servers that process email traffic may false and should be excluded from this rule as this is expected behavior for them. Consumer and / or personal devices may send email traffic to remote Internet destinations; in that case, such devices or networks can be excluded from this rule if this is expected behavior." + "NATed servers that process email traffic may false and should be excluded from this rule as this is expected behavior for them. Consumer and personal devices may send email traffic to remote Internet destinations. In this case, such devices or networks can be excluded from this rule if this is expected behavior." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SMTP to the Internet", "query": "network.transport: tcp and destination.port: (25 or 465 or 587) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_sql_server_port_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_sql_server_port_activity_to_the_internet.json index df779d47246a5..67e6a08ddf791 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_sql_server_port_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_sql_server_port_activity_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects events that may describe database traffic\n(MS SQL, Oracle, MySQL, and Postgresql) across the Internet. Databases\nshould almost never be directly exposed to the Internet, as they are\nfrequently targeted by threat actors to gain initial access to network resources.\n", + "description": "This rule detects events that may describe database traffic\n(MS SQL, Oracle, MySQL, and Postgresql) across the Internet. Databases\nshould almost never be directly exposed to the Internet, as they are\nfrequently targeted by threat actors to gain initial access to network resources.\n", "false_positives": [ - "Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used a port in the range by coincidence. In this case, such servers can be excluded if desired. Some cloud environments may use this port when VPNs or direct connects are not in use and database instances are accessed directly across the Internet." + "Because these ports are in the ephemeral range, this rule may false under certain conditions\n such as when a NATed web server replies to a client which has used a port in the range by\n coincidence. In this case, such servers can be excluded if desired. Some cloud environments may\n use this port when VPNs or direct connects are not in use and database instances are accessed\n directly across the Internet." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SQL Traffic to the Internet", "query": "network.transport: tcp and destination.port: (1433 or 1521 or 3336 or 5432) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_from_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_from_the_internet.json index 6c278700450b1..052600a0db68a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_from_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_from_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of SSH traffic\nfrom the Internet. SSH is commonly used by system administrators to remotely\ncontrol a system using the command line shell. If it is exposed to the Internet,\nit should be done with strong security controls as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of SSH traffic\nfrom the Internet. SSH is commonly used by system administrators to remotely\ncontrol a system using the command line shell. If it is exposed to the Internet,\nit should be done with strong security controls as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "false_positives": [ - "Some network security policies allow SSH directly from the Internet but usage that is unfamiliar to server or network owners can be unexpected and suspicious. SSH services may be exposed directly to the Internet in some networks such as cloud environments. In such cases, only SSH gateways, bastions or jump servers may be expected expose SSH directly to the Internet and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected." + "Some network security policies allow SSH directly from the Internet but usage that is\n unfamiliar to server or network owners can be unexpected and suspicious. SSH services may\n be exposed directly to the Internet in some networks such as cloud environments. In such\n cases, only SSH gateways, bastions or jump servers may be expected expose SSH directly to\n the Internet and can be exempted from this rule. SSH may be required by some work-flows\n such as remote access and support for specialized software products and servers. Such\n work-flows are usually known and not unexpected." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SSH (Secure Shell) from the Internet", "query": "network.transport: tcp and destination.port:22 and (\n network.direction: inbound or (\n not source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_to_the_internet.json index 63f2dbc8a34f1..e3c3135c9240d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_ssh_secure_shell_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of SSH traffic\nfrom the Internet. SSH is commonly used by system administrators to remotely\ncontrol a system using the command line shell. If it is exposed to the Internet,\nit should be done with strong security controls as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of SSH traffic\nfrom the Internet. SSH is commonly used by system administrators to remotely\ncontrol a system using the command line shell. If it is exposed to the Internet,\nit should be done with strong security controls as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "false_positives": [ - "SSH connections may be made directly to Internet destinations in order to access Linux cloud server instances but such connections are usually made only by engineers. In such cases, only SSH gateways, bastions or jump servers may be expected Internet destinations and can be exempted from this rule. SSH may be required by some work-flows such as remote access and support for specialized software products and servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." + "SSH connections may be made directly to Internet destinations in order to access Linux\n cloud server instances but such connections are usually made only by engineers. In such cases,\n only SSH gateways, bastions or jump servers may be expected Internet destinations and can be\n exempted from this rule. SSH may be required by some work-flows such as remote access and support\n for specialized software products and servers. Such work-flows are usually known and not unexpected.\n Usage that is unfamiliar to server or network owners can be unexpected and suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "SSH (Secure Shell) to the Internet", "query": "network.transport: tcp and destination.port:22 and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 21, @@ -18,7 +18,7 @@ "severity": "low", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_telnet_port_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_telnet_port_activity.json index 0d28f0ea53d9a..c05791c8a107d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_telnet_port_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_telnet_port_activity.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of Telnet traffic.\nTelnet is commonly used by system administrators to remotely control older or embed ed\nsystems using the command line shell. It should almost never be directly exposed to\nthe Internet, as it is frequently targeted and exploited by threat actors as an\ninitial access or back-door vector. As a plain-text protocol, it may also expose\n", + "description": "This rule detects network events that may indicate the use of Telnet traffic.\nTelnet is commonly used by system administrators to remotely control older or embed ed\nsystems using the command line shell. It should almost never be directly exposed to\nthe Internet, as it is frequently targeted and exploited by threat actors as an\ninitial access or back-door vector. As a plain-text protocol, it may also expose\nusernames and passwords to anyone capable of observing the traffic.\n", "false_positives": [ - "IoT (Internet of Things) devices and networks may use telnet and can be excluded if desired. Some business work-flows may use Telnet for administration of older devices. These often have a predictable behavior. Telnet activity involving an unusual source or destination may be more suspicious. Telnet activity involving a production server that has no known associated Telnet work-flow or business requirement is often suspicious." + "IoT (Internet of Things) devices and networks may use telnet and can be excluded if\n desired. Some business work-flows may use Telnet for administration of older devices. These\n often have a predictable behavior. Telnet activity involving an unusual source or destination\n may be more suspicious. Telnet activity involving a production server that has no known\n associated Telnet work-flow or business requirement is often suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "Telnet Port Activity", "query": "network.transport: tcp and destination.port: 23", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_tor_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_tor_activity_to_the_internet.json index 80893e9404f02..64397716eded2 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_tor_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_tor_activity_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of Tor traffic\nto the Internet. Tor is a network protocol that sends traffic through a\nseries of encrypted tunnels used to conceal a user's location and usage.\nTor may be used by threat actors as an alternate communication pathway to\nconceal the actor's identity and avoid detection.\n", + "description": "This rule detects network events that may indicate the use of Tor traffic\nto the Internet. Tor is a network protocol that sends traffic through a\nseries of encrypted tunnels used to conceal a user's location and usage.\nTor may be used by threat actors as an alternate communication pathway to\nconceal the actor's identity and avoid detection.\n", "false_positives": [ - "Tor client activity is uncommon in managed enterprise networks but may be common in unmanaged or public networks where few security policies apply. Because these ports are in the ephemeral range, this rule may false under certain conditions such as when a NATed web server replies to a client which has used one of these ports by coincidence. In this case, such servers can be excluded if desired." + "Tor client activity is uncommon in managed enterprise networks but may be common\n in unmanaged or public networks where few security policies apply. Because these ports\n are in the ephemeral range, this rule may false under certain conditions such as when a\n NATed web server replies to a client which has used one of these ports by coincidence.\n In this case, such servers can be excluded if desired." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "Tor Activity to the Internet", "query": "network.transport: tcp and destination.port: (9001 or 9030) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_from_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_from_the_internet.json index e64138dd053fa..dc4fbb281f762 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_from_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_from_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of VNC traffic\nfrom the Internet. VNC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of VNC traffic\nfrom the Internet. VNC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", "false_positives": [ - "VNC connections may be received directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work-flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." + "VNC connections may be received directly to Linux cloud server instances but\n such connections are usually made only by engineers. VNC is less common than SSH\n or RDP but may be required by some work-flows such as remote access and support\n for specialized software products or servers. Such work-flows are usually known\n and not unexpected. Usage that is unfamiliar to server or network owners can be\n unexpected and suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "VNC (Virtual Network Computing) from the Internet", "query": "network.transport: tcp and (destination.port >= 5800 and destination.port <= 5810) and (\n network.direction: inbound or (\n not source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 73, @@ -18,7 +18,7 @@ "severity": "high", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_to_the_internet.json index 8c43419c3ead5..7da5db39d9bfe 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_vnc_virtual_network_computing_to_the_internet.json @@ -1,16 +1,16 @@ { - "description": "This signal detects network events that may indicate the use of VNC traffic\nto the Internet. VNC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.\n", + "description": "This rule detects network events that may indicate the use of VNC traffic\nto the Internet. VNC is commonly used by system administrators to remotely\ncontrol a system for maintenance or to use shared resources. It should almost\nnever be directly exposed to the Internet, as it is frequently targeted and\nexploited by threat actors as an initial access or back-door vector.", "false_positives": [ - "VNC connections may be made directly to Linux cloud server instances but such connections are usually made only by engineers. VNC is less common than SSH or RDP but may be required by some work flows such as remote access and support for specialized software products or servers. Such work-flows are usually known and not unexpected. Usage that is unfamiliar to server or network owners can be unexpected and suspicious." + "VNC connections may be made directly to Linux cloud server instances but such\n connections are usually made only by engineers. VNC is less common than SSH or RDP\n but may be required by some work flows such as remote access and support for\n specialized software products or servers. Such work-flows are usually known and not\n unexpected. Usage that is unfamiliar to server or network owners can be unexpected\n and suspicious." ], "index": [ "auditbeat-*", - "endgame-*", "filebeat-*", "packetbeat-*", "winlogbeat-*" ], "language": "kuery", + "max_signals": 100, "name": "VNC (Virtual Network Computing) to the Internet", "query": "network.transport: tcp and (destination.port >= 5800 and destination.port <= 5810) and (\n network.direction: outbound or (\n source.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16) and\n not destination.ip: (10.0.0.0/8 or 172.16.0.0/12 or 192.168.0.0/16)\n )\n)\n", "risk_score": 47, @@ -18,7 +18,7 @@ "severity": "medium", "tags": [ "Elastic", - "network" + "Network" ], "threat": [ { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/notice.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/notice.ts index cd24d823b8cd6..a597220db752f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/notice.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/notice.ts @@ -9,8 +9,6 @@ * - windows_priv_escalation_via_accessibility_features.json * - windows_persistence_via_application_shimming.json * - windows_execution_via_trusted_developer_utilities.json - * - windows_execution_via_net_com_assemblies.json - * - windows_execution_via_connection_manager.json * * MIT License * diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json index 7975c30a4ea38..c08d910a3b355 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/null_user_agent.json @@ -1,7 +1,7 @@ { "description": "A request to a web application server contained no identifying user agent string.", "false_positives": [ - "Some normal applications and scripts may contain no user agent. Most legitimate web requests from the Internet contain a user agent string. Requests from web browsers almost always contain a user agent string. If the source is unexpected, or the user is unauthorized, or the request is unusual, these may be suspicious or malicious activity." + "Some normal applications and scripts may contain no user agent. Most legitimate web requests from the Internet contain a user agent string. Requests from web browsers almost always contain a user agent string. If the source is unexpected, the user unauthorized, or the request unusual, these may indicate suspicious or malicious activity." ], "filters": [ { @@ -25,7 +25,7 @@ "apm-*-transaction*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Web Application Suspicious Activity: No User Agent", "query": "url.path: *", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json index 44e112d09a45b..5c03c3a76e4a7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/sqlmap_user_agent.json @@ -1,12 +1,13 @@ { - "description": "This is an example of how to detect an unwanted web client user agent. This search matches the user agent for sqlmap 1.3.11 which is a popular FOSS tool for testing web applications for SQL injection vulnerabilities. ", + "description": "This is an example of how to detect an unwanted web client user agent. This search matches the user agent for sqlmap 1.3.11, which is a popular FOSS tool for testing web applications for SQL injection vulnerabilities.", "false_positives": [ - "This signal does not indicate that a SQL injection attack occurred, only that the sqlmap tool was used. Security scans and tests may result in these errors. If the source is not an authorized security tester, this is generally suspicious or malicious activity." + "This rule does not indicate that a SQL injection attack occurred, only that the `sqlmap` tool was used. Security scans and tests may result in these errors. If the source is not an authorized security tester, this is generally suspicious or malicious activity." ], "index": [ "apm-*-transaction*" ], "language": "kuery", + "max_signals": 100, "name": "Web Application Suspicious Activity: sqlmap User Agent", "query": "user_agent.original:\"sqlmap/1.3.11#stable (http://sqlmap.org)\"", "references": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json deleted file mode 100644 index 9b3784345b013..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_background_intelligent_transfer_service_bits_connecting_to_the_internet.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "description": "Adversaries may abuse the Background Intelligent Transfer Service (BITS) to download, execute, or clean up after performing a malicious action.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Background Intelligent Transfer Service (BITS) connecting to the Internet", - "query": "process.name:bitsadmin.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 21, - "rule_id": "7edadee3-98ae-472c-b1c4-8c0a2c4877cc", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" - }, - "technique": [ - { - "id": "T1197", - "name": "BITS Jobs", - "reference": "https://attack.mitre.org/techniques/T1197/" - } - ] - }, - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [ - { - "id": "T1197", - "name": "BITS Jobs", - "reference": "https://attack.mitre.org/techniques/T1197/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_connecting_to_the_internet.json deleted file mode 100644 index 0a960fc427d7b..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_certutil_connecting_to_the_internet.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies certutil.exe making a network connection. Adversaries could abuse certutil.exe to download a certificate, or malware, from a remote URL.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Certutil Network Connection", - "query": "process.name:certutil.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", - "risk_score": 21, - "rule_id": "1a2cf526-6784-4c51-a2b9-f0adcc05d85c", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0011", - "name": "Command and Control", - "reference": "https://attack.mitre.org/tactics/TA0011/" - }, - "technique": [ - { - "id": "T1105", - "name": "Remote File Copy", - "reference": "https://attack.mitre.org/techniques/T1105/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json index 87dbd4cd70777..7ab8034ef4083 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_prompt_connecting_to_the_internet.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Command Prompt Network Connection", "query": "process.name:cmd.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_internet_explorer.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_internet_explorer.json deleted file mode 100644 index a214ab4544b97..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_internet_explorer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies a suspicious parent child process relationship with cmd.exe spawning form Internet Explorer.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Internet Explorer spawning cmd.exe", - "query": "process.parent.name:iexplore.exe and process.name:cmd.exe", - "risk_score": 21, - "rule_id": "7a6e1e81-deae-4cf6-b807-9a768fff3c06", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1059", - "name": "Command-Line Interface", - "reference": "https://attack.mitre.org/techniques/T1059/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json index 187cc9d344902..d914fd2e91a07 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_powershell.json @@ -4,8 +4,8 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, - "name": "PowerShell spawning cmd.exe", + "max_signals": 100, + "name": "PowerShell spawning Cmd", "query": "process.parent.name:powershell.exe and process.name:cmd.exe", "risk_score": 21, "rule_id": "0f616aee-8161-4120-857e-742366f5eeb3", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json index 81114bf8b8766..b7f0c54fedf62 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_command_shell_started_by_svchost.json @@ -4,8 +4,8 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, - "name": "Svchost spawning cmd.exe", + "max_signals": 100, + "name": "Svchost spawning Cmd", "query": "process.parent.name:svchost.exe and process.name:cmd.exe", "risk_score": 21, "rule_id": "fd7a6052-58fa-4397-93c3-4795249ccfa2", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json index 7c999c1fc1e03..86242fd1081a1 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_defense_evasion_via_filter_manager.json @@ -4,15 +4,14 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Potential Evasion via Filter Manager", - "query": "event.code:1 and process.name:fltmc.exe", + "query": "event.code:1 and process.name:fltMC.exe", "risk_score": 21, "rule_id": "06dceabf-adca-48af-ac79-ffdf4c3b1e9a", "severity": "low", "tags": [ "Elastic", - "D-SA", "Windows" ], "threat": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json index 62c8942dda9c3..7789b0723b3fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_compiled_html_file.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Process Activity via Compiled HTML File", "query": "event.code:1 and process.name:hh.exe", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_connection_manager.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_connection_manager.json deleted file mode 100644 index 657487232fe81..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_connection_manager.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "description": "Various Windows utilities may be used to execute commands, possibly without invoking cmd.exe, including the Program Compatibility Assistant (pcalua.exe) or forfiles.exe.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Indirect Command Execution", - "query": "event.code:1 and process.parent.name:pcalua.exe or (process.name:bash.exe or process.name:forfiles.exe or process.name:pcalua.exe)", - "risk_score": 21, - "rule_id": "f2728299-167a-489c-913c-2e0955ac3c40", - "severity": "low", - "tags": [ - "Elastic", - "D-SA", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" - }, - "technique": [ - { - "id": "T1202", - "name": "Indirect Command Execution", - "reference": "https://attack.mitre.org/techniques/T1202/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json deleted file mode 100644 index 80d91fa515342..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_net_com_assemblies.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "description": "Adversaries can use Regsvcs.exe and Regasm.exe to proxy execution of code through a trusted Windows utility.", - "false_positives": [ - "Administrators may use the command prompt for regular administrative tasks. It's important to baseline your environment for network connections being made from the command prompt to determine any abnormal use of this tool." - ], - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Execution via Regsvcs/Regasm", - "query": "event.code:1 and (process.name:regasm.exe or process.name:regsvcs.exe)", - "risk_score": 21, - "rule_id": "5c12412f-602c-4120-8c4f-69d723dbba04", - "severity": "low", - "tags": [ - "Elastic", - "D-SA", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1121", - "name": "Regsvcs/Regasm", - "reference": "https://attack.mitre.org/techniques/T1121/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_regsvr32.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_regsvr32.json index 6b2c54d527963..e8e7ddfc168dc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_regsvr32.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_regsvr32.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Suspicious Script Object Execution", "query": "event.code: 1 and scrobj.dll and (process.name:certutil.exe or process.name:regsvr32.exe or process.name:rundll32.exe)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json index e722d311b86c7..bd2376a0897f4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_execution_via_trusted_developer_utilities.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Trusted Developer Application Usage", "query": "event.code:1 and (process.name:MSBuild.exe or process.name:msxsl.exe)", "risk_score": 21, @@ -15,7 +15,6 @@ "severity": "low", "tags": [ "Elastic", - "D-SA", "Windows" ], "threat": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json index 2b4d774281b84..32fa953388be3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_html_help_executable_program_connecting_to_the_internet.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Network Connection via Compiled HTML File", "query": "process.name:hh.exe and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json index 8a4cb75588bff..0015371f03067 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_misc_lolbin_connecting_to_the_internet.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Network Connection via Signed Binary", "query": "(process.name:expand.exe or process.name:extrac.exe or process.name:ieexec.exe or process.name:makecab.exe) and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_activity_by_the_system_account.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_activity_by_the_system_account.json deleted file mode 100644 index 5b3257daec8fb..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_net_command_activity_by_the_system_account.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "description": "Identifies attempts to create new users via the SYSTEM account.", - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Net command via SYSTEM account", - "query": "process.name: (net.exe or net1.exe) and user.name:SYSTEM", - "risk_score": 21, - "rule_id": "c3f5dc81-a8b4-4144-95a7-d0a818d7355d", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0003", - "name": "Persistence", - "reference": "https://attack.mitre.org/tactics/TA0003/" - }, - "technique": [ - { - "id": "T1136", - "name": "Create Account", - "reference": "https://attack.mitre.org/techniques/T1136/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json index 2c10382cdbc7c..6eaac7b9e6cab 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_persistence_via_application_shimming.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Potential Application Shimming via Sdbinst", "query": "event.code:1 and process.name:sdbinst.exe", "risk_score": 21, @@ -12,7 +12,6 @@ "severity": "low", "tags": [ "Elastic", - "D-SA", "Windows" ], "threat": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json index 23d05aaf526e3..b2463633b0c5d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_priv_escalation_via_accessibility_features.json @@ -4,7 +4,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Potential Modification of Accessibility Binaries", "query": "event.code:1 and process.parent.name:winlogon.exe and (process.name:atbroker.exe or process.name:displayswitch.exe or process.name:magnify.exe or process.name:narrator.exe or process.name:osk.exe or process.name:sethc.exe or process.name:utilman.exe)", "risk_score": 21, @@ -12,7 +12,6 @@ "severity": "low", "tags": [ "Elastic", - "D-SA", "Windows" ], "threat": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json index 5f5215ddff8c6..a0542ef59d8cd 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_discovery_via_tasklist_command.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Process Discovery via Tasklist", "query": "event.code:1 and process.name:tasklist.exe", "risk_score": 21, @@ -15,7 +15,6 @@ "severity": "low", "tags": [ "Elastic", - "D-SA", "Windows" ], "threat": [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_execution_via_wmi.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_execution_via_wmi.json deleted file mode 100644 index 6d6343330a7ff..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_process_execution_via_wmi.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "description": "Identifies use of scrcons.exe, which is a Windows Management Instrumentation (WMI) Standard Event Consumer scripting application.", - "false_positives": [ - " Windows Management Instrumentation (WMI) processes can be used for an array of administrative capabilities. It's important to baseline your environment to determine any abnormal use of this tool." - ], - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Execution via Scrcons", - "query": "process.name:scrcons.exe", - "risk_score": 21, - "rule_id": "7e6cd4b9-6346-4683-b3e6-6a3e66f3208f", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1047", - "name": "Windows Management Instrumentation", - "reference": "https://attack.mitre.org/techniques/T1047/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json index b35e016be15d7..d0f2e809c1aa7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_register_server_program_connecting_to_the_internet.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Network Connection via Regsvr", "query": "(process.name:regsvr32.exe or process.name:regsvr64.exe) and event.action:\"Network connection detected (rule: NetworkConnect)\" and not destination.ip:169.254.169.254/32 and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution.json index cf5135cc490eb..be4ccef2a0887 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Execution via Signed Binary", "query": "event.code:1 and http and (process.name:certutil.exe or process.name:msiexec.exe)", "risk_score": 21, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution_download.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution_download.json deleted file mode 100644 index 117a40d0fdcee..0000000000000 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_signed_binary_proxy_execution_download.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "description": "Binaries signed with trusted digital certificates can execute on Windows systems protected by digital signature validation. Adversaries may use these binaries to 'live off the land' and execute malicious files that could bypass application whitelisting and signature validation.", - "false_positives": [ - "Security testing may produce events like this. Activity of this kind performed by non-engineers and ordinary users is unusual." - ], - "index": [ - "winlogbeat-*" - ], - "language": "kuery", - "max_signals": 33, - "name": "Potential Download via Signed Binary", - "query": " event.code:3 and http and (process.name:certutil.exe or process.name:replace.exe)", - "risk_score": 21, - "rule_id": "68ecc190-cce2-4021-b976-c7c846ac0a00", - "severity": "low", - "tags": [ - "Elastic", - "Windows" - ], - "threat": [ - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0005", - "name": "Defense Evasion", - "reference": "https://attack.mitre.org/tactics/TA0005/" - }, - "technique": [ - { - "id": "T1218", - "name": "Signed Binary Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1218/" - } - ] - }, - { - "framework": "MITRE ATT&CK", - "tactic": { - "id": "TA0002", - "name": "Execution", - "reference": "https://attack.mitre.org/tactics/TA0002/" - }, - "technique": [ - { - "id": "T1218", - "name": "Signed Binary Proxy Execution", - "reference": "https://attack.mitre.org/techniques/T1218/" - } - ] - } - ], - "type": "query", - "version": 1 -} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_suspicious_process_started_by_a_script.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_suspicious_process_started_by_a_script.json index 3691c59d784fb..235a04f8063fc 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_suspicious_process_started_by_a_script.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_suspicious_process_started_by_a_script.json @@ -7,8 +7,8 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, - "name": "Suspicious Process Spawning from Script Interpreter", + "max_signals": 100, + "name": "Suspicious Process spawning from Script Interpreter", "query": "(process.parent.name:cmd.exe or process.parent.name:cscript.exe or process.parent.name:mshta.exe or process.parent.name:powershell.exe or process.parent.name:rundll32.exe or process.parent.name:wscript.exe or process.parent.name:wmiprvse.exe) and (process.name:bitsadmin.exe or process.name:certutil.exe or mshta.exe or process.name:nslookup.exe or process.name:schtasks.exe) and event.code:1", "risk_score": 21, "rule_id": "89db767d-99f9-479f-8052-9205fd3090c4", diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json index 3618d304dc32a..678160f945ba0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/windows_whoami_command_activity.json @@ -7,7 +7,7 @@ "winlogbeat-*" ], "language": "kuery", - "max_signals": 33, + "max_signals": 100, "name": "Whoami Process Activity", "query": "process.name:whoami.exe and event.code:1", "risk_score": 21, From ff41f4e56f1dfe9a3d2fcd9ccd508de6eb9b1104 Mon Sep 17 00:00:00 2001 From: Corey Robertson <corey.robertson@elastic.co> Date: Tue, 4 Feb 2020 21:25:43 -0500 Subject: [PATCH 59/60] Only change handlers as the element changes (#56782) Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> --- .../public/components/element_wrapper/index.js | 6 ++---- .../components/element_wrapper/lib/handlers.js | 4 ++-- .../canvas/public/state/actions/elements.js | 4 ++-- .../canvas/public/state/reducers/elements.js | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js index 60c7e731691fa..85882377b7684 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/index.js @@ -58,12 +58,10 @@ function selectorFactory(dispatch) { export const ElementWrapper = compose( connectAdvanced(selectorFactory), withPropsOnChange( - (props, nextProps) => - !isEqual(props.element, nextProps.element) || - !isEqual(props.selectedPage, nextProps.selectedPage), + (props, nextProps) => !isEqual(props.element, nextProps.element), props => { const { element, createHandlers } = props; - const handlers = createHandlers(element, props.selectedPage); + const handlers = createHandlers(element); // this removes element and createHandlers from passed props return { handlers }; } diff --git a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js index d71c5ead2c802..8ea90974e2c53 100644 --- a/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js +++ b/x-pack/legacy/plugins/canvas/public/components/element_wrapper/lib/handlers.js @@ -16,7 +16,7 @@ export const createHandlers = dispatch => { let oldElement; let completeFn = () => {}; - return (element, pageId) => { + return element => { // reset isComplete when element changes if (!isEqual(oldElement, element)) { isComplete = false; @@ -25,7 +25,7 @@ export const createHandlers = dispatch => { return { setFilter(text) { - dispatch(setFilter(text, element.id, pageId, true)); + dispatch(setFilter(text, element.id, true)); }, getFilter() { diff --git a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js index 7b7e87b027af5..8b49d16e87b21 100644 --- a/x-pack/legacy/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/legacy/plugins/canvas/public/state/actions/elements.js @@ -254,9 +254,9 @@ export const removeElements = createThunk( export const setFilter = createThunk( 'setFilter', - ({ dispatch }, filter, elementId, pageId, doRender = true) => { + ({ dispatch }, filter, elementId, doRender = true) => { const _setFilter = createAction('setFilter'); - dispatch(_setFilter({ filter, elementId, pageId })); + dispatch(_setFilter({ filter, elementId })); if (doRender === true) { dispatch(fetchAllRenderables()); diff --git a/x-pack/legacy/plugins/canvas/public/state/reducers/elements.js b/x-pack/legacy/plugins/canvas/public/state/reducers/elements.js index c7e8a5c2ff2d8..630694a860aad 100644 --- a/x-pack/legacy/plugins/canvas/public/state/reducers/elements.js +++ b/x-pack/legacy/plugins/canvas/public/state/reducers/elements.js @@ -87,6 +87,18 @@ const trimElement = ({ id, position, expression, filter }) => ({ ...(filter !== void 0 && { filter }), }); +const getPageWithElementId = (workpad, elementId) => { + const matchingPage = workpad.pages.find(page => + page.elements.map(element => element.id).includes(elementId) + ); + + if (matchingPage) { + return matchingPage.id; + } + + return undefined; +}; + export const elementsReducer = handleActions( { // TODO: This takes the entire element, which is not necessary, it could just take the id. @@ -95,7 +107,8 @@ export const elementsReducer = handleActions( return assignNodeProperties(workpadState, pageId, elementId, { expression }); }, [actions.setFilter]: (workpadState, { payload }) => { - const { filter, pageId, elementId } = payload; + const { filter, elementId } = payload; + const pageId = getPageWithElementId(workpadState, elementId); return assignNodeProperties(workpadState, pageId, elementId, { filter }); }, [actions.setMultiplePositions]: (workpadState, { payload }) => From 117bfb5cc72fdb85afa411fa349b5e1735861147 Mon Sep 17 00:00:00 2001 From: Andrew Cholakian <andrew@andrewvc.com> Date: Tue, 4 Feb 2020 22:07:37 -0600 Subject: [PATCH 60/60] Improve pull request template proposal (#56756) Looking through Kibana PRs many currently ignore the check boxes or just delete them. The goal here is to make the process easier, delete some unused options and align the checkboxes with what's most useful. We've had some discussions on the Uptime team about ways the PR template could be potentially improved. These changes are based on an extended discussion we had on the topic. We'd love to hear if other teams would be OK with these recommended changes. The changes here are: * Allow authors to just delete unnecessary items rather than strike through. Adding all the `~~` in markdown is painful * Remove the unnecessary checkbox for release notes, the build bot catches this and blocks merges without that being set. * Add a checkbox for testing in various browser sizes / mobile responsive devices * Move IE checkbox to the bottom of the list since it's seldom checked and makes the checklist seem daunting --- .github/PULL_REQUEST_TEMPLATE.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5a80a11c5cbea..4cc0c8016f1d0 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,16 +4,15 @@ Summarize your PR. If it involves visual changes include a screenshot or gif. ### Checklist -Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR. +Delete any items that are not applicable to this PR. -- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) - [ ] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md) - [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios - [ ] This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist) +- [ ] This renders correctly on smaller devices using a responsive layout. (You can test this [in your browser](https://www.browserstack.com/guide/responsive-testing-on-local-server) +- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) -- [ ] This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process) -
- + ; -export const MonitorChartsComponent = ({ - data, - mean, - range, - monitorId, - dateRangeStart, - dateRangeEnd, - loading, -}: Props) => { - const [getUrlParams] = useUrlParams(); +export const MonitorChartsComponent = ({ data, mean, range, monitorId, loading }: Props) => { if (data && data.monitorChartsData) { const { monitorChartsData: { locationDurationLines }, } = data; - const { absoluteDateRangeStart, absoluteDateRangeEnd } = getUrlParams(); - return ( @@ -58,15 +44,7 @@ export const MonitorChartsComponent = ({ /> - + ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap index f779efca7b18a..3655db5aaff1e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/__snapshots__/monitor_list.test.tsx.snap @@ -106,6 +106,560 @@ exports[`MonitorList component renders a no items message when no data is provid `; exports[`MonitorList component renders the monitor list 1`] = ` +.c1 { + padding-left: 17px; +} + +.c2 { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +@media (max-width:574px) { + .c0 { + min-width: 230px; + } +} + +
+
+ Monitor status +
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + Status + +
+
+
+ + Name + +
+
+
+ + Url + +
+
+
+ + Downtime history + +
+
+
+ +
+
+
+ Status +
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+ 1897 Yr ago +
+
+
+
+
+
+
+ in 0/1 Location +
+
+
+
+
+
+ Name +
+ +
+
+ Url +
+
+ +
+
+
+ +
+
+ -- +
+
+
+
+
+
+ +
+
+
+ Status +
+
+
+
+
+
+
+ +
+
+
+
+ + +
+
+ 1895 Yr ago +
+
+
+
+
+
+
+ in 1/1 Location +
+
+
+
+
+
+ Name +
+ +
+
+ Url +
+
+ +
+
+
+ +
+
+ -- +
+
+
+
+
+
+ +
+
+
+
+
+
+
+ +
+
+ +
+
+
+`; + +exports[`MonitorList component shallow renders the monitor list 1`] = ` `; -exports[`MonitorList component renders the monitor list 1`] = ` +exports[`MonitorListPagination component renders the monitor list 1`] = ` { let result: MonitorSummaryResult; @@ -81,11 +82,9 @@ describe('MonitorList component', () => { }; }); - it('renders the monitor list', () => { + it('shallow renders the monitor list', () => { const component = shallowWithIntl( { it('renders a no items message when no data is provided', () => { const component = shallowWithIntl( { ); expect(component).toMatchSnapshot(); }); + + it('renders the monitor list', () => { + const component = renderWithIntl( + renderWithRouter( + + ) + ); + + expect(component).toMatchSnapshot(); + }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx index a172513409455..ff54e61006156 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/__tests__/monitor_list_pagination.test.tsx @@ -13,7 +13,7 @@ import { } from '../../../../../common/graphql/types'; import { MonitorListComponent } from '../monitor_list'; -describe('MonitorList component', () => { +describe('MonitorListPagination component', () => { let result: MonitorSummaryResult; beforeEach(() => { @@ -98,8 +98,6 @@ describe('MonitorList component', () => { it('renders the monitor list', () => { const component = shallowWithIntl( { it('renders a no items message when no data is provided', () => { const component = shallowWithIntl( { - const { - absoluteStartDate, - absoluteEndDate, - dangerColor, - data, - errors, - hasActiveFilters, - linkParameters, - loading, - } = props; + const { dangerColor, data, errors, hasActiveFilters, linkParameters, loading } = props; const [drawerIds, updateDrawerIds] = useState([]); const items = data?.monitorStates?.summaries ?? []; @@ -132,12 +121,7 @@ export const MonitorListComponent = (props: Props) => { show: false, }, render: (histogramSeries: SummaryHistogramPoint[] | null) => ( - + ), }, { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap rename to x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/monitor_status.bar.test.tsx.snap diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx similarity index 79% rename from x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx rename to x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx index 545405f91d537..0a53eeb89d793 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_status.bar.test.tsx @@ -7,14 +7,12 @@ import moment from 'moment'; import React from 'react'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; -import { Ping } from '../../../../common/graphql/types'; -import { MonitorStatusBarComponent } from '../monitor_status_details/monitor_status_bar'; +import { MonitorStatusBarComponent } from '../monitor_status_bar'; +import { Ping } from '../../../../../common/graphql/types'; describe('MonitorStatusBar component', () => { let monitorStatus: Ping; let monitorLocations: any; - let dateStart: string; - let dateEnd: string; beforeEach(() => { monitorStatus = { @@ -46,9 +44,6 @@ describe('MonitorStatusBar component', () => { }, ], }; - - dateStart = moment('01-01-2010').toString(); - dateEnd = moment('10-10-2010').toString(); }); it('renders duration in ms, not us', () => { @@ -56,10 +51,7 @@ describe('MonitorStatusBar component', () => { ); expect(component).toMatchSnapshot(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts index 7b4e1ea353c11..385788cc825a0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts @@ -3,34 +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 { connect } from 'react-redux'; -import { AppState } from '../../../state'; -import { selectMonitorLocations } from '../../../state/selectors'; -import { fetchMonitorLocations } from '../../../state/actions/monitor'; -import { MonitorStatusDetailsComponent } from './monitor_status_details'; -const mapStateToProps = (state: AppState, { monitorId }: any) => ({ - monitorLocations: selectMonitorLocations(state, monitorId), -}); - -const mapDispatchToProps = (dispatch: any, ownProps: any) => ({ - loadMonitorLocations: () => { - const { dateStart, dateEnd, monitorId } = ownProps; - dispatch( - fetchMonitorLocations({ - monitorId, - dateStart, - dateEnd, - }) - ); - }, -}); - -export const MonitorStatusDetails = connect( - mapStateToProps, - mapDispatchToProps -)(MonitorStatusDetailsComponent); - -export * from './monitor_status_details'; -export { MonitorStatusBar } from './monitor_status_bar'; +export { MonitorStatusBarComponent } from './monitor_status_bar'; +export { MonitorStatusDetailsComponent } from './monitor_status_details'; export { StatusByLocations } from './monitor_status_bar/status_by_location'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts index 94bd7fa7f026b..0cb11587eee48 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts @@ -4,47 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; -import { - StateProps, - DispatchProps, - MonitorStatusBarComponent, - MonitorStatusBarProps, -} from './monitor_status_bar'; -import { selectMonitorStatus, selectMonitorLocations } from '../../../../state/selectors'; -import { AppState } from '../../../../state'; -import { getMonitorStatus, getSelectedMonitor } from '../../../../state/actions'; - -const mapStateToProps = (state: AppState, ownProps: MonitorStatusBarProps) => ({ - monitorStatus: selectMonitorStatus(state), - monitorLocations: selectMonitorLocations(state, ownProps.monitorId), -}); - -const mapDispatchToProps = (dispatch: Dispatch, ownProps: MonitorStatusBarProps) => ({ - loadMonitorStatus: () => { - const { dateStart, dateEnd, monitorId } = ownProps; - dispatch( - getMonitorStatus({ - monitorId, - dateStart, - dateEnd, - }) - ); - dispatch( - getSelectedMonitor({ - monitorId, - }) - ); - }, -}); - -// @ts-ignore TODO: Investigate typescript issues here -export const MonitorStatusBar = connect( - // @ts-ignore TODO: Investigate typescript issues here - mapStateToProps, - mapDispatchToProps -)(MonitorStatusBarComponent); - export { MonitorSSLCertificate } from './monitor_ssl_certificate'; -export * from './monitor_status_bar'; +export { MonitorStatusBarComponent } from './monitor_status_bar'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx index 2524039829add..22e4377944be1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import { EuiLink, EuiTitle, @@ -13,42 +14,23 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; -import React, { useEffect } from 'react'; import { MonitorSSLCertificate } from './monitor_ssl_certificate'; import * as labels from './translations'; import { StatusByLocations } from './status_by_location'; import { Ping } from '../../../../../common/graphql/types'; import { MonitorLocations } from '../../../../../common/runtime_types'; -export interface StateProps { +interface MonitorStatusBarProps { + monitorId: string; monitorStatus: Ping; monitorLocations: MonitorLocations; } -export interface DispatchProps { - loadMonitorStatus: () => void; -} - -export interface MonitorStatusBarProps { - monitorId: string; - dateStart: string; - dateEnd: string; -} - -type Props = MonitorStatusBarProps & StateProps & DispatchProps; - -export const MonitorStatusBarComponent: React.FC = ({ - dateStart, - dateEnd, +export const MonitorStatusBarComponent: React.FC = ({ monitorId, - loadMonitorStatus, monitorStatus, monitorLocations, }) => { - useEffect(() => { - loadMonitorStatus(); - }, [dateStart, dateEnd, loadMonitorStatus]); - const full = monitorStatus?.url?.full ?? ''; return ( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx index 29bd8eb3a7183..7dea73da7bba0 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx @@ -8,16 +8,13 @@ import React, { useContext, useEffect, useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import styled from 'styled-components'; import { LocationMap } from '../location_map'; -import { MonitorStatusBar } from './monitor_status_bar'; import { UptimeRefreshContext } from '../../../contexts'; +import { MonitorLocations } from '../../../../common/runtime_types'; +import { MonitorStatusBar } from '../../connected'; -interface MonitorStatusBarProps { +interface MonitorStatusDetailsProps { monitorId: string; - variables: any; - loadMonitorLocations: any; - monitorLocations: any; - dateStart: any; - dateEnd: any; + monitorLocations: MonitorLocations; } const WrapFlexItem = styled(EuiFlexItem)` @@ -28,15 +25,8 @@ const WrapFlexItem = styled(EuiFlexItem)` export const MonitorStatusDetailsComponent = ({ monitorId, - variables, - loadMonitorLocations, monitorLocations, - dateStart, - dateEnd, -}: MonitorStatusBarProps) => { - useEffect(() => { - loadMonitorLocations(monitorId); - }, [loadMonitorLocations, monitorId, dateStart, dateEnd]); +}: MonitorStatusDetailsProps) => { const { refreshApp } = useContext(UptimeRefreshContext); const [isTabActive] = useState(document.visibilityState); @@ -63,12 +53,7 @@ export const MonitorStatusDetailsComponent = ({ - + diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index 90d716001cff9..8531cd1a3cc83 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -5,63 +5,28 @@ */ import { EuiSpacer } from '@elastic/eui'; -import React, { useEffect } from 'react'; +import React from 'react'; import { get } from 'lodash'; -import { connect } from 'react-redux'; -import { Snapshot as SnapshotType } from '../../../common/runtime_types'; import { DonutChart } from './charts'; -import { fetchSnapshotCount } from '../../state/actions'; import { ChartWrapper } from './charts/chart_wrapper'; import { SnapshotHeading } from './snapshot_heading'; -import { AppState } from '../../state'; +import { Snapshot as SnapshotType } from '../../../common/runtime_types'; const SNAPSHOT_CHART_WIDTH = 144; const SNAPSHOT_CHART_HEIGHT = 144; -/** - * Props expected from parent components. - */ -interface OwnProps { - dateRangeStart: string; - dateRangeEnd: string; - filters?: string; - /** - * Height is needed, since by default charts takes height of 100% - */ - height?: string; - statusFilter?: string; -} - -/** - * Props given by the Redux store based on action input. - */ -interface StoreProps { +interface SnapshotComponentProps { count: SnapshotType; - lastRefresh: number; loading: boolean; + height?: string; } /** - * Contains functions that will dispatch actions used - * for this component's life cycle - */ -interface DispatchProps { - loadSnapshotCount: typeof fetchSnapshotCount; -} - -/** - * Props used to render the Snapshot component. + * This component visualizes a KPI and histogram chart to help users quickly + * glean the status of their uptime environment. + * @param props the props required by the component */ -type Props = OwnProps & StoreProps & DispatchProps; - -type PresentationalComponentProps = Pick & - Pick; - -export const PresentationalComponent: React.FC = ({ - count, - height, - loading, -}) => ( +export const SnapshotComponent: React.FC = ({ count, height, loading }) => ( (count, 'down', 0)} total={get(count, 'total', 0)} /> @@ -73,59 +38,3 @@ export const PresentationalComponent: React.FC = ( /> ); - -/** - * This component visualizes a KPI and histogram chart to help users quickly - * glean the status of their uptime environment. - * @param props the props required by the component - */ -export const Container: React.FC = ({ - count, - dateRangeStart, - dateRangeEnd, - filters, - height, - statusFilter, - lastRefresh, - loading, - loadSnapshotCount, -}: Props) => { - useEffect(() => { - loadSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter); - }, [dateRangeStart, dateRangeEnd, filters, lastRefresh, loadSnapshotCount, statusFilter]); - return ; -}; - -/** - * Provides state to connected component. - * @param state the root app state - */ -const mapStateToProps = ({ - snapshot: { count, loading }, - ui: { lastRefresh }, -}: AppState): StoreProps => ({ - count, - lastRefresh, - loading, -}); - -/** - * Used for fetching snapshot counts. - * @param dispatch redux-provided action dispatcher - */ -const mapDispatchToProps = (dispatch: any) => ({ - loadSnapshotCount: ( - dateRangeStart: string, - dateRangeEnd: string, - filters?: string, - statusFilter?: string - ): DispatchProps => { - return dispatch(fetchSnapshotCount(dateRangeStart, dateRangeEnd, filters, statusFilter)); - }, -}); - -export const Snapshot = connect( - // @ts-ignore connect is expecting null | undefined for some reason - mapStateToProps, - mapDispatchToProps -)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx index 03ab9fb5cf194..2c0be2aa15d6f 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/status_panel.tsx @@ -4,52 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import React from 'react'; -import { Snapshot } from './snapshot'; -import { PingHistogram } from '../connected'; - -interface StatusPanelProps { - absoluteDateRangeStart: number; - absoluteDateRangeEnd: number; - dateRangeStart: string; - dateRangeEnd: string; - filters?: string; - statusFilter?: string; -} +import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import { PingHistogram, Snapshot } from '../connected'; const STATUS_CHART_HEIGHT = '160px'; -export const StatusPanel = ({ - absoluteDateRangeStart, - absoluteDateRangeEnd, - dateRangeStart, - dateRangeEnd, - filters, - statusFilter, -}: StatusPanelProps) => ( +export const StatusPanel = ({}) => ( - + - + diff --git a/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts b/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts index d02a6fc2afb5d..8c9eec3abe223 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/update_kuery_string.ts @@ -55,9 +55,9 @@ export const useUpdateKueryString = ( const elasticsearchQuery = esKuery.toElasticsearchQuery(ast, indexPattern); esFilters = JSON.stringify(elasticsearchQuery); - - updateEsQueryForFilterGroup(filterQueryString, indexPattern); } + updateEsQueryForFilterGroup(filterQueryString, indexPattern); + return [esFilters]; } catch (err) { return [urlFilters, err]; diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx index 408d2584911e0..a8501ff14313a 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx @@ -12,8 +12,8 @@ import { UMUpdateBreadcrumbs } from '../lib/lib'; import { UptimeRefreshContext, UptimeThemeContext } from '../contexts'; import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks'; import { useTrackPageview } from '../../../infra/public'; -import { MonitorStatusDetails } from '../components/functional/monitor_status_details'; import { PageHeader } from './page_header'; +import { MonitorStatusDetails } from '../components/connected'; interface MonitorPageProps { setBreadcrumbs: UMUpdateBreadcrumbs; @@ -49,20 +49,9 @@ export const MonitorPage = ({ setBreadcrumbs }: MonitorPageProps) => { - + - + } - + Date: Mon, 3 Feb 2020 10:12:32 -0600 Subject: [PATCH 10/60] [SIEM] Use Core HTTP Client (#54210) * Replace uses of chrome.getBasePath and fetch with core.http While core.http is coming from 'above' these functions, it doesn't make a lot of sense to pass the client through the entire stack to be able to call it at the bottom, because longer-term we'll abstract these http calls with some redux middleware. In the short term we have a mechanism to refer to core through a singleton, 'ui/new_platform', which should be around until 8.0 at least. Ideally, we'll have a more robust architecture in place by then. If not, we can reproduce the singleton module ourselves. * Fix index patterns API call core.http.fetch doesn't like a querystring in the path argument, so we move it to the query object instead. The 'type' field specifier is redundant as the type is always returned (and not as an attribute). * Refactor getIndexPatterns to use the savedObjects client We lose the updated_at field by using the client, but we weren't actually using it. I don't _think_ that the savedObjects client supports request aborts right now, but when it does we'll get that back for free. * Pass query params as object to core.http A request with query params in its path does not properly encode said params (the '?', at the very least), leading to malformed requests that result in 404s. * Remove redundant API logic This function was originally used to query both an individual rule and a list of rules, but the former functionality has been moved to fetchRuleById. * Allow throwIfNotOk to handle an undefined response This is also an error for us, and we throw as such.. * Convert new Rules APIs to use core.http These all occurred on master, this fixes them post-merge. * Refactor Signals requests expecting custom Errors These requests package up error responses in custom errors, which callers are expecting. We should refactor all of these calls to behave similarly, but for now let's just not break existing ones. * Remove default credentials specification The default is credentials: 'same-origin', and so we can omit it. * Update types in new uses of hook This savedObject type is slightly modified now that the hook is using the NP savedObjects client. * Replace explicit system header with fetch option The asSystemRequest option accomplishes the same thing without requiring us to know the implementation. With the addition of this option, setting this header explicitly causes an error. This also removes the credentials: same-origin specifier as it is the default. * Remove redundant awaits The response has previously been resolved, and so our body should be populated, here. Co-authored-by: Elastic Machine --- .../components/embeddables/__mocks__/mock.ts | 20 +- .../embeddables/embedded_map_helpers.tsx | 2 +- .../components/ml/api/anomalies_table_data.ts | 30 +-- .../components/ml/api/get_ml_capabilities.ts | 17 +- .../siem/public/components/ml_popover/api.tsx | 191 ++++++++------- .../public/components/ml_popover/types.ts | 10 - .../containers/detection_engine/rules/api.ts | 223 +++++++----------- .../detection_engine/rules/types.ts | 9 + .../detection_engine/signals/api.ts | 123 ++++------ .../siem/public/hooks/api/__mock__/api.tsx | 6 +- .../plugins/siem/public/hooks/api/api.test.ts | 4 + .../plugins/siem/public/hooks/api/api.tsx | 43 ++-- .../legacy/plugins/siem/public/hooks/types.ts | 23 +- .../siem/public/hooks/use_index_patterns.tsx | 8 +- x-pack/legacy/plugins/siem/public/plugin.tsx | 1 + 15 files changed, 299 insertions(+), 411 deletions(-) diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts index 7834bb4511dc6..19ad0d452feb1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/__mocks__/mock.ts @@ -5,7 +5,7 @@ */ import { IndexPatternMapping } from '../types'; -import { IndexPatternSavedObject } from '../../ml_popover/types'; +import { IndexPatternSavedObject } from '../../../hooks/types'; export const mockIndexPatternIds: IndexPatternMapping[] = [ { title: 'filebeat-*', id: '8c7323ac-97ad-4b53-ac0a-40f8f691a918' }, @@ -425,8 +425,7 @@ export const mockLayerListMixed = [ export const mockAPMIndexPattern: IndexPatternSavedObject = { id: 'apm-*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: 'apm-*', }, @@ -435,8 +434,7 @@ export const mockAPMIndexPattern: IndexPatternSavedObject = { export const mockAPMRegexIndexPattern: IndexPatternSavedObject = { id: 'apm-7.*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: 'apm-7.*', }, @@ -445,8 +443,7 @@ export const mockAPMRegexIndexPattern: IndexPatternSavedObject = { export const mockFilebeatIndexPattern: IndexPatternSavedObject = { id: 'filebeat-*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: 'filebeat-*', }, @@ -455,8 +452,7 @@ export const mockFilebeatIndexPattern: IndexPatternSavedObject = { export const mockAuditbeatIndexPattern: IndexPatternSavedObject = { id: 'auditbeat-*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: 'auditbeat-*', }, @@ -465,8 +461,7 @@ export const mockAuditbeatIndexPattern: IndexPatternSavedObject = { export const mockAPMTransactionIndexPattern: IndexPatternSavedObject = { id: 'apm-*-transaction*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: 'apm-*-transaction*', }, @@ -475,8 +470,7 @@ export const mockAPMTransactionIndexPattern: IndexPatternSavedObject = { export const mockGlobIndexPattern: IndexPatternSavedObject = { id: '*', type: 'index-pattern', - updated_at: '', - version: 'abc', + _version: 'abc', attributes: { title: '*', }, diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx index 2d4714401f3b3..e370cbbf64a4a 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx @@ -21,7 +21,7 @@ import { getLayerList } from './map_config'; import { MAP_SAVED_OBJECT_TYPE } from '../../../../maps/common/constants'; import * as i18n from './translations'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; -import { IndexPatternSavedObject } from '../ml_popover/types'; +import { IndexPatternSavedObject } from '../../hooks/types'; /** * Creates MapEmbeddable with provided initial configuration diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts index 10b2538d1e785..cb84d9182d2e0 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - +import { npStart } from 'ui/new_platform'; import { Anomalies, InfluencerInput, CriteriaFields } from '../types'; import { throwIfNotOk } from '../../../hooks/api/api'; + export interface Body { jobIds: string[]; criteriaFields: CriteriaFields[]; @@ -22,17 +22,17 @@ export interface Body { } export const anomaliesTableData = async (body: Body, signal: AbortSignal): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/results/anomalies_table_data`, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify(body), - headers: { - 'content-Type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - signal, - }); - await throwIfNotOk(response); - return response.json(); + const response = await npStart.core.http.fetch( + '/api/ml/results/anomalies_table_data', + { + method: 'POST', + body: JSON.stringify(body), + asResponse: true, + asSystemRequest: true, + signal, + } + ); + + await throwIfNotOk(response.response); + return response.body!; }; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts index 1333951028494..dcfd7365f8e0d 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; import { InfluencerInput, MlCapabilities } from '../types'; import { throwIfNotOk } from '../../../hooks/api/api'; @@ -23,16 +23,13 @@ export interface Body { } export const getMlCapabilities = async (signal: AbortSignal): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/ml_capabilities`, { + const response = await npStart.core.http.fetch('/api/ml/ml_capabilities', { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-Type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, + asResponse: true, + asSystemRequest: true, signal, }); - await throwIfNotOk(response); - return response.json(); + + await throwIfNotOk(response.response); + return response.body!; }; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx index a04b8f4b99653..cf939d8e09b7e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; + import { CheckRecognizerProps, CloseJobsResponse, @@ -31,21 +32,18 @@ export const checkRecognizer = async ({ indexPatternName, signal, }: CheckRecognizerProps): Promise => { - const response = await fetch( - `${chrome.getBasePath()}/api/ml/modules/recognize/${indexPatternName}`, + const response = await npStart.core.http.fetch( + `/api/ml/modules/recognize/${indexPatternName}`, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, + asResponse: true, + asSystemRequest: true, signal, } ); - await throwIfNotOk(response); - return response.json(); + + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -55,18 +53,18 @@ export const checkRecognizer = async ({ * @param signal to cancel request */ export const getModules = async ({ moduleId = '', signal }: GetModulesProps): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/get_module/${moduleId}`, { - method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - signal, - }); - await throwIfNotOk(response); - return response.json(); + const response = await npStart.core.http.fetch( + `/api/ml/modules/get_module/${moduleId}`, + { + method: 'GET', + asResponse: true, + asSystemRequest: true, + signal, + } + ); + + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -77,7 +75,6 @@ export const getModules = async ({ moduleId = '', signal }: GetModulesProps): Pr * @param jobIdErrorFilter - if provided, filters all errors except for given jobIds * @param groups - list of groups to add to jobs being installed * @param prefix - prefix to be added to job name - * @param headers optional headers to add */ export const setupMlJob = async ({ configTemplate, @@ -86,25 +83,26 @@ export const setupMlJob = async ({ groups = ['siem'], prefix = '', }: MlSetupArgs): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/setup/${configTemplate}`, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - prefix, - groups, - indexPatternName, - startDatafeed: false, - useDedicatedIndex: true, - }), - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - }); - await throwIfNotOk(response); - const json = await response.json(); + const response = await npStart.core.http.fetch( + `/api/ml/modules/setup/${configTemplate}`, + { + method: 'POST', + body: JSON.stringify({ + prefix, + groups, + indexPatternName, + startDatafeed: false, + useDedicatedIndex: true, + }), + asResponse: true, + asSystemRequest: true, + } + ); + + await throwIfNotOk(response.response); + const json = response.body!; throwIfErrorAttachedToSetup(json, jobIdErrorFilter); + return json; }; @@ -121,22 +119,23 @@ export const startDatafeeds = async ({ datafeedIds: string[]; start: number; }): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/force_start_datafeeds`, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - datafeedIds, - ...(start !== 0 && { start }), - }), - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - }); - await throwIfNotOk(response); - const json = await response.json(); + const response = await npStart.core.http.fetch( + '/api/ml/jobs/force_start_datafeeds', + { + method: 'POST', + body: JSON.stringify({ + datafeedIds, + ...(start !== 0 && { start }), + }), + asResponse: true, + asSystemRequest: true, + } + ); + + await throwIfNotOk(response.response); + const json = response.body!; throwIfErrorAttached(json, datafeedIds); + return json; }; @@ -144,49 +143,46 @@ export const startDatafeeds = async ({ * Stops the given dataFeedIds and sets the corresponding Job's jobState to closed * * @param datafeedIds - * @param headers optional headers to add */ export const stopDatafeeds = async ({ datafeedIds, }: { datafeedIds: string[]; }): Promise<[StopDatafeedResponse | ErrorResponse, CloseJobsResponse]> => { - const stopDatafeedsResponse = await fetch(`${chrome.getBasePath()}/api/ml/jobs/stop_datafeeds`, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - datafeedIds, - }), - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - }); + const stopDatafeedsResponse = await npStart.core.http.fetch( + '/api/ml/jobs/stop_datafeeds', + { + method: 'POST', + body: JSON.stringify({ + datafeedIds, + }), + asResponse: true, + asSystemRequest: true, + } + ); - await throwIfNotOk(stopDatafeedsResponse); - const stopDatafeedsResponseJson = await stopDatafeedsResponse.json(); + await throwIfNotOk(stopDatafeedsResponse.response); + const stopDatafeedsResponseJson = stopDatafeedsResponse.body!; const datafeedPrefix = 'datafeed-'; - const closeJobsResponse = await fetch(`${chrome.getBasePath()}/api/ml/jobs/close_jobs`, { - method: 'POST', - credentials: 'same-origin', - body: JSON.stringify({ - jobIds: datafeedIds.map(dataFeedId => - dataFeedId.startsWith(datafeedPrefix) - ? dataFeedId.substring(datafeedPrefix.length) - : dataFeedId - ), - }), - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - }); + const closeJobsResponse = await npStart.core.http.fetch( + '/api/ml/jobs/close_jobs', + { + method: 'POST', + body: JSON.stringify({ + jobIds: datafeedIds.map(dataFeedId => + dataFeedId.startsWith(datafeedPrefix) + ? dataFeedId.substring(datafeedPrefix.length) + : dataFeedId + ), + }), + asResponse: true, + asSystemRequest: true, + } + ); - await throwIfNotOk(closeJobsResponse); - return [stopDatafeedsResponseJson, await closeJobsResponse.json()]; + await throwIfNotOk(closeJobsResponse.response); + return [stopDatafeedsResponseJson, closeJobsResponse.body!]; }; /** @@ -198,17 +194,14 @@ export const stopDatafeeds = async ({ * @param signal to cancel request */ export const getJobsSummary = async (signal: AbortSignal): Promise => { - const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/jobs_summary`, { + const response = await npStart.core.http.fetch('/api/ml/jobs/jobs_summary', { method: 'POST', - credentials: 'same-origin', body: JSON.stringify({}), - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, + asResponse: true, + asSystemRequest: true, signal, }); - await throwIfNotOk(response); - return response.json(); + + await throwIfNotOk(response.response); + return response.body!; }; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts index 964ae8c8242d4..f3bf78fdbb94c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts @@ -193,16 +193,6 @@ export interface CloseJobsResponse { }; } -export interface IndexPatternSavedObject { - attributes: { - title: string; - }; - id: string; - type: string; - updated_at: string; - version: string; -} - export interface JobsFilters { filterQuery: string; showCustomJobs: boolean; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index 8f4abeb31c226..4f50a9bd14788 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; + import { AddRulesProps, DeleteRulesProps, @@ -19,8 +20,9 @@ import { ImportRulesProps, ExportRulesProps, RuleError, - RuleStatus, + RuleStatusResponse, ImportRulesResponse, + PrePackagedRulesStatusResponse, } from './types'; import { throwIfNotOk } from '../../../hooks/api/api'; import { @@ -39,19 +41,15 @@ import * as i18n from '../../../pages/detection_engine/rules/translations'; * @param signal to cancel request */ export const addRule = async ({ rule, signal }: AddRulesProps): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_RULES_URL, { method: rule.id != null ? 'PUT' : 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, body: JSON.stringify(rule), + asResponse: true, signal, }); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -79,40 +77,36 @@ export const fetchRules = async ({ signal, }: FetchRulesProps): Promise => { const filters = [ - ...(filterOptions.filter.length !== 0 - ? [`alert.attributes.name:%20${encodeURIComponent(filterOptions.filter)}`] - : []), + ...(filterOptions.filter.length ? [`alert.attributes.name: ${filterOptions.filter}`] : []), ...(filterOptions.showCustomRules - ? ['alert.attributes.tags:%20%22__internal_immutable:false%22'] + ? [`alert.attributes.tags: "__internal_immutable:false"`] : []), ...(filterOptions.showElasticRules - ? ['alert.attributes.tags:%20%22__internal_immutable:true%22'] + ? [`alert.attributes.tags: "__internal_immutable:true"`] : []), - ...(filterOptions.tags?.map(t => `alert.attributes.tags:${encodeURIComponent(t)}`) ?? []), + ...(filterOptions.tags?.map(t => `alert.attributes.tags: ${t}`) ?? []), ]; - const queryParams = [ - `page=${pagination.page}`, - `per_page=${pagination.perPage}`, - `sort_field=${filterOptions.sortField}`, - `sort_order=${filterOptions.sortOrder}`, - ...(filters.length > 0 ? [`filter=${filters.join('%20AND%20')}`] : []), - ]; + const query = { + page: pagination.page, + per_page: pagination.perPage, + sort_field: filterOptions.sortField, + sort_order: filterOptions.sortOrder, + ...(filters.length ? { filter: filters.join(' AND ') } : {}), + }; - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_find?${queryParams.join('&')}`, + const response = await npStart.core.http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_find`, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, + query, signal, + asResponse: true, } ); - await throwIfNotOk(response); - return response.json(); + + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -123,18 +117,15 @@ export const fetchRules = async ({ * */ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_RULES_URL, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, + query: { id }, + asResponse: true, signal, }); - await throwIfNotOk(response); - const rule: Rule = await response.json(); - return rule; + + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -146,21 +137,17 @@ export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + const response = await npStart.core.http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_bulk_update`, { method: 'PUT', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, body: JSON.stringify(ids.map(id => ({ id, enabled }))), + asResponse: true, } ); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -171,21 +158,17 @@ export const enableRules = async ({ ids, enabled }: EnableRulesProps): Promise> => { - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + const response = await npStart.core.http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, { - method: 'DELETE', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, + method: 'PUT', body: JSON.stringify(ids.map(id => ({ id }))), + asResponse: true, } ); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -194,15 +177,10 @@ export const deleteRules = async ({ ids }: DeleteRulesProps): Promise => { - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_create`, + const response = await npStart.core.http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_bulk_create`, { method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, body: JSON.stringify( rules.map(rule => ({ ...rule, @@ -223,11 +201,12 @@ export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_URL}`, { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_PREPACKAGED_URL, { method: 'PUT', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, signal, + asResponse: true, }); - await throwIfNotOk(response); + + await throwIfNotOk(response.response); return true; }; @@ -266,21 +242,19 @@ export const importRules = async ({ const formData = new FormData(); formData.append('file', fileToImport); - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_import?overwrite=${overwrite}`, + const response = await npStart.core.http.fetch( + `${DETECTION_ENGINE_RULES_URL}/_import`, { method: 'POST', - credentials: 'same-origin', - headers: { - 'kbn-xsrf': 'true', - }, + query: { overwrite }, body: formData, + asResponse: true, signal, } ); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -304,24 +278,19 @@ export const exportRules = async ({ ? JSON.stringify({ objects: ruleIds.map(rule => ({ rule_id: rule })) }) : undefined; - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_export?exclude_export_details=${excludeExportDetails}&file_name=${encodeURIComponent( - filename - )}`, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, - body, - signal, - } - ); + const response = await npStart.core.http.fetch(`${DETECTION_ENGINE_RULES_URL}/_export`, { + method: 'POST', + body, + query: { + exclude_export_details: excludeExportDetails, + file_name: filename, + }, + signal, + asResponse: true, + }); - await throwIfNotOk(response); - return response.blob(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -338,24 +307,19 @@ export const getRuleStatusById = async ({ }: { id: string; signal: AbortSignal; -}): Promise> => { - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS_URL}?ids=${encodeURIComponent( - JSON.stringify([id]) - )}`, +}): Promise => { + const response = await npStart.core.http.fetch( + DETECTION_ENGINE_RULES_STATUS_URL, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, + query: { ids: JSON.stringify([id]) }, signal, + asResponse: true, } ); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -365,18 +329,14 @@ export const getRuleStatusById = async ({ * */ export const fetchTags = async ({ signal }: { signal: AbortSignal }): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_TAGS_URL}`, { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_TAGS_URL, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, signal, + asResponse: true, }); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -390,25 +350,16 @@ export const getPrePackagedRulesStatus = async ({ signal, }: { signal: AbortSignal; -}): Promise<{ - rules_custom_installed: number; - rules_installed: number; - rules_not_installed: number; - rules_not_updated: number; -}> => { - const response = await fetch( - `${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL}`, +}): Promise => { + const response = await npStart.core.http.fetch( + DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, signal, + asResponse: true, } ); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index b30c3b211b1b8..0aaffb7b86b28 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -197,3 +197,12 @@ export interface RuleInfoStatus { last_failure_message: string | null; last_success_message: string | null; } + +export type RuleStatusResponse = Record; + +export interface PrePackagedRulesStatusResponse { + rules_custom_installed: number; + rules_installed: number; + rules_not_installed: number; + rules_not_updated: number; +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts index 8754d73637e7c..d0da70e646124 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; import { throwIfNotOk } from '../../../hooks/api/api'; import { @@ -14,40 +14,37 @@ import { DETECTION_ENGINE_PRIVILEGES_URL, } from '../../../../common/constants'; import { + BasicSignals, + PostSignalError, + Privilege, QuerySignals, + SignalIndexError, SignalSearchResponse, - UpdateSignalStatusProps, SignalsIndex, - SignalIndexError, - Privilege, - PostSignalError, - BasicSignals, + UpdateSignalStatusProps, } from './types'; -import { parseJsonFromBody } from '../../../utils/api'; /** * Fetch Signals by providing a query * * @param query String to match a dsl - * @param signal AbortSignal for cancelling request */ export const fetchQuerySignals = async ({ query, signal, }: QuerySignals): Promise> => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_QUERY_SIGNALS_URL}`, { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, - body: JSON.stringify(query), - signal, - }); - await throwIfNotOk(response); - const signals = await response.json(); - return signals; + const response = await npStart.core.http.fetch>( + DETECTION_ENGINE_QUERY_SIGNALS_URL, + { + method: 'POST', + body: JSON.stringify(query), + asResponse: true, + signal, + } + ); + + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -62,19 +59,15 @@ export const updateSignalStatus = async ({ status, signal, }: UpdateSignalStatusProps): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_SIGNALS_STATUS_URL}`, { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_SIGNALS_STATUS_URL, { method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, body: JSON.stringify({ status, ...query }), + asResponse: true, signal, }); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -82,25 +75,18 @@ export const updateSignalStatus = async ({ * * @param signal AbortSignal for cancelling request */ -export const getSignalIndex = async ({ signal }: BasicSignals): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { - method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, - signal, - }); - if (response.ok) { - const signalIndex = await response.json(); - return signalIndex; +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => { + try { + return await npStart.core.http.fetch(DETECTION_ENGINE_INDEX_URL, { + method: 'GET', + signal, + }); + } catch (e) { + if (e.body) { + throw new SignalIndexError(e.body); + } + throw e; } - const error = await parseJsonFromBody(response); - if (error != null) { - throw new SignalIndexError(error); - } - return null; }; /** @@ -108,19 +94,15 @@ export const getSignalIndex = async ({ signal }: BasicSignals): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PRIVILEGES_URL}`, { +export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => { + const response = await npStart.core.http.fetch(DETECTION_ENGINE_PRIVILEGES_URL, { method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, signal, + asResponse: true, }); - await throwIfNotOk(response); - return response.json(); + await throwIfNotOk(response.response); + return response.body!; }; /** @@ -128,23 +110,16 @@ export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => { - const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { - method: 'POST', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-xsrf': 'true', - }, - signal, - }); - if (response.ok) { - const signalIndex = await response.json(); - return signalIndex; - } - const error = await parseJsonFromBody(response); - if (error != null) { - throw new PostSignalError(error); +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => { + try { + return await npStart.core.http.fetch(DETECTION_ENGINE_INDEX_URL, { + method: 'POST', + signal, + }); + } catch (e) { + if (e.body) { + throw new PostSignalError(e.body); + } + throw e; } - return null; }; diff --git a/x-pack/legacy/plugins/siem/public/hooks/api/__mock__/api.tsx b/x-pack/legacy/plugins/siem/public/hooks/api/__mock__/api.tsx index 13f53cd34feb6..b12b04e8f760b 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/api/__mock__/api.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/api/__mock__/api.tsx @@ -13,8 +13,7 @@ export const mockIndexPatternSavedObjects: IndexPatternSavedObject[] = [ attributes: { title: 'filebeat-*', }, - updated_at: '2019-08-26T04:30:09.111Z', - version: 'WzE4LLwxXQ==', + _version: 'WzE4LLwxXQ==', }, { type: 'index-pattern', @@ -22,7 +21,6 @@ export const mockIndexPatternSavedObjects: IndexPatternSavedObject[] = [ attributes: { title: 'auditbeat-*', }, - updated_at: '2019-08-26T04:31:12.934Z', - version: 'WzELLywxXQ==', + _version: 'WzELLywxXQ==', }, ]; diff --git a/x-pack/legacy/plugins/siem/public/hooks/api/api.test.ts b/x-pack/legacy/plugins/siem/public/hooks/api/api.test.ts index 95825b7d4abda..208a3b14ca283 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/api/api.test.ts +++ b/x-pack/legacy/plugins/siem/public/hooks/api/api.test.ts @@ -13,6 +13,10 @@ describe('api', () => { }); describe('#throwIfNotOk', () => { + test('throws a network error if there is no response', async () => { + await expect(throwIfNotOk()).rejects.toThrow('Network Error'); + }); + test('does a throw if it is given response that is not ok and the body is not parsable', async () => { fetchMock.mock('http://example.com', 500); const response = await fetch('http://example.com'); diff --git a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx index 8d319ffe23902..f5f32da7d8c0b 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx @@ -4,46 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; - +import { npStart } from 'ui/new_platform'; import * as i18n from '../translations'; import { parseJsonFromBody, ToasterErrors } from '../../components/ml/api/throw_if_not_ok'; -import { IndexPatternResponse, IndexPatternSavedObject } from '../types'; - -const emptyIndexPattern: IndexPatternSavedObject[] = []; +import { IndexPatternSavedObject, IndexPatternSavedObjectAttributes } from '../types'; /** * Fetches Configured Index Patterns from the Kibana saved objects API * * TODO: Refactor to context provider: https://github.com/elastic/siem-team/issues/448 - * - * @param signal */ -export const getIndexPatterns = async (signal: AbortSignal): Promise => { - const response = await fetch( - `${chrome.getBasePath()}/api/saved_objects/_find?type=index-pattern&fields=title&fields=type&per_page=10000`, - { - method: 'GET', - credentials: 'same-origin', - headers: { - 'content-type': 'application/json', - 'kbn-system-api': 'true', - 'kbn-xsrf': 'true', - }, - signal, - } - ); - await throwIfNotOk(response); - const results: IndexPatternResponse = await response.json(); +export const getIndexPatterns = async (): Promise => { + const response = await npStart.core.savedObjects.client.find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }); - if (results.saved_objects && Array.isArray(results.saved_objects)) { - return results.saved_objects; - } else { - return emptyIndexPattern; - } + return response.savedObjects; }; -export const throwIfNotOk = async (response: Response): Promise => { +export const throwIfNotOk = async (response?: Response): Promise => { + if (!response) { + throw new ToasterErrors([i18n.NETWORK_ERROR]); + } + if (!response.ok) { const body = await parseJsonFromBody(response); if (body != null && body.message) { diff --git a/x-pack/legacy/plugins/siem/public/hooks/types.ts b/x-pack/legacy/plugins/siem/public/hooks/types.ts index 4d66d8e191235..301b8bd655333 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/types.ts +++ b/x-pack/legacy/plugins/siem/public/hooks/types.ts @@ -4,19 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface IndexPatternSavedObject { - attributes: { - title: string; - }; - id: string; - type: string; - updated_at: string; - version: string; -} +import { SimpleSavedObject } from '../../../../../../src/core/public'; -export interface IndexPatternResponse { - page: number; - per_page: number; - saved_objects: IndexPatternSavedObject[]; - total: number; -} +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +export type IndexPatternSavedObjectAttributes = { title: string }; + +export type IndexPatternSavedObject = Pick< + SimpleSavedObject, + 'type' | 'id' | 'attributes' | '_version' +>; diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx index 7abe88402096c..35bed69e8617e 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx @@ -8,10 +8,10 @@ import { useEffect, useState } from 'react'; import { useStateToaster } from '../components/toasters'; import { errorToToaster } from '../components/ml/api/error_to_toaster'; -import { IndexPatternSavedObject } from '../components/ml_popover/types'; -import { getIndexPatterns } from './api/api'; import * as i18n from './translations'; +import { IndexPatternSavedObject } from './types'; +import { getIndexPatterns } from './api/api'; type Return = [boolean, IndexPatternSavedObject[]]; @@ -22,12 +22,11 @@ export const useIndexPatterns = (refreshToggle = false): Return => { useEffect(() => { let isSubscribed = true; - const abortCtrl = new AbortController(); setIsLoading(true); async function fetchIndexPatterns() { try { - const data = await getIndexPatterns(abortCtrl.signal); + const data = await getIndexPatterns(); if (isSubscribed) { setIndexPatterns(data); @@ -44,7 +43,6 @@ export const useIndexPatterns = (refreshToggle = false): Return => { fetchIndexPatterns(); return () => { isSubscribed = false; - abortCtrl.abort(); }; }, [refreshToggle]); diff --git a/x-pack/legacy/plugins/siem/public/plugin.tsx b/x-pack/legacy/plugins/siem/public/plugin.tsx index 7911b5eb9833b..74fc913d2b573 100644 --- a/x-pack/legacy/plugins/siem/public/plugin.tsx +++ b/x-pack/legacy/plugins/siem/public/plugin.tsx @@ -41,6 +41,7 @@ export type Start = ReturnType; export class Plugin implements IPlugin { public id = 'siem'; public name = 'SIEM'; + constructor( // @ts-ignore this is added to satisfy the New Platform typing constraint, // but we're not leveraging any of its functionality yet. From 4f4d3d753c0fef8df7e4ac242d5f15609af243b8 Mon Sep 17 00:00:00 2001 From: Wylie Conlon Date: Mon, 3 Feb 2020 11:19:41 -0500 Subject: [PATCH 11/60] [Lens] Fix bugs in Lens filters (#56441) * [Lens] Fix bug where filters were not displayed * Fix #55603 Co-authored-by: Elastic Machine --- .../lens/public/app_plugin/app.test.tsx | 24 +++++++++++++++---- .../plugins/lens/public/app_plugin/app.tsx | 7 +++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 80a7ceb61c324..99926c646da22 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -60,10 +60,14 @@ function createMockFilterManager() { return unsubscribe; }, }), - setFilters: (newFilters: unknown[]) => { + setFilters: jest.fn((newFilters: unknown[]) => { filters = newFilters; - subscriber(); - }, + if (subscriber) subscriber(); + }), + setAppFilters: jest.fn((newFilters: unknown[]) => { + filters = newFilters; + if (subscriber) subscriber(); + }), getFilters: () => filters, getGlobalFilters: () => { // @ts-ignore @@ -189,6 +193,13 @@ describe('Lens App', () => { `); }); + it('clears app filters on load', () => { + const defaultArgs = makeDefaultArgs(); + mount(); + + expect(defaultArgs.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([]); + }); + it('sets breadcrumbs when the document title changes', async () => { const defaultArgs = makeDefaultArgs(); const instance = mount(); @@ -226,7 +237,7 @@ describe('Lens App', () => { expect(args.docStorage.load).not.toHaveBeenCalled(); }); - it('loads a document and uses query if there is a document id', async () => { + it('loads a document and uses query and filters if there is a document id', async () => { const args = makeDefaultArgs(); args.editorFrame = frame; (args.docStorage.load as jest.Mock).mockResolvedValue({ @@ -234,6 +245,7 @@ describe('Lens App', () => { expression: 'valid expression', state: { query: 'fake query', + filters: [{ query: { match_phrase: { src: 'test' } } }], datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] }, }, }); @@ -245,6 +257,9 @@ describe('Lens App', () => { expect(args.docStorage.load).toHaveBeenCalledWith('1234'); expect(args.data.indexPatterns.get).toHaveBeenCalledWith('1'); + expect(args.data.query.filterManager.setAppFilters).toHaveBeenCalledWith([ + { query: { match_phrase: { src: 'test' } } }, + ]); expect(TopNavMenu).toHaveBeenCalledWith( expect.objectContaining({ query: 'fake query', @@ -260,6 +275,7 @@ describe('Lens App', () => { expression: 'valid expression', state: { query: 'fake query', + filters: [{ query: { match_phrase: { src: 'test' } } }], datasourceMetaData: { filterableIndexPatterns: [{ id: '1', title: 'saved' }] }, }, }, diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 35e45af6a3d68..6d2ebee1d88db 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -83,6 +83,10 @@ export function App({ const { lastKnownDoc } = state; useEffect(() => { + // Clear app-specific filters when navigating to Lens. Necessary because Lens + // can be loaded without a full page refresh + data.query.filterManager.setAppFilters([]); + const filterSubscription = data.query.filterManager.getUpdates$().subscribe({ next: () => { setState(s => ({ ...s, filters: data.query.filterManager.getFilters() })); @@ -123,13 +127,14 @@ export function App({ core.notifications ) .then(indexPatterns => { + // Don't overwrite any pinned filters + data.query.filterManager.setAppFilters(doc.state.filters); setState(s => ({ ...s, isLoading: false, persistedDoc: doc, lastKnownDoc: doc, query: doc.state.query, - filters: doc.state.filters, indexPatternsForTopNav: indexPatterns, })); }) From 479223b0a1acf4926a18f61e2c62c458019f747d Mon Sep 17 00:00:00 2001 From: Liza Katz Date: Mon, 3 Feb 2020 19:29:59 +0200 Subject: [PATCH 12/60] Update plugin generator to generate NP plugins (#55281) * Generate NP plugin * Added tsconfig * tsconfig * Adjust sao test * Add server side to plugin gen * Added navigation * add empty element * eslint * platform team CR * design CR improvements * text updates * temp disable plugin gen tests * eslint * Code review fixes * Add scss support - requires #53976 to be merged to work * CR fixes * comment fixes * Don't generate eslint for internal plugins by default * Update tests * reenable jest test for sao * Fix regex * review comments * code review Co-authored-by: Elastic Machine --- packages/kbn-plugin-generator/index.js | 16 ++- packages/kbn-plugin-generator/index.js.d.ts | 24 ++++ .../integration_tests/generate_plugin.test.js | 18 +-- .../kbn-plugin-generator/sao_template/sao.js | 69 +++++----- .../sao_template/sao.test.js | 127 ++++------------- .../sao_template/template/.i18nrc.json | 9 -- .../template/.kibana-plugin-helpers.json | 3 - .../sao_template/template/README.md | 31 +---- .../sao_template/template/common/index.ts | 2 + .../sao_template/template/eslintrc.js | 31 ++--- .../sao_template/template/gitignore | 6 - .../sao_template/template/index.js | 89 ------------ .../sao_template/template/kibana.json | 8 ++ .../template/package_template.json | 41 ------ .../template/public/__tests__/index.js | 7 - .../sao_template/template/public/app.js | 45 ------ .../template/public/application.tsx | 25 ++++ .../template/public/components/app.tsx | 129 ++++++++++++++++++ .../template/public/components/main/index.js | 1 - .../template/public/components/main/main.js | 97 ------------- .../sao_template/template/public/hack.js | 7 - .../template/public/{app.scss => index.scss} | 0 .../sao_template/template/public/index.ts | 16 +++ .../sao_template/template/public/plugin.ts | 42 ++++++ .../sao_template/template/public/types.ts | 11 ++ .../template/server/__tests__/index.js | 7 - .../sao_template/template/server/index.ts | 15 ++ .../sao_template/template/server/plugin.ts | 30 ++++ .../template/server/routes/example.js | 11 -- .../template/server/routes/index.ts | 17 +++ .../sao_template/template/server/types.ts | 4 + .../template/translations/zh-CN.json | 84 ------------ packages/kbn-plugin-generator/tsconfig.json | 5 + 33 files changed, 412 insertions(+), 615 deletions(-) create mode 100644 packages/kbn-plugin-generator/index.js.d.ts delete mode 100644 packages/kbn-plugin-generator/sao_template/template/.i18nrc.json delete mode 100644 packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json create mode 100644 packages/kbn-plugin-generator/sao_template/template/common/index.ts mode change 100755 => 100644 packages/kbn-plugin-generator/sao_template/template/eslintrc.js delete mode 100755 packages/kbn-plugin-generator/sao_template/template/gitignore delete mode 100755 packages/kbn-plugin-generator/sao_template/template/index.js create mode 100644 packages/kbn-plugin-generator/sao_template/template/kibana.json delete mode 100644 packages/kbn-plugin-generator/sao_template/template/package_template.json delete mode 100755 packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js delete mode 100755 packages/kbn-plugin-generator/sao_template/template/public/app.js create mode 100644 packages/kbn-plugin-generator/sao_template/template/public/application.tsx create mode 100644 packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx delete mode 100644 packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js delete mode 100644 packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js delete mode 100755 packages/kbn-plugin-generator/sao_template/template/public/hack.js rename packages/kbn-plugin-generator/sao_template/template/public/{app.scss => index.scss} (100%) create mode 100644 packages/kbn-plugin-generator/sao_template/template/public/index.ts create mode 100644 packages/kbn-plugin-generator/sao_template/template/public/plugin.ts create mode 100644 packages/kbn-plugin-generator/sao_template/template/public/types.ts delete mode 100755 packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js create mode 100644 packages/kbn-plugin-generator/sao_template/template/server/index.ts create mode 100644 packages/kbn-plugin-generator/sao_template/template/server/plugin.ts delete mode 100755 packages/kbn-plugin-generator/sao_template/template/server/routes/example.js create mode 100644 packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts create mode 100644 packages/kbn-plugin-generator/sao_template/template/server/types.ts delete mode 100644 packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json create mode 100644 packages/kbn-plugin-generator/tsconfig.json diff --git a/packages/kbn-plugin-generator/index.js b/packages/kbn-plugin-generator/index.js index 90274288357b8..15adce7f01c8e 100644 --- a/packages/kbn-plugin-generator/index.js +++ b/packages/kbn-plugin-generator/index.js @@ -29,6 +29,7 @@ exports.run = function run(argv) { const options = getopts(argv, { alias: { h: 'help', + i: 'internal', }, }); @@ -40,17 +41,22 @@ exports.run = function run(argv) { if (options.help) { console.log( dedent(chalk` - {dim usage:} node scripts/generate-plugin {bold [name]} - - generate a fresh Kibana plugin in the plugins/ directory + # {dim Usage:} + node scripts/generate-plugin {bold [name]} + Generate a fresh Kibana plugin in the plugins/ directory + + # {dim Core Kibana plugins:} + node scripts/generate-plugin {bold [name]} -i + To generate a core Kibana plugin inside the src/plugins/ directory, add the -i flag. `) + '\n' ); process.exit(1); } const name = options._[0]; + const isKibanaPlugin = options.internal; const template = resolve(__dirname, './sao_template'); - const kibanaPlugins = resolve(__dirname, '../../plugins'); + const kibanaPlugins = resolve(__dirname, isKibanaPlugin ? '../../src/plugins' : '../../plugins'); const targetPath = resolve(kibanaPlugins, snakeCase(name)); sao({ @@ -58,6 +64,8 @@ exports.run = function run(argv) { targetPath: targetPath, configOptions: { name, + isKibanaPlugin, + targetPath, }, }).catch(error => { console.error(chalk`{red fatal error}!`); diff --git a/packages/kbn-plugin-generator/index.js.d.ts b/packages/kbn-plugin-generator/index.js.d.ts new file mode 100644 index 0000000000000..46f7c43fd5790 --- /dev/null +++ b/packages/kbn-plugin-generator/index.js.d.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ +interface PluginGenerator { + /** + * Run plugin generator. + */ + run: (...args: any[]) => any; +} diff --git a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js index aa6611f3b6738..129125c4583d5 100644 --- a/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js +++ b/packages/kbn-plugin-generator/integration_tests/generate_plugin.test.js @@ -61,7 +61,8 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug expect(stats.isDirectory()).toBe(true); }); - it(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => { + // skipped until internationalization is re-introduced + it.skip(`should create an internationalization config file with a blank line appended to satisfy the parser`, async () => { // Link to the error that happens when the blank line is not there: // https://github.com/elastic/kibana/pull/45044#issuecomment-530092627 const intlFile = `${generatedPath}/.i18nrc.json`; @@ -78,16 +79,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug }); }); - it(`'yarn test:server' should exit 0`, async () => { - await execa('yarn', ['test:server'], { - cwd: generatedPath, - env: { - DISABLE_JUNIT_REPORTER: '1', - }, - }); - }); - - it(`'yarn build' should exit 0`, async () => { + it.skip(`'yarn build' should exit 0`, async () => { await execa('yarn', ['build'], { cwd: generatedPath }); }); @@ -109,7 +101,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug '--migrations.skip=true', ], cwd: generatedPath, - wait: /ispec_plugin.+Status changed from uninitialized to green - Ready/, + wait: new RegExp('\\[ispecPlugin\\]\\[plugins\\] Setting up plugin'), }); await proc.stop('kibana'); }); @@ -120,7 +112,7 @@ describe(`running the plugin-generator via 'node scripts/generate_plugin.js plug await execa('yarn', ['preinstall'], { cwd: generatedPath }); }); - it(`'yarn lint' should exit 0`, async () => { + it.skip(`'yarn lint' should exit 0`, async () => { await execa('yarn', ['lint'], { cwd: generatedPath }); }); diff --git a/packages/kbn-plugin-generator/sao_template/sao.js b/packages/kbn-plugin-generator/sao_template/sao.js index f7401cba84358..aed4b9a02838f 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.js +++ b/packages/kbn-plugin-generator/sao_template/sao.js @@ -17,21 +17,19 @@ * under the License. */ -const { resolve, relative, dirname } = require('path'); +const { relative } = require('path'); const startCase = require('lodash.startcase'); const camelCase = require('lodash.camelcase'); const snakeCase = require('lodash.snakecase'); -const execa = require('execa'); const chalk = require('chalk'); +const execa = require('execa'); const pkg = require('../package.json'); const kibanaPkgPath = require.resolve('../../../package.json'); const kibanaPkg = require(kibanaPkgPath); // eslint-disable-line import/no-dynamic-require -const KBN_DIR = dirname(kibanaPkgPath); - -module.exports = function({ name }) { +module.exports = function({ name, targetPath, isKibanaPlugin }) { return { prompts: { description: { @@ -47,41 +45,38 @@ module.exports = function({ name }) { message: 'Should an app component be generated?', default: true, }, - generateTranslations: { - type: 'confirm', - message: 'Should translation files be generated?', - default: true, - }, - generateHack: { - type: 'confirm', - message: 'Should a hack component be generated?', - default: true, - }, generateApi: { type: 'confirm', message: 'Should a server API be generated?', default: true, }, + // generateTranslations: { + // type: 'confirm', + // message: 'Should translation files be generated?', + // default: true, + // }, generateScss: { type: 'confirm', message: 'Should SCSS be used?', when: answers => answers.generateApp, default: true, }, + generateEslint: { + type: 'confirm', + message: 'Would you like to use a custom eslint file?', + default: !isKibanaPlugin, + }, }, filters: { + 'public/**/index.scss': 'generateScss', 'public/**/*': 'generateApp', - 'translations/**/*': 'generateTranslations', - '.i18nrc.json': 'generateTranslations', - 'public/hack.js': 'generateHack', 'server/**/*': 'generateApi', - 'public/app.scss': 'generateScss', - '.kibana-plugin-helpers.json': 'generateScss', + // 'translations/**/*': 'generateTranslations', + // '.i18nrc.json': 'generateTranslations', + 'eslintrc.js': 'generateEslint', }, move: { - gitignore: '.gitignore', 'eslintrc.js': '.eslintrc.js', - 'package_template.json': 'package.json', }, data: answers => Object.assign( @@ -91,34 +86,36 @@ module.exports = function({ name }) { camelCase, snakeCase, name, + isKibanaPlugin, + kbnVersion: answers.kbnVersion, + upperCamelCaseName: name.charAt(0).toUpperCase() + camelCase(name).slice(1), + hasUi: !!answers.generateApp, + hasServer: !!answers.generateApi, + hasScss: !!answers.generateScss, + relRoot: isKibanaPlugin ? '../../../..' : '../../..', }, answers ), enforceNewFolder: true, installDependencies: false, - gitInit: true, + gitInit: !isKibanaPlugin, async post({ log }) { - await execa('yarn', ['kbn', 'bootstrap'], { - cwd: KBN_DIR, - stdio: 'inherit', - }); - - const dir = relative(process.cwd(), resolve(KBN_DIR, 'plugins', snakeCase(name))); + const dir = relative(process.cwd(), targetPath); + // Apply eslint to the generated plugin try { - await execa('yarn', ['lint', '--fix'], { - cwd: dir, - all: true, - }); + await execa('yarn', ['lint:es', `./${dir}/**/*.ts*`, '--no-ignore', '--fix']); } catch (error) { - throw new Error(`Failure when running prettier on the generated output: ${error.all}`); + console.error(error); + throw new Error( + `Failure when running prettier on the generated output: ${error.all || error}` + ); } log.success(chalk`🎉 -Your plugin has been created in {bold ${dir}}. Move into that directory to run it: +Your plugin has been created in {bold ${dir}}. - {bold cd "${dir}"} {bold yarn start} `); }, diff --git a/packages/kbn-plugin-generator/sao_template/sao.test.js b/packages/kbn-plugin-generator/sao_template/sao.test.js index 80149c008dad8..0dbdb7d3c097b 100755 --- a/packages/kbn-plugin-generator/sao_template/sao.test.js +++ b/packages/kbn-plugin-generator/sao_template/sao.test.js @@ -19,8 +19,6 @@ const sao = require('sao'); -const templatePkg = require('../package.json'); - const template = { fromPath: __dirname, configOptions: { @@ -32,121 +30,57 @@ function getFileContents(file) { return file.contents.toString(); } -function getConfig(file) { - const contents = getFileContents(file).replace(/\r?\n/gm, ''); - return contents.split('kibana.Plugin(')[1]; -} - describe('plugin generator sao integration', () => { test('skips files when answering no', async () => { const res = await sao.mockPrompt(template, { generateApp: false, - generateHack: false, generateApi: false, }); - expect(res.fileList).not.toContain('public/app.js'); - expect(res.fileList).not.toContain('public/__tests__/index.js'); - expect(res.fileList).not.toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).not.toContain('app:'); - expect(uiExports).not.toContain('hacks:'); - expect(uiExports).not.toContain('init(server, options)'); - expect(uiExports).not.toContain('registerFeature('); + expect(res.fileList).toContain('common/index.ts'); + expect(res.fileList).not.toContain('public/index.ts'); + expect(res.fileList).not.toContain('server/index.ts'); }); it('includes app when answering yes', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: false, - generateApi: false, - }); - - // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).not.toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); - expect(uiExports).not.toContain('hacks:'); - }); - - it('includes hack when answering yes', async () => { - const res = await sao.mockPrompt(template, { - generateApp: true, - generateHack: true, generateApi: false, }); // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).toContain('public/hack.js'); - expect(res.fileList).not.toContain('server/routes/example.js'); - expect(res.fileList).not.toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('hacks:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); + expect(res.fileList).toContain('common/index.ts'); + expect(res.fileList).toContain('public/index.ts'); + expect(res.fileList).toContain('public/plugin.ts'); + expect(res.fileList).toContain('public/types.ts'); + expect(res.fileList).toContain('public/components/app.tsx'); + expect(res.fileList).not.toContain('server/index.ts'); }); it('includes server api when answering yes', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); // check output files - expect(res.fileList).toContain('public/app.js'); - expect(res.fileList).toContain('public/__tests__/index.js'); - expect(res.fileList).toContain('public/hack.js'); - expect(res.fileList).toContain('server/routes/example.js'); - expect(res.fileList).toContain('server/__tests__/index.js'); - - const uiExports = getConfig(res.files['index.js']); - expect(uiExports).toContain('app:'); - expect(uiExports).toContain('hacks:'); - expect(uiExports).toContain('init(server, options)'); - expect(uiExports).toContain('registerFeature('); - }); - - it('plugin config has correct name and main path', async () => { - const res = await sao.mockPrompt(template, { - generateApp: true, - generateHack: true, - generateApi: true, - }); - - const indexContents = getFileContents(res.files['index.js']); - const nameLine = indexContents.match('name: (.*)')[1]; - const mainLine = indexContents.match('main: (.*)')[1]; - - expect(nameLine).toContain('some_fancy_plugin'); - expect(mainLine).toContain('plugins/some_fancy_plugin/app'); + expect(res.fileList).toContain('public/plugin.ts'); + expect(res.fileList).toContain('server/plugin.ts'); + expect(res.fileList).toContain('server/index.ts'); + expect(res.fileList).toContain('server/types.ts'); + expect(res.fileList).toContain('server/routes/index.ts'); }); - it('plugin package has correct name', async () => { + it('plugin package has correct title', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); - const packageContents = getFileContents(res.files['package.json']); - const pkg = JSON.parse(packageContents); + const contents = getFileContents(res.files['common/index.ts']); + const controllerLine = contents.match("PLUGIN_NAME = '(.*)'")[1]; - expect(pkg.name).toBe('some_fancy_plugin'); + expect(controllerLine).toContain('Some fancy plugin'); }); it('package has version "kibana" with master', async () => { @@ -154,10 +88,10 @@ describe('plugin generator sao integration', () => { kbnVersion: 'master', }); - const packageContents = getFileContents(res.files['package.json']); + const packageContents = getFileContents(res.files['kibana.json']); const pkg = JSON.parse(packageContents); - expect(pkg.kibana.version).toBe('kibana'); + expect(pkg.version).toBe('master'); }); it('package has correct version', async () => { @@ -165,39 +99,26 @@ describe('plugin generator sao integration', () => { kbnVersion: 'v6.0.0', }); - const packageContents = getFileContents(res.files['package.json']); - const pkg = JSON.parse(packageContents); - - expect(pkg.kibana.version).toBe('v6.0.0'); - }); - - it('package has correct templateVersion', async () => { - const res = await sao.mockPrompt(template, { - kbnVersion: 'master', - }); - - const packageContents = getFileContents(res.files['package.json']); + const packageContents = getFileContents(res.files['kibana.json']); const pkg = JSON.parse(packageContents); - expect(pkg.kibana.templateVersion).toBe(templatePkg.version); + expect(pkg.version).toBe('v6.0.0'); }); it('sample app has correct values', async () => { const res = await sao.mockPrompt(template, { generateApp: true, - generateHack: true, generateApi: true, }); - const contents = getFileContents(res.files['public/app.js']); - const controllerLine = contents.match('setRootController(.*)')[1]; + const contents = getFileContents(res.files['common/index.ts']); + const controllerLine = contents.match("PLUGIN_ID = '(.*)'")[1]; expect(controllerLine).toContain('someFancyPlugin'); }); it('includes dotfiles', async () => { const res = await sao.mockPrompt(template); - expect(res.files['.gitignore']).toBeTruthy(); expect(res.files['.eslintrc.js']).toBeTruthy(); }); }); diff --git a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json b/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json deleted file mode 100644 index 1a8aea8853876..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/.i18nrc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "paths": { - "<%= camelCase(name) %>": "./" - }, - "translations": [ - "translations/zh-CN.json" - ] -} - diff --git a/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json b/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json deleted file mode 100644 index 383368c7f8ce1..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/.kibana-plugin-helpers.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "styleSheetToCompile": "public/app.scss" -} diff --git a/packages/kbn-plugin-generator/sao_template/template/README.md b/packages/kbn-plugin-generator/sao_template/template/README.md index 59c3adf2713c8..1e0139428fcbc 100755 --- a/packages/kbn-plugin-generator/sao_template/template/README.md +++ b/packages/kbn-plugin-generator/sao_template/template/README.md @@ -6,34 +6,7 @@ --- -## development +## Development -See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. Once you have completed that, use the following yarn scripts. +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. - - `yarn kbn bootstrap` - - Install dependencies and crosslink Kibana and all projects/plugins. - - > ***IMPORTANT:*** Use this script instead of `yarn` to install dependencies when switching branches, and re-run it whenever your dependencies change. - - - `yarn start` - - Start kibana and have it include this plugin. You can pass any arguments that you would normally send to `bin/kibana` - - ``` - yarn start --elasticsearch.hosts http://localhost:9220 - ``` - - - `yarn build` - - Build a distributable archive of your plugin. - - - `yarn test:browser` - - Run the browser tests in a real web browser. - - - `yarn test:mocha` - - Run the server tests using mocha. - -For more information about any of these commands run `yarn ${task} --help`. For a full list of tasks checkout the `package.json` file, or run `yarn run`. diff --git a/packages/kbn-plugin-generator/sao_template/template/common/index.ts b/packages/kbn-plugin-generator/sao_template/template/common/index.ts new file mode 100644 index 0000000000000..90ffcb70045aa --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/common/index.ts @@ -0,0 +1,2 @@ +export const PLUGIN_ID = '<%= camelCase(name) %>'; +export const PLUGIN_NAME = '<%= name %>'; diff --git a/packages/kbn-plugin-generator/sao_template/template/eslintrc.js b/packages/kbn-plugin-generator/sao_template/template/eslintrc.js old mode 100755 new mode 100644 index e1dfadc212b7e..b68d42e32e047 --- a/packages/kbn-plugin-generator/sao_template/template/eslintrc.js +++ b/packages/kbn-plugin-generator/sao_template/template/eslintrc.js @@ -1,24 +1,9 @@ -module.exports = { - root: true, +module.exports = { + root: true, extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'], - settings: { - 'import/resolver': { - '@kbn/eslint-import-resolver-kibana': { - rootPackageName: '<%= snakeCase(name) %>', - }, - }, - }, - overrides: [ - { - files: ['**/public/**/*'], - settings: { - 'import/resolver': { - '@kbn/eslint-import-resolver-kibana': { - forceNode: false, - rootPackageName: '<%= snakeCase(name) %>', - }, - }, - }, - }, - ] -}; + <%_ if (!isKibanaPlugin) { -%> + rules: { + "@kbn/eslint/require-license-header": "off" + } + <%_ } -%> +}; \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/gitignore b/packages/kbn-plugin-generator/sao_template/template/gitignore deleted file mode 100755 index db28fed19376d..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/gitignore +++ /dev/null @@ -1,6 +0,0 @@ -npm-debug.log* -node_modules -/build/ -<%_ if (generateScss) { -%> -/public/app.css -<%_ } -%> diff --git a/packages/kbn-plugin-generator/sao_template/template/index.js b/packages/kbn-plugin-generator/sao_template/template/index.js deleted file mode 100755 index 4bc3347ae6019..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/index.js +++ /dev/null @@ -1,89 +0,0 @@ -<% if (generateScss) { -%> -import { resolve } from 'path'; -import { existsSync } from 'fs'; - -<% } -%> - -<% if (generateApp) { -%> -import { i18n } from '@kbn/i18n'; -<% } -%> - -<% if (generateApi) { -%> -import exampleRoute from './server/routes/example'; - -<% } -%> -export default function (kibana) { - return new kibana.Plugin({ - require: ['elasticsearch'], - name: '<%= snakeCase(name) %>', - uiExports: { - <%_ if (generateApp) { -%> - app: { - title: '<%= startCase(name) %>', - description: '<%= description %>', - main: 'plugins/<%= snakeCase(name) %>/app', - }, - <%_ } -%> - <%_ if (generateHack) { -%> - hacks: [ - 'plugins/<%= snakeCase(name) %>/hack' - ], - <%_ } -%> - <%_ if (generateScss) { -%> - styleSheetPaths: [resolve(__dirname, 'public/app.scss'), resolve(__dirname, 'public/app.css')].find(p => existsSync(p)), - <%_ } -%> - }, - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - <%_ if (generateApi || generateApp) { -%> - - // eslint-disable-next-line no-unused-vars - init(server, options) { - <%_ if (generateApp) { -%> - const xpackMainPlugin = server.plugins.xpack_main; - if (xpackMainPlugin) { - const featureId = '<%= snakeCase(name) %>'; - - xpackMainPlugin.registerFeature({ - id: featureId, - name: i18n.translate('<%= camelCase(name) %>.featureRegistry.featureName', { - defaultMessage: '<%= name %>', - }), - navLinkId: featureId, - icon: 'questionInCircle', - app: [featureId, 'kibana'], - catalogue: [], - privileges: { - all: { - api: [], - savedObject: { - all: [], - read: [], - }, - ui: ['show'], - }, - read: { - api: [], - savedObject: { - all: [], - read: [], - }, - ui: ['show'], - }, - }, - }); - } - <%_ } -%> - - <%_ if (generateApi) { -%> - // Add server routes and initialize the plugin here - exampleRoute(server); - <%_ } -%> - } - <%_ } -%> - }); -} diff --git a/packages/kbn-plugin-generator/sao_template/template/kibana.json b/packages/kbn-plugin-generator/sao_template/template/kibana.json new file mode 100644 index 0000000000000..f8bb07040abeb --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "<%= camelCase(name) %>", + "version": "<%= kbnVersion %>", + "server": <%= hasServer %>, + "ui": <%= hasUi %>, + "requiredPlugins": ["navigation"], + "optionalPlugins": [] +} diff --git a/packages/kbn-plugin-generator/sao_template/template/package_template.json b/packages/kbn-plugin-generator/sao_template/template/package_template.json deleted file mode 100644 index 4b6629fa90268..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/package_template.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "<%= snakeCase(name) %>", - "version": "0.0.0", - "description": "<%= description %>", - "main": "index.js", - "kibana": { - "version": "<%= (kbnVersion === 'master') ? 'kibana' : kbnVersion %>", - "templateVersion": "<%= templateVersion %>" - }, - "scripts": { - "preinstall": "node ../../preinstall_check", - "kbn": "node ../../scripts/kbn", - "es": "node ../../scripts/es", - "lint": "eslint .", - "start": "plugin-helpers start", - "test:server": "plugin-helpers test:server", - "test:browser": "plugin-helpers test:browser", - "build": "plugin-helpers build" - }, - <%_ if (generateTranslations) { _%> - "dependencies": { - "@kbn/i18n": "link:../../packages/kbn-i18n" - }, - <%_ } _%> - "devDependencies": { - "@elastic/eslint-config-kibana": "link:../../packages/eslint-config-kibana", - "@elastic/eslint-import-resolver-kibana": "link:../../packages/kbn-eslint-import-resolver-kibana", - "@kbn/expect": "link:../../packages/kbn-expect", - "@kbn/plugin-helpers": "link:../../packages/kbn-plugin-helpers", - "babel-eslint": "^10.0.1", - "eslint": "^5.16.0", - "eslint-plugin-babel": "^5.3.0", - "eslint-plugin-import": "^2.16.0", - "eslint-plugin-jest": "^22.4.1", - "eslint-plugin-jsx-a11y": "^6.2.1", - "eslint-plugin-mocha": "^5.3.0", - "eslint-plugin-no-unsanitized": "^3.0.2", - "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.12.4" - } -} diff --git a/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js b/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js deleted file mode 100755 index 9320bd7b028a8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/__tests__/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import expect from '@kbn/expect'; - -describe('suite', () => { - it('is a test', () => { - expect(true).to.equal(true); - }); -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.js b/packages/kbn-plugin-generator/sao_template/template/public/app.js deleted file mode 100755 index 37a7c37e916a0..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/app.js +++ /dev/null @@ -1,45 +0,0 @@ -import React from 'react'; -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; -import { render, unmountComponentAtNode } from 'react-dom'; -<%_ if (generateTranslations) { _%> -import { I18nProvider } from '@kbn/i18n/react'; -<%_ } _%> - -import { Main } from './components/main'; - -const app = uiModules.get('apps/<%= camelCase(name) %>'); - -app.config($locationProvider => { - $locationProvider.html5Mode({ - enabled: false, - requireBase: false, - rewriteLinks: false, - }); -}); -app.config(stateManagementConfigProvider => - stateManagementConfigProvider.disable() -); - -function RootController($scope, $element, $http) { - const domNode = $element[0]; - - // render react to DOM - <%_ if (generateTranslations) { _%> - render( - -
- , - domNode - ); - <%_ } else { _%> - render(
, domNode); - <%_ } _%> - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); -} - -chrome.setRootController('<%= camelCase(name) %>', RootController); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/application.tsx b/packages/kbn-plugin-generator/sao_template/template/public/application.tsx new file mode 100644 index 0000000000000..8106a18a784e7 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/application.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { AppMountParameters, CoreStart } from '<%= relRoot %>/src/core/public'; +import { AppPluginStartDependencies } from './types'; +import { <%= upperCamelCaseName %>App } from './components/app'; + + +export const renderApp = ( + { notifications, http }: CoreStart, + { navigation }: AppPluginStartDependencies, + { appBasePath, element }: AppMountParameters + ) => { + ReactDOM.render( + <<%= upperCamelCaseName %>App + basename={appBasePath} + notifications={notifications} + http={http} + navigation={navigation} + />, + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); + }; + \ No newline at end of file diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx b/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx new file mode 100644 index 0000000000000..7b259a9c5b99d --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/components/app.tsx @@ -0,0 +1,129 @@ +/* + * 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 React, { useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { BrowserRouter as Router } from 'react-router-dom'; + +import { + EuiButton, + EuiHorizontalRule, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageHeader, + EuiTitle, + EuiText, +} from '@elastic/eui'; + +import { CoreStart } from '<%= relRoot %>/../src/core/public'; +import { NavigationPublicPluginStart } from '<%= relRoot %>/../src/plugins/navigation/public'; + +import { PLUGIN_ID, PLUGIN_NAME } from '../../common'; + +interface <%= upperCamelCaseName %>AppDeps { + basename: string; + notifications: CoreStart['notifications']; + http: CoreStart['http']; + navigation: NavigationPublicPluginStart; +} + +export const <%= upperCamelCaseName %>App = ({ basename, notifications, http, navigation }: <%= upperCamelCaseName %>AppDeps) => { + // Use React hooks to manage state. + const [timestamp, setTimestamp] = useState(); + + const onClickHandler = () => { +<%_ if (generateApi) { -%> + // Use the core http service to make a response to the server API. + http.get('/api/<%= snakeCase(name) %>/example').then(res => { + setTimestamp(res.time); + // Use the core notifications service to display a success message. + notifications.toasts.addSuccess(i18n.translate('<%= camelCase(name) %>.dataUpdated', { + defaultMessage: 'Data updated', + })); + }); +<%_ } else { -%> + setTimestamp(new Date().toISOString()); + notifications.toasts.addSuccess(PLUGIN_NAME); +<%_ } -%> + }; + + // Render the application DOM. + // Note that `navigation.ui.TopNavMenu` is a stateful component exported on the `navigation` plugin's start contract. + return ( + + + <> + + + + + +

+ +

+
+
+ + + +

+ +

+
+
+ + +

+ +

+ +

+ +

+ + + +
+
+
+
+
+ +
+
+ ); +}; diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js b/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js deleted file mode 100644 index 68710baa1bee8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/components/main/index.js +++ /dev/null @@ -1 +0,0 @@ -export { Main } from './main'; diff --git a/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js b/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js deleted file mode 100644 index 59fd667c709aa..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/components/main/main.js +++ /dev/null @@ -1,97 +0,0 @@ -import React from 'react'; -import { - EuiPage, - EuiPageHeader, - EuiTitle, - EuiPageBody, - EuiPageContent, - EuiPageContentHeader, - EuiPageContentBody, - EuiText -} from '@elastic/eui'; -<%_ if (generateTranslations) { _%> -import { FormattedMessage } from '@kbn/i18n/react'; -<%_ } _%> - -export class Main extends React.Component { - constructor(props) { - super(props); - this.state = {}; - } - - componentDidMount() { - /* - FOR EXAMPLE PURPOSES ONLY. There are much better ways to - manage state and update your UI than this. - */ - const { httpClient } = this.props; - httpClient.get('../api/<%= name %>/example').then((resp) => { - this.setState({ time: resp.data.time }); - }); - } - render() { - const { title } = this.props; - return ( - - - - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - {title} Hello World! - <%_ } _%> -

-
-
- - - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - Congratulations - <%_ } _%> -

-
-
- - -

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - You have successfully created your first Kibana Plugin! - <%_ } _%> -

-

- <%_ if (generateTranslations) { _%> - - <%_ } else { _%> - The server time (via API call) is {this.state.time || 'NO API CALL YET'} - <%_ } _%> -

-
-
-
-
-
- ); - } -} diff --git a/packages/kbn-plugin-generator/sao_template/template/public/hack.js b/packages/kbn-plugin-generator/sao_template/template/public/hack.js deleted file mode 100755 index 775526c8e44a3..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/public/hack.js +++ /dev/null @@ -1,7 +0,0 @@ -import $ from 'jquery'; - -$(document.body).on('keypress', function (event) { - if (event.which === 58) { - alert('boo!'); - } -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/public/app.scss b/packages/kbn-plugin-generator/sao_template/template/public/index.scss similarity index 100% rename from packages/kbn-plugin-generator/sao_template/template/public/app.scss rename to packages/kbn-plugin-generator/sao_template/template/public/index.scss diff --git a/packages/kbn-plugin-generator/sao_template/template/public/index.ts b/packages/kbn-plugin-generator/sao_template/template/public/index.ts new file mode 100644 index 0000000000000..2999dc7264ddb --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/index.ts @@ -0,0 +1,16 @@ +<%_ if (hasScss) { -%> +import './index.scss'; +<%_ } -%> + +import { <%= upperCamelCaseName %>Plugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin() { + return new <%= upperCamelCaseName %>Plugin(); +} +export { + <%= upperCamelCaseName %>PluginSetup, + <%= upperCamelCaseName %>PluginStart, +} from './types'; + diff --git a/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts b/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts new file mode 100644 index 0000000000000..76f7f1a6f9908 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/plugin.ts @@ -0,0 +1,42 @@ +import { i18n } from '@kbn/i18n'; +import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '<%= relRoot %>/src/core/public'; +import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart, AppPluginStartDependencies } from './types'; +import { PLUGIN_NAME } from '../common'; + +export class <%= upperCamelCaseName %>Plugin + implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> { + + public setup(core: CoreSetup): <%= upperCamelCaseName %>PluginSetup { + // Register an application into the side navigation menu + core.application.register({ + id: '<%= camelCase(name) %>', + title: PLUGIN_NAME, + async mount(params: AppMountParameters) { + // Load application bundle + const { renderApp } = await import('./application'); + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Render the application + return renderApp(coreStart, depsStart as AppPluginStartDependencies, params); + }, + }); + + // Return methods that should be available to other plugins + return { + getGreeting() { + return i18n.translate('<%= camelCase(name) %>.greetingText', { + defaultMessage: 'Hello from {name}!', + values: { + name: PLUGIN_NAME, + }, + }); + }, + }; + } + + public start(core: CoreStart): <%= upperCamelCaseName %>PluginStart { + return {}; + } + + public stop() {} +} diff --git a/packages/kbn-plugin-generator/sao_template/template/public/types.ts b/packages/kbn-plugin-generator/sao_template/template/public/types.ts new file mode 100644 index 0000000000000..2ebb0c0d1257f --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/public/types.ts @@ -0,0 +1,11 @@ +import { NavigationPublicPluginStart } from '<%= relRoot %>/src/plugins/navigation/public'; + +export interface <%= upperCamelCaseName %>PluginSetup { + getGreeting: () => string; +} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginStart {} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart +}; diff --git a/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js b/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js deleted file mode 100755 index 9320bd7b028a8..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/server/__tests__/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import expect from '@kbn/expect'; - -describe('suite', () => { - it('is a test', () => { - expect(true).to.equal(true); - }); -}); diff --git a/packages/kbn-plugin-generator/sao_template/template/server/index.ts b/packages/kbn-plugin-generator/sao_template/template/server/index.ts new file mode 100644 index 0000000000000..816b8faec2a45 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/index.ts @@ -0,0 +1,15 @@ +import { PluginInitializerContext } from '<%= relRoot %>/src/core/server'; +import { <%= upperCamelCaseName %>Plugin } from './plugin'; + + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. + + export function plugin(initializerContext: PluginInitializerContext) { + return new <%= upperCamelCaseName %>Plugin(initializerContext); +} + +export { + <%= upperCamelCaseName %>PluginSetup, + <%= upperCamelCaseName %>PluginStart, +} from './types'; diff --git a/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts b/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts new file mode 100644 index 0000000000000..d6a343209e39e --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/plugin.ts @@ -0,0 +1,30 @@ +import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '<%= relRoot %>/src/core/server'; + +import { <%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart } from './types'; +import { defineRoutes } from './routes'; + +export class <%= upperCamelCaseName %>Plugin + implements Plugin<<%= upperCamelCaseName %>PluginSetup, <%= upperCamelCaseName %>PluginStart> { + private readonly logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get(); + } + + public setup(core: CoreSetup) { + this.logger.debug('<%= name %>: Setup'); + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('<%= name %>: Started'); + return {}; + } + + public stop() {} +} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js b/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js deleted file mode 100755 index 5a612645f48fc..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/server/routes/example.js +++ /dev/null @@ -1,11 +0,0 @@ -export default function (server) { - - server.route({ - path: '/api/<%= name %>/example', - method: 'GET', - handler() { - return { time: (new Date()).toISOString() }; - } - }); - -} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts b/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts new file mode 100644 index 0000000000000..d8bb00f0dea6c --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/routes/index.ts @@ -0,0 +1,17 @@ +import { IRouter } from '<%= relRoot %>/../src/core/server'; + +export function defineRoutes(router: IRouter) { + router.get( + { + path: '/api/<%= snakeCase(name) %>/example', + validate: false, + }, + async (context, request, response) => { + return response.ok({ + body: { + time: new Date().toISOString(), + }, + }); + } + ); +} diff --git a/packages/kbn-plugin-generator/sao_template/template/server/types.ts b/packages/kbn-plugin-generator/sao_template/template/server/types.ts new file mode 100644 index 0000000000000..adbc5e93f03c5 --- /dev/null +++ b/packages/kbn-plugin-generator/sao_template/template/server/types.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface <%= upperCamelCaseName %>PluginStart {} diff --git a/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json b/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json deleted file mode 100644 index 3447511c6739a..0000000000000 --- a/packages/kbn-plugin-generator/sao_template/template/translations/zh-CN.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "formats": { - "number": { - "currency": { - "style": "currency" - }, - "percent": { - "style": "percent" - } - }, - "date": { - "short": { - "month": "numeric", - "day": "numeric", - "year": "2-digit" - }, - "medium": { - "month": "short", - "day": "numeric", - "year": "numeric" - }, - "long": { - "month": "long", - "day": "numeric", - "year": "numeric" - }, - "full": { - "weekday": "long", - "month": "long", - "day": "numeric", - "year": "numeric" - } - }, - "time": { - "short": { - "hour": "numeric", - "minute": "numeric" - }, - "medium": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric" - }, - "long": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short" - }, - "full": { - "hour": "numeric", - "minute": "numeric", - "second": "numeric", - "timeZoneName": "short" - } - }, - "relative": { - "years": { - "units": "year" - }, - "months": { - "units": "month" - }, - "days": { - "units": "day" - }, - "hours": { - "units": "hour" - }, - "minutes": { - "units": "minute" - }, - "seconds": { - "units": "second" - } - } - }, - "messages": { - "<%= camelCase(name) %>.congratulationsText": "您已经成功创建第一个 Kibana 插件。", - "<%= camelCase(name) %>.congratulationsTitle": "恭喜!", - "<%= camelCase(name) %>.helloWorldText": "{title} 您好,世界!", - "<%= camelCase(name) %>.serverTimeText": "服务器时间(通过 API 调用)为 {time}" - } -} diff --git a/packages/kbn-plugin-generator/tsconfig.json b/packages/kbn-plugin-generator/tsconfig.json new file mode 100644 index 0000000000000..fe0f7112f1fa9 --- /dev/null +++ b/packages/kbn-plugin-generator/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["**/*", "index.js.d.ts"], + "exclude": ["sao_template/template/*"] +} From 4aa727560a72b3f284910dd34e78d5b9dbc81ebc Mon Sep 17 00:00:00 2001 From: Shahzad Date: Mon, 3 Feb 2020 19:00:40 +0100 Subject: [PATCH 13/60] fix timespan referencing to same values (#56601) (#56612) --- .../server/lib/adapters/monitor_states/search/query_context.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts index 961cc94dcea19..a51931ba11630 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import moment from 'moment'; import { APICaller } from 'kibana/server'; import { CursorPagination } from '../adapter_types'; import { INDEX_NAMES } from '../../../../../common/constants'; @@ -97,7 +98,7 @@ export class QueryContext { // behavior. const tsEnd = parseRelativeDate(this.dateRangeEnd, { roundUp: true })!; - const tsStart = tsEnd.subtract(5, 'minutes'); + const tsStart = moment(tsEnd).subtract(5, 'minutes'); return { range: { From e28e149b46d27df6f4477a4e83600742431873b6 Mon Sep 17 00:00:00 2001 From: CJ Cenizal Date: Mon, 3 Feb 2020 11:20:41 -0800 Subject: [PATCH 14/60] Fix incorrect app name in ILM license checker. (#56355) --- .../server/lib/check_license/check_license.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/index_lifecycle_management/server/lib/check_license/check_license.js b/x-pack/legacy/plugins/index_lifecycle_management/server/lib/check_license/check_license.js index e71820a346f91..7534f3cd0934e 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/server/lib/check_license/check_license.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/server/lib/check_license/check_license.js @@ -5,7 +5,7 @@ */ import { i18n } from '@kbn/i18n'; export function checkLicense(xpackLicenseInfo) { - const pluginName = 'Index Management'; + const pluginName = 'Index Lifecycle Policies'; // If, for some reason, we cannot get the license information // from Elasticsearch, assume worst case and disable From 6398a9911df0d45ba72dafbb2d9c8398916ddba6 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Mon, 3 Feb 2020 15:55:50 -0500 Subject: [PATCH 15/60] [Monitoring] Migrate license expiration alert to Kibana alerting (#54306) * License expiration * Flip off * Only require alerting and actions if enabled * Support date formating and timezones in the alert UI messages, support ccs better * Fix status tests * Fix up front end tests * Fix linting, and switch this back * Add this back in so legacy alerts continue to work * Fix type issues * Handle CCS better * Code cleanup * Fix type issues * Flip this off, and fix test * Moved the email address config to advanced settings, but need help with test failures and typescript * Fix issue with task manager * Deprecate email_address * Use any until we can figure out this TS issue * Fix type issue * More tests * Fix mocha tests * Use mock instead of any * I'm not sure why these changed... * Provide timezone in moment usage in tests for consistency * Fix type issue * Change how we get dateFormat and timezone * Change where we calculate the dates to show in the alerts UI * Show deprecation warning based on the feature toggle * Ensure we are using UTC * PR feedback * Only add this if the feature flag is enabled * Fix tests * Ensure we only attempt to look this up if the feature flag is enabled Co-authored-by: Elastic Machine --- .../common/{constants.js => constants.ts} | 42 ++ .../legacy/plugins/monitoring/deprecations.js | 20 +- x-pack/legacy/plugins/monitoring/index.js | 12 +- .../alerts/__snapshots__/status.test.tsx.snap | 70 +++ .../__snapshots__/configuration.test.tsx.snap | 120 +++++ .../__snapshots__/step1.test.tsx.snap | 297 ++++++++++++ .../__snapshots__/step2.test.tsx.snap | 49 ++ .../__snapshots__/step3.test.tsx.snap | 95 ++++ .../configuration/configuration.test.tsx | 147 ++++++ .../alerts/configuration/configuration.tsx | 193 ++++++++ .../components/alerts/configuration/index.ts | 7 + .../alerts/configuration/step1.test.tsx | 338 +++++++++++++ .../components/alerts/configuration/step1.tsx | 334 +++++++++++++ .../alerts/configuration/step2.test.tsx | 51 ++ .../components/alerts/configuration/step2.tsx | 38 ++ .../alerts/configuration/step3.test.tsx | 48 ++ .../components/alerts/configuration/step3.tsx | 47 ++ .../components/alerts/manage_email_action.tsx | 301 ++++++++++++ .../public/components/alerts/status.test.tsx | 81 ++++ .../public/components/alerts/status.tsx | 203 ++++++++ .../cluster/overview/alerts_panel.js | 62 ++- .../components/cluster/overview/index.js | 14 +- .../plugins/monitoring/public/jest.helpers.ts | 36 ++ ...rror_handler.js => ajax_error_handler.tsx} | 7 +- .../monitoring/public/lib/form_validation.ts | 48 ++ .../monitoring/public/lib/setup_mode.test.js | 8 +- .../lib/{setup_mode.js => setup_mode.tsx} | 62 ++- .../monitoring/public/views/alerts/index.js | 2 +- .../public/views/cluster/overview/index.js | 21 +- .../server/alerts/license_expiration.test.ts | 453 ++++++++++++++++++ .../server/alerts/license_expiration.ts | 162 +++++++ .../monitoring/server/alerts/types.d.ts | 45 ++ .../lib/alerts/fetch_available_ccs.test.ts | 36 ++ .../server/lib/alerts/fetch_available_ccs.ts | 19 + .../server/lib/alerts/fetch_clusters.test.ts | 33 ++ .../server/lib/alerts/fetch_clusters.ts | 52 ++ .../fetch_default_email_address.test.ts | 17 + .../lib/alerts/fetch_default_email_address.ts | 13 + .../server/lib/alerts/fetch_licenses.test.ts | 105 ++++ .../server/lib/alerts/fetch_licenses.ts | 67 +++ .../server/lib/alerts/fetch_status.ts | 87 ++++ .../lib/alerts/get_ccs_index_pattern.test.ts | 24 + .../lib/alerts/get_ccs_index_pattern.ts | 13 + .../lib/alerts/license_expiration.lib.test.ts | 55 +++ .../lib/alerts/license_expiration.lib.ts | 58 +++ .../lib/cluster/get_clusters_from_request.js | 32 +- .../monitoring/server/lib/get_date_format.js | 9 + .../setup/collection/get_collection_status.js | 1 - .../plugins/monitoring/server/plugin.js | 39 +- .../server/routes/api/v1/alerts/alerts.js | 89 ++++ .../server/routes/api/v1/alerts/index.js | 53 +- .../routes/api/v1/alerts/legacy_alerts.js | 57 +++ .../monitoring/server/routes/api/v1/ui.js | 2 +- .../legacy/plugins/monitoring/ui_exports.js | 72 ++- x-pack/plugins/actions/common/types.ts | 7 + 55 files changed, 4223 insertions(+), 130 deletions(-) rename x-pack/legacy/plugins/monitoring/common/{constants.js => constants.ts} (85%) create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/__snapshots__/status.test.tsx.snap create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step2.test.tsx.snap create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step3.test.tsx.snap create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/index.ts create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.test.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.test.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/manage_email_action.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx create mode 100644 x-pack/legacy/plugins/monitoring/public/jest.helpers.ts rename x-pack/legacy/plugins/monitoring/public/lib/{ajax_error_handler.js => ajax_error_handler.tsx} (94%) create mode 100644 x-pack/legacy/plugins/monitoring/public/lib/form_validation.ts rename x-pack/legacy/plugins/monitoring/public/lib/{setup_mode.js => setup_mode.tsx} (76%) create mode 100644 x-pack/legacy/plugins/monitoring/server/alerts/license_expiration.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/alerts/license_expiration.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/alerts/types.d.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_status.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts create mode 100644 x-pack/legacy/plugins/monitoring/server/lib/get_date_format.js create mode 100644 x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/alerts.js create mode 100644 x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js diff --git a/x-pack/legacy/plugins/monitoring/common/constants.js b/x-pack/legacy/plugins/monitoring/common/constants.ts similarity index 85% rename from x-pack/legacy/plugins/monitoring/common/constants.js rename to x-pack/legacy/plugins/monitoring/common/constants.ts index ff16b0e9c5167..53764f592dc15 100644 --- a/x-pack/legacy/plugins/monitoring/common/constants.js +++ b/x-pack/legacy/plugins/monitoring/common/constants.ts @@ -233,3 +233,45 @@ export const REPORTING_SYSTEM_ID = 'reporting'; * @type {Number} */ export const TELEMETRY_COLLECTION_INTERVAL = 86400000; + +/** + * We want to slowly rollout the migration from watcher-based cluster alerts to + * kibana alerts and we only want to enable the kibana alerts once all + * watcher-based cluster alerts have been migrated so this flag will serve + * as the only way to see the new UI and actually run Kibana alerts. It will + * be false until all alerts have been migrated, then it will be removed + */ +export const KIBANA_ALERTING_ENABLED = false; + +/** + * The prefix for all alert types used by monitoring + */ +export const ALERT_TYPE_PREFIX = 'monitoring_'; + +/** + * This is the alert type id for the license expiration alert + */ +export const ALERT_TYPE_LICENSE_EXPIRATION = `${ALERT_TYPE_PREFIX}alert_type_license_expiration`; + +/** + * A listing of all alert types + */ +export const ALERT_TYPES = [ALERT_TYPE_LICENSE_EXPIRATION]; + +/** + * Matches the id for the built-in in email action type + * See x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts + */ +export const ALERT_ACTION_TYPE_EMAIL = '.email'; + +/** + * The number of alerts that have been migrated + */ +export const NUMBER_OF_MIGRATED_ALERTS = 1; + +/** + * The advanced settings config name for the email address + */ +export const MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS = 'monitoring:alertingEmailAddress'; + +export const ALERT_EMAIL_SERVICES = ['gmail', 'hotmail', 'icloud', 'outlook365', 'ses', 'yahoo']; diff --git a/x-pack/legacy/plugins/monitoring/deprecations.js b/x-pack/legacy/plugins/monitoring/deprecations.js index 6e35e86dd9d71..ae8650fd3b26a 100644 --- a/x-pack/legacy/plugins/monitoring/deprecations.js +++ b/x-pack/legacy/plugins/monitoring/deprecations.js @@ -5,7 +5,7 @@ */ import { get } from 'lodash'; -import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY } from './common/constants'; +import { CLUSTER_ALERTS_ADDRESS_CONFIG_KEY, KIBANA_ALERTING_ENABLED } from './common/constants'; /** * Re-writes deprecated user-defined config settings and logs warnings as a @@ -21,10 +21,20 @@ export const deprecations = () => { const clusterAlertsEnabled = get(settings, 'cluster_alerts.enabled'); const emailNotificationsEnabled = clusterAlertsEnabled && get(settings, 'cluster_alerts.email_notifications.enabled'); - if (emailNotificationsEnabled && !get(settings, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { - log( - `Config key "${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}" will be required for email notifications to work in 7.0."` - ); + if (emailNotificationsEnabled) { + if (KIBANA_ALERTING_ENABLED) { + if (get(settings, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { + log( + `Config key "${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}" is deprecated. Please configure the email adddress through the Stack Monitoring UI instead."` + ); + } + } else { + if (!get(settings, CLUSTER_ALERTS_ADDRESS_CONFIG_KEY)) { + log( + `Config key "${CLUSTER_ALERTS_ADDRESS_CONFIG_KEY}" will be required for email notifications to work in 7.0."` + ); + } + } } }, (settings, log) => { diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js index ca595836133c2..ade172f527dab 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -10,15 +10,20 @@ import { deprecations } from './deprecations'; import { getUiExports } from './ui_exports'; import { Plugin } from './server/plugin'; import { initInfraSource } from './server/lib/logs/init_infra_source'; +import { KIBANA_ALERTING_ENABLED } from './common/constants'; /** * Invokes plugin modules to instantiate the Monitoring plugin for Kibana * @param kibana {Object} Kibana plugin instance * @return {Object} Monitoring UI Kibana plugin object */ +const deps = ['kibana', 'elasticsearch', 'xpack_main']; +if (KIBANA_ALERTING_ENABLED) { + deps.push(...['alerting', 'actions']); +} export const monitoring = kibana => new kibana.Plugin({ - require: ['kibana', 'elasticsearch', 'xpack_main'], + require: deps, id: 'monitoring', configPrefix: 'monitoring', publicDir: resolve(__dirname, 'public'), @@ -59,6 +64,7 @@ export const monitoring = kibana => }), injectUiAppVars: server.injectUiAppVars, log: (...args) => server.log(...args), + logger: server.newPlatform.coreContext.logger, getOSInfo: server.getOSInfo, events: { on: (...args) => server.events.on(...args), @@ -73,11 +79,13 @@ export const monitoring = kibana => xpack_main: server.plugins.xpack_main, elasticsearch: server.plugins.elasticsearch, infra: server.plugins.infra, + alerting: server.plugins.alerting, usageCollection, licensing, }; - new Plugin().setup(serverFacade, plugins); + const plugin = new Plugin(); + plugin.setup(serverFacade, plugins); }, config, deprecations, diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/__snapshots__/status.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/__snapshots__/status.test.tsx.snap new file mode 100644 index 0000000000000..4cf1f4df2eb2e --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/__snapshots__/status.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Status should render a flyout when clicking the link 1`] = ` + + + +

+ Monitoring alerts +

+
+ +

+ Configure an email server and email address to receive alerts. +

+
+
+ + + +
+`; + +exports[`Status should render a success message if all alerts have been migrated and in setup mode 1`] = ` + +

+ + Want to make changes? Click here. + +

+
+`; + +exports[`Status should render without setup mode 1`] = ` + + +

+ + Migrate cluster alerts to our new alerting platform. + +

+
+ +
+`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap new file mode 100644 index 0000000000000..f044e001700c5 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/configuration.test.tsx.snap @@ -0,0 +1,120 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Configuration shallow view should render step 1 1`] = ` + + + Create new email action... + , + "inputDisplay": + Create new email action... + , + "value": "__new__", + }, + ] + } + valueOfSelected="" + /> + +`; + +exports[`Configuration shallow view should render step 2 1`] = ` + + + + + +`; + +exports[`Configuration shallow view should render step 3 1`] = ` + + + Save + + +`; + +exports[`Configuration should render high level steps 1`] = ` +
+ + + + + + + + + +
+`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap new file mode 100644 index 0000000000000..fa03769ea3d09 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step1.test.tsx.snap @@ -0,0 +1,297 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Step1 creating should render a create form 1`] = ` + + + + + +`; + +exports[`Step1 editing should allow for editing 1`] = ` + + +

+ Edit the action below. +

+
+ + +
+`; + +exports[`Step1 should render normally 1`] = ` + + + From: , Service: + , + "inputDisplay": + From: , Service: + , + "value": "1", + }, + Object { + "dropdownDisplay": + Create new email action... + , + "inputDisplay": + Create new email action... + , + "value": "__new__", + }, + ] + } + valueOfSelected="1" + /> + + + + + Edit + + + + + Test + + + + + Delete + + + + +`; + +exports[`Step1 testing should should a tooltip if there is no email address 1`] = ` + + + Test + + +`; + +exports[`Step1 testing should show a failed test error 1`] = ` + + + From: , Service: + , + "inputDisplay": + From: , Service: + , + "value": "1", + }, + Object { + "dropdownDisplay": + Create new email action... + , + "inputDisplay": + Create new email action... + , + "value": "__new__", + }, + ] + } + valueOfSelected="1" + /> + + + + + Edit + + + + + Test + + + + + Delete + + + + + +

+ Very detailed error message +

+
+
+`; + +exports[`Step1 testing should show a successful test 1`] = ` + + + From: , Service: + , + "inputDisplay": + From: , Service: + , + "value": "1", + }, + Object { + "dropdownDisplay": + Create new email action... + , + "inputDisplay": + Create new email action... + , + "value": "__new__", + }, + ] + } + valueOfSelected="1" + /> + + + + + Edit + + + + + Test + + + + + Delete + + + + + +

+ Looks good on our end! +

+
+
+`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step2.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step2.test.tsx.snap new file mode 100644 index 0000000000000..bac183618b491 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step2.test.tsx.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Step2 should render normally 1`] = ` + + + + + +`; + +exports[`Step2 should show form errors 1`] = ` + + + + + +`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step3.test.tsx.snap b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step3.test.tsx.snap new file mode 100644 index 0000000000000..ed15ae9a9cff7 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/__snapshots__/step3.test.tsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Step3 should render normally 1`] = ` + + + Save + + +`; + +exports[`Step3 should show a disabled state 1`] = ` + + + Save + + +`; + +exports[`Step3 should show a saving state 1`] = ` + + + Save + + +`; + +exports[`Step3 should show an error 1`] = ` + + +

+ Test error +

+
+ + + Save + +
+`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx new file mode 100644 index 0000000000000..6b7e2391e0301 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.test.tsx @@ -0,0 +1,147 @@ +/* + * 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 { mockUseEffects } from '../../../jest.helpers'; +import { shallow, ShallowWrapper } from 'enzyme'; +import { kfetch } from 'ui/kfetch'; +import { AlertsConfiguration, AlertsConfigurationProps } from './configuration'; + +jest.mock('ui/kfetch', () => ({ + kfetch: jest.fn(), +})); + +const defaultProps: AlertsConfigurationProps = { + emailAddress: 'test@elastic.co', + onDone: jest.fn(), +}; + +describe('Configuration', () => { + it('should render high level steps', () => { + const component = shallow(); + expect(component.find('EuiSteps').shallow()).toMatchSnapshot(); + }); + + function getStep(component: ShallowWrapper, index: number) { + return component + .find('EuiSteps') + .shallow() + .find('EuiStep') + .at(index) + .children() + .shallow(); + } + + describe('shallow view', () => { + it('should render step 1', () => { + const component = shallow(); + const stepOne = getStep(component, 0); + expect(stepOne).toMatchSnapshot(); + }); + + it('should render step 2', () => { + const component = shallow(); + const stepTwo = getStep(component, 1); + expect(stepTwo).toMatchSnapshot(); + }); + + it('should render step 3', () => { + const component = shallow(); + const stepThree = getStep(component, 2); + expect(stepThree).toMatchSnapshot(); + }); + }); + + describe('selected action', () => { + const actionId = 'a123b'; + let component: ShallowWrapper; + beforeEach(async () => { + mockUseEffects(2); + + (kfetch as jest.Mock).mockImplementation(() => { + return { + data: [ + { + actionTypeId: '.email', + id: actionId, + config: {}, + }, + ], + }; + }); + + component = shallow(); + }); + + it('reflect in Step1', async () => { + const steps = component.find('EuiSteps').dive(); + expect( + steps + .find('EuiStep') + .at(0) + .prop('title') + ).toBe('Select email action'); + expect(steps.find('Step1').prop('selectedEmailActionId')).toBe(actionId); + }); + + it('should enable Step2', async () => { + const steps = component.find('EuiSteps').dive(); + expect(steps.find('Step2').prop('isDisabled')).toBe(false); + }); + + it('should enable Step3', async () => { + const steps = component.find('EuiSteps').dive(); + expect(steps.find('Step3').prop('isDisabled')).toBe(false); + }); + }); + + describe('edit action', () => { + let component: ShallowWrapper; + beforeEach(async () => { + (kfetch as jest.Mock).mockImplementation(() => { + return { + data: [], + }; + }); + + component = shallow(); + }); + + it('disable Step2', async () => { + const steps = component.find('EuiSteps').dive(); + expect(steps.find('Step2').prop('isDisabled')).toBe(true); + }); + + it('disable Step3', async () => { + const steps = component.find('EuiSteps').dive(); + expect(steps.find('Step3').prop('isDisabled')).toBe(true); + }); + }); + + describe('no email address', () => { + let component: ShallowWrapper; + beforeEach(async () => { + (kfetch as jest.Mock).mockImplementation(() => { + return { + data: [ + { + actionTypeId: '.email', + id: 'actionId', + config: {}, + }, + ], + }; + }); + + component = shallow(); + }); + + it('should disable Step3', async () => { + const steps = component.find('EuiSteps').dive(); + expect(steps.find('Step3').prop('isDisabled')).toBe(true); + }); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx new file mode 100644 index 0000000000000..0933cd22db7c9 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/configuration.tsx @@ -0,0 +1,193 @@ +/* + * 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, { ReactNode } from 'react'; +import { kfetch } from 'ui/kfetch'; +import { EuiSteps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ActionResult } from '../../../../../../../plugins/actions/common'; +import { ALERT_ACTION_TYPE_EMAIL } from '../../../../common/constants'; +import { getMissingFieldErrors } from '../../../lib/form_validation'; +import { Step1 } from './step1'; +import { Step2 } from './step2'; +import { Step3 } from './step3'; + +export interface AlertsConfigurationProps { + emailAddress: string; + onDone: Function; +} + +export interface StepResult { + title: string; + children: ReactNode; + status: any; +} + +export interface AlertsConfigurationForm { + email: string | null; +} + +export const NEW_ACTION_ID = '__new__'; + +export const AlertsConfiguration: React.FC = ( + props: AlertsConfigurationProps +) => { + const { onDone } = props; + + const [emailActions, setEmailActions] = React.useState([]); + const [selectedEmailActionId, setSelectedEmailActionId] = React.useState(''); + const [editAction, setEditAction] = React.useState(null); + const [emailAddress, setEmailAddress] = React.useState(props.emailAddress); + const [formErrors, setFormErrors] = React.useState({ email: null }); + const [showFormErrors, setShowFormErrors] = React.useState(false); + const [isSaving, setIsSaving] = React.useState(false); + const [saveError, setSaveError] = React.useState(''); + + React.useEffect(() => { + async function fetchData() { + await fetchEmailActions(); + } + + fetchData(); + }, []); + + React.useEffect(() => { + setFormErrors(getMissingFieldErrors({ email: emailAddress }, { email: '' })); + }, [emailAddress]); + + async function fetchEmailActions() { + const kibanaActions = await kfetch({ + method: 'GET', + pathname: `/api/action/_find`, + }); + + const actions = kibanaActions.data.filter( + (action: ActionResult) => action.actionTypeId === ALERT_ACTION_TYPE_EMAIL + ); + if (actions.length > 0) { + setSelectedEmailActionId(actions[0].id); + } else { + setSelectedEmailActionId(NEW_ACTION_ID); + } + setEmailActions(actions); + } + + async function save() { + if (emailAddress.length === 0) { + setShowFormErrors(true); + return; + } + setIsSaving(true); + setShowFormErrors(false); + + try { + await kfetch({ + method: 'POST', + pathname: `/api/monitoring/v1/alerts`, + body: JSON.stringify({ selectedEmailActionId, emailAddress }), + }); + } catch (err) { + setIsSaving(false); + setSaveError( + err?.body?.message || + i18n.translate('xpack.monitoring.alerts.configuration.unknownError', { + defaultMessage: 'Something went wrong. Please consult the server logs.', + }) + ); + return; + } + + onDone(); + } + + function isStep2Disabled() { + return isStep2AndStep3Disabled(); + } + + function isStep3Disabled() { + return isStep2AndStep3Disabled() || !emailAddress || emailAddress.length === 0; + } + + function isStep2AndStep3Disabled() { + return !!editAction || !selectedEmailActionId || selectedEmailActionId === NEW_ACTION_ID; + } + + function getStep2Status() { + const isDisabled = isStep2AndStep3Disabled(); + + if (isDisabled) { + return 'disabled' as const; + } + + if (emailAddress && emailAddress.length) { + return 'complete' as const; + } + + return 'incomplete' as const; + } + + function getStep1Status() { + if (editAction) { + return 'incomplete' as const; + } + + return selectedEmailActionId ? ('complete' as const) : ('incomplete' as const); + } + + const steps = [ + { + title: emailActions.length + ? i18n.translate('xpack.monitoring.alerts.configuration.selectEmailAction', { + defaultMessage: 'Select email action', + }) + : i18n.translate('xpack.monitoring.alerts.configuration.createEmailAction', { + defaultMessage: 'Create email action', + }), + children: ( + await fetchEmailActions()} + emailActions={emailActions} + selectedEmailActionId={selectedEmailActionId} + setSelectedEmailActionId={setSelectedEmailActionId} + emailAddress={emailAddress} + editAction={editAction} + setEditAction={setEditAction} + /> + ), + status: getStep1Status(), + }, + { + title: i18n.translate('xpack.monitoring.alerts.configuration.setEmailAddress', { + defaultMessage: 'Set the email to receive alerts', + }), + status: getStep2Status(), + children: ( + + ), + }, + { + title: i18n.translate('xpack.monitoring.alerts.configuration.confirm', { + defaultMessage: 'Confirm and save', + }), + status: getStep2Status(), + children: ( + + ), + }, + ]; + + return ( +
+ +
+ ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/index.ts b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/index.ts new file mode 100644 index 0000000000000..7a96c6e324ab3 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { AlertsConfiguration } from './configuration'; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx new file mode 100644 index 0000000000000..650294c29e9a5 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.test.tsx @@ -0,0 +1,338 @@ +/* + * 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 { omit, pick } from 'lodash'; +import '../../../jest.helpers'; +import { shallow } from 'enzyme'; +import { GetStep1Props } from './step1'; +import { EmailActionData } from '../manage_email_action'; +import { ALERT_ACTION_TYPE_EMAIL } from '../../../../common/constants'; + +let Step1: React.FC; +let NEW_ACTION_ID: string; + +function setModules() { + Step1 = require('./step1').Step1; + NEW_ACTION_ID = require('./configuration').NEW_ACTION_ID; +} + +describe('Step1', () => { + const emailActions = [ + { + id: '1', + actionTypeId: '1abc', + name: 'Testing', + config: {}, + }, + ]; + const selectedEmailActionId = emailActions[0].id; + const setSelectedEmailActionId = jest.fn(); + const emailAddress = 'test@test.com'; + const editAction = null; + const setEditAction = jest.fn(); + const onActionDone = jest.fn(); + + const defaultProps: GetStep1Props = { + onActionDone, + emailActions, + selectedEmailActionId, + setSelectedEmailActionId, + emailAddress, + editAction, + setEditAction, + }; + + beforeEach(() => { + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch: () => { + return {}; + }, + })); + setModules(); + }); + }); + + it('should render normally', () => { + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + describe('creating', () => { + it('should render a create form', () => { + const customProps = { + emailActions: [], + selectedEmailActionId: NEW_ACTION_ID, + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + it('should render the select box if at least one action exists', () => { + const customProps = { + emailActions: [ + { + id: 'foo', + actionTypeId: '.email', + name: '', + config: {}, + }, + ], + selectedEmailActionId: NEW_ACTION_ID, + }; + + const component = shallow(); + expect(component.find('EuiSuperSelect').exists()).toBe(true); + }); + + it('should send up the create to the server', async () => { + const kfetch = jest.fn().mockImplementation(() => {}); + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch, + })); + setModules(); + }); + + const customProps = { + emailActions: [], + selectedEmailActionId: NEW_ACTION_ID, + }; + + const component = shallow(); + + const data: EmailActionData = { + service: 'gmail', + host: 'smtp.gmail.com', + port: 465, + secure: true, + from: 'test@test.com', + user: 'user@user.com', + password: 'password', + }; + + const createEmailAction: (data: EmailActionData) => void = component + .find('ManageEmailAction') + .prop('createEmailAction'); + createEmailAction(data); + + expect(kfetch).toHaveBeenCalledWith({ + method: 'POST', + pathname: `/api/action`, + body: JSON.stringify({ + name: 'Email action for Stack Monitoring alerts', + actionTypeId: ALERT_ACTION_TYPE_EMAIL, + config: omit(data, ['user', 'password']), + secrets: pick(data, ['user', 'password']), + }), + }); + }); + }); + + describe('editing', () => { + it('should allow for editing', () => { + const customProps = { + editAction: emailActions[0], + }; + + const component = shallow(); + + expect(component).toMatchSnapshot(); + }); + + it('should send up the edit to the server', async () => { + const kfetch = jest.fn().mockImplementation(() => {}); + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch, + })); + setModules(); + }); + + const customProps = { + editAction: emailActions[0], + }; + + const component = shallow(); + + const data: EmailActionData = { + service: 'gmail', + host: 'smtp.gmail.com', + port: 465, + secure: true, + from: 'test@test.com', + user: 'user@user.com', + password: 'password', + }; + + const createEmailAction: (data: EmailActionData) => void = component + .find('ManageEmailAction') + .prop('createEmailAction'); + createEmailAction(data); + + expect(kfetch).toHaveBeenCalledWith({ + method: 'PUT', + pathname: `/api/action/${emailActions[0].id}`, + body: JSON.stringify({ + name: emailActions[0].name, + config: omit(data, ['user', 'password']), + secrets: pick(data, ['user', 'password']), + }), + }); + }); + }); + + describe('testing', () => { + it('should allow for testing', async () => { + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch: jest.fn().mockImplementation(arg => { + if (arg.pathname === '/api/action/1/_execute') { + return { status: 'ok' }; + } + return {}; + }), + })); + setModules(); + }); + + const component = shallow(); + + expect( + component + .find('EuiButton') + .at(1) + .prop('isLoading') + ).toBe(false); + component + .find('EuiButton') + .at(1) + .simulate('click'); + expect( + component + .find('EuiButton') + .at(1) + .prop('isLoading') + ).toBe(true); + await component.update(); + expect( + component + .find('EuiButton') + .at(1) + .prop('isLoading') + ).toBe(false); + }); + + it('should show a successful test', async () => { + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch: (arg: any) => { + if (arg.pathname === '/api/action/1/_execute') { + return { status: 'ok' }; + } + return {}; + }, + })); + setModules(); + }); + + const component = shallow(); + + component + .find('EuiButton') + .at(1) + .simulate('click'); + await component.update(); + expect(component).toMatchSnapshot(); + }); + + it('should show a failed test error', async () => { + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch: (arg: any) => { + if (arg.pathname === '/api/action/1/_execute') { + return { message: 'Very detailed error message' }; + } + return {}; + }, + })); + setModules(); + }); + + const component = shallow(); + + component + .find('EuiButton') + .at(1) + .simulate('click'); + await component.update(); + expect(component).toMatchSnapshot(); + }); + + it('should not allow testing if there is no email address', () => { + const customProps = { + emailAddress: '', + }; + const component = shallow(); + expect( + component + .find('EuiButton') + .at(1) + .prop('isDisabled') + ).toBe(true); + }); + + it('should should a tooltip if there is no email address', () => { + const customProps = { + emailAddress: '', + }; + const component = shallow(); + expect(component.find('EuiToolTip')).toMatchSnapshot(); + }); + }); + + describe('deleting', () => { + it('should send up the delete to the server', async () => { + const kfetch = jest.fn().mockImplementation(() => {}); + jest.isolateModules(() => { + jest.doMock('ui/kfetch', () => ({ + kfetch, + })); + setModules(); + }); + + const customProps = { + setSelectedEmailActionId: jest.fn(), + onActionDone: jest.fn(), + }; + const component = shallow(); + + await component + .find('EuiButton') + .at(2) + .simulate('click'); + await component.update(); + + expect(kfetch).toHaveBeenCalledWith({ + method: 'DELETE', + pathname: `/api/action/${emailActions[0].id}`, + }); + + expect(customProps.setSelectedEmailActionId).toHaveBeenCalledWith(''); + expect(customProps.onActionDone).toHaveBeenCalled(); + expect( + component + .find('EuiButton') + .at(2) + .prop('isLoading') + ).toBe(false); + }); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.tsx new file mode 100644 index 0000000000000..fc051a68e29f3 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step1.tsx @@ -0,0 +1,334 @@ +/* + * 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, { Fragment } from 'react'; +import { + EuiText, + EuiSpacer, + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiSuperSelect, + EuiToolTip, + EuiCallOut, +} from '@elastic/eui'; +import { kfetch } from 'ui/kfetch'; +import { omit, pick } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { ActionResult } from '../../../../../../../plugins/actions/common'; +import { ManageEmailAction, EmailActionData } from '../manage_email_action'; +import { ALERT_ACTION_TYPE_EMAIL } from '../../../../common/constants'; +import { NEW_ACTION_ID } from './configuration'; + +export interface GetStep1Props { + onActionDone: () => Promise; + emailActions: ActionResult[]; + selectedEmailActionId: string; + setSelectedEmailActionId: (id: string) => void; + emailAddress: string; + editAction: ActionResult | null; + setEditAction: (action: ActionResult | null) => void; +} + +export const Step1: React.FC = (props: GetStep1Props) => { + const [isTesting, setIsTesting] = React.useState(false); + const [isDeleting, setIsDeleting] = React.useState(false); + const [testingStatus, setTestingStatus] = React.useState(null); + const [fullTestingError, setFullTestingError] = React.useState(''); + + async function createEmailAction(data: EmailActionData) { + if (props.editAction) { + await kfetch({ + method: 'PUT', + pathname: `/api/action/${props.editAction.id}`, + body: JSON.stringify({ + name: props.editAction.name, + config: omit(data, ['user', 'password']), + secrets: pick(data, ['user', 'password']), + }), + }); + props.setEditAction(null); + } else { + await kfetch({ + method: 'POST', + pathname: '/api/action', + body: JSON.stringify({ + name: i18n.translate('xpack.monitoring.alerts.configuration.emailAction.name', { + defaultMessage: 'Email action for Stack Monitoring alerts', + }), + actionTypeId: ALERT_ACTION_TYPE_EMAIL, + config: omit(data, ['user', 'password']), + secrets: pick(data, ['user', 'password']), + }), + }); + } + + await props.onActionDone(); + } + + async function deleteEmailAction(id: string) { + setIsDeleting(true); + + await kfetch({ + method: 'DELETE', + pathname: `/api/action/${id}`, + }); + + if (props.editAction && props.editAction.id === id) { + props.setEditAction(null); + } + if (props.selectedEmailActionId === id) { + props.setSelectedEmailActionId(''); + } + await props.onActionDone(); + setIsDeleting(false); + setTestingStatus(null); + } + + async function testEmailAction() { + setIsTesting(true); + setTestingStatus(null); + + const params = { + subject: 'Kibana alerting test configuration', + message: `This is a test for the configured email action for Kibana alerting.`, + to: [props.emailAddress], + }; + + const result = await kfetch({ + method: 'POST', + pathname: `/api/action/${props.selectedEmailActionId}/_execute`, + body: JSON.stringify({ params }), + }); + if (result.status === 'ok') { + setTestingStatus(true); + } else { + setTestingStatus(false); + setFullTestingError(result.message); + } + setIsTesting(false); + } + + function getTestButton() { + const isTestingDisabled = !props.emailAddress || props.emailAddress.length === 0; + const testBtn = ( + + {i18n.translate('xpack.monitoring.alerts.configuration.testConfiguration.buttonText', { + defaultMessage: 'Test', + })} + + ); + + if (isTestingDisabled) { + return ( + + {testBtn} + + ); + } + + return testBtn; + } + + if (props.editAction) { + return ( + + +

+ {i18n.translate('xpack.monitoring.alerts.configuration.step1.editAction', { + defaultMessage: 'Edit the action below.', + })} +

+
+ + await createEmailAction(data)} + cancel={() => props.setEditAction(null)} + isNew={false} + action={props.editAction} + /> +
+ ); + } + + const newAction = ( + + {i18n.translate('xpack.monitoring.alerts.configuration.newActionDropdownDisplay', { + defaultMessage: 'Create new email action...', + })} + + ); + + const options = [ + ...props.emailActions.map(action => { + const actionLabel = i18n.translate( + 'xpack.monitoring.alerts.configuration.selectAction.inputDisplay', + { + defaultMessage: 'From: {from}, Service: {service}', + values: { + service: action.config.service, + from: action.config.from, + }, + } + ); + + return { + value: action.id, + inputDisplay: {actionLabel}, + dropdownDisplay: {actionLabel}, + }; + }), + { + value: NEW_ACTION_ID, + inputDisplay: newAction, + dropdownDisplay: newAction, + }, + ]; + + let selectBox: React.ReactNode | null = ( + props.setSelectedEmailActionId(id)} + hasDividers + /> + ); + let createNew = null; + if (props.selectedEmailActionId === NEW_ACTION_ID) { + createNew = ( + + await createEmailAction(data)} + isNew={true} + /> + + ); + + // If there are no actions, do not show the select box as there are no choices + if (props.emailActions.length === 0) { + selectBox = null; + } else { + // Otherwise, add a spacer + selectBox = ( + + {selectBox} + + + ); + } + } + + let manageConfiguration = null; + const selectedEmailAction = props.emailActions.find( + action => action.id === props.selectedEmailActionId + ); + + if ( + props.selectedEmailActionId !== NEW_ACTION_ID && + props.selectedEmailActionId && + selectedEmailAction + ) { + let testingStatusUi = null; + if (testingStatus === true) { + testingStatusUi = ( + + + +

+ {i18n.translate('xpack.monitoring.alerts.configuration.testConfiguration.success', { + defaultMessage: 'Looks good on our end!', + })} +

+
+
+ ); + } else if (testingStatus === false) { + testingStatusUi = ( + + + +

{fullTestingError}

+
+
+ ); + } + + manageConfiguration = ( + + + + + { + const editAction = + props.emailActions.find(action => action.id === props.selectedEmailActionId) || + null; + props.setEditAction(editAction); + }} + > + {i18n.translate( + 'xpack.monitoring.alerts.configuration.editConfiguration.buttonText', + { + defaultMessage: 'Edit', + } + )} + + + {getTestButton()} + + deleteEmailAction(props.selectedEmailActionId)} + isLoading={isDeleting} + > + {i18n.translate( + 'xpack.monitoring.alerts.configuration.deleteConfiguration.buttonText', + { + defaultMessage: 'Delete', + } + )} + + + + {testingStatusUi} + + ); + } + + return ( + + {selectBox} + {manageConfiguration} + {createNew} + + ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.test.tsx new file mode 100644 index 0000000000000..14e3cb078f9cc --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 '../../../jest.helpers'; +import { shallow } from 'enzyme'; +import { Step2, GetStep2Props } from './step2'; + +describe('Step2', () => { + const defaultProps: GetStep2Props = { + emailAddress: 'test@test.com', + setEmailAddress: jest.fn(), + showFormErrors: false, + formErrors: { email: null }, + isDisabled: false, + }; + + it('should render normally', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should set the email address properly', () => { + const newEmail = 'email@email.com'; + const component = shallow(); + component.find('EuiFieldText').simulate('change', { target: { value: newEmail } }); + expect(defaultProps.setEmailAddress).toHaveBeenCalledWith(newEmail); + }); + + it('should show form errors', () => { + const customProps = { + showFormErrors: true, + formErrors: { + email: 'This is required', + }, + }; + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should disable properly', () => { + const customProps = { + isDisabled: true, + }; + const component = shallow(); + expect(component.find('EuiFieldText').prop('disabled')).toBe(true); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.tsx new file mode 100644 index 0000000000000..974dd8513d231 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step2.tsx @@ -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 React from 'react'; +import { EuiForm, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AlertsConfigurationForm } from './configuration'; + +export interface GetStep2Props { + emailAddress: string; + setEmailAddress: (email: string) => void; + showFormErrors: boolean; + formErrors: AlertsConfigurationForm; + isDisabled: boolean; +} + +export const Step2: React.FC = (props: GetStep2Props) => { + return ( + + + props.setEmailAddress(e.target.value)} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.test.tsx new file mode 100644 index 0000000000000..9b1304c42a507 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.test.tsx @@ -0,0 +1,48 @@ +/* + * 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 '../../../jest.helpers'; +import { shallow } from 'enzyme'; +import { Step3 } from './step3'; + +describe('Step3', () => { + const defaultProps = { + isSaving: false, + isDisabled: false, + save: jest.fn(), + error: null, + }; + + it('should render normally', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should save properly', () => { + const component = shallow(); + component.find('EuiButton').simulate('click'); + expect(defaultProps.save).toHaveBeenCalledWith(); + }); + + it('should show a saving state', () => { + const customProps = { isSaving: true }; + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should show a disabled state', () => { + const customProps = { isDisabled: true }; + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should show an error', () => { + const customProps = { error: 'Test error' }; + const component = shallow(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.tsx new file mode 100644 index 0000000000000..80acb8992cbc1 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/configuration/step3.tsx @@ -0,0 +1,47 @@ +/* + * 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, { Fragment } from 'react'; +import { EuiButton, EuiSpacer, EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export interface GetStep3Props { + isSaving: boolean; + isDisabled: boolean; + save: () => void; + error: string | null; +} + +export const Step3: React.FC = (props: GetStep3Props) => { + let errorUi = null; + if (props.error) { + errorUi = ( + + +

{props.error}

+
+ +
+ ); + } + + return ( + + {errorUi} + + {i18n.translate('xpack.monitoring.alerts.configuration.save', { + defaultMessage: 'Save', + })} + + + ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/manage_email_action.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/manage_email_action.tsx new file mode 100644 index 0000000000000..2bd9804795cb5 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/manage_email_action.tsx @@ -0,0 +1,301 @@ +/* + * 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, { Fragment } from 'react'; +import { + EuiForm, + EuiFormRow, + EuiFieldText, + EuiLink, + EuiSpacer, + EuiFieldNumber, + EuiFieldPassword, + EuiSwitch, + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiSuperSelect, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ActionResult } from '../../../../../../plugins/actions/common'; +import { getMissingFieldErrors, hasErrors, getRequiredFieldError } from '../../lib/form_validation'; +import { ALERT_EMAIL_SERVICES } from '../../../common/constants'; + +export interface EmailActionData { + service: string; + host: string; + port?: number; + secure: boolean; + from: string; + user: string; + password: string; +} + +interface ManageActionModalProps { + createEmailAction: (handler: EmailActionData) => void; + cancel?: () => void; + isNew: boolean; + action?: ActionResult | null; +} + +const DEFAULT_DATA: EmailActionData = { + service: '', + host: '', + port: 0, + secure: false, + from: '', + user: '', + password: '', +}; + +const CREATE_LABEL = i18n.translate('xpack.monitoring.alerts.migrate.manageAction.createLabel', { + defaultMessage: 'Create email action', +}); +const SAVE_LABEL = i18n.translate('xpack.monitoring.alerts.migrate.manageAction.saveLabel', { + defaultMessage: 'Save email action', +}); +const CANCEL_LABEL = i18n.translate('xpack.monitoring.alerts.migrate.manageAction.cancelLabel', { + defaultMessage: 'Cancel', +}); + +const NEW_SERVICE_ID = '__new__'; + +export const ManageEmailAction: React.FC = ( + props: ManageActionModalProps +) => { + const { createEmailAction, cancel, isNew, action } = props; + + const defaultData = Object.assign({}, DEFAULT_DATA, action ? action.config : {}); + const [isSaving, setIsSaving] = React.useState(false); + const [showErrors, setShowErrors] = React.useState(false); + const [errors, setErrors] = React.useState( + getMissingFieldErrors(defaultData, DEFAULT_DATA) + ); + const [data, setData] = React.useState(defaultData); + const [createNewService, setCreateNewService] = React.useState(false); + const [newService, setNewService] = React.useState(''); + + React.useEffect(() => { + const missingFieldErrors = getMissingFieldErrors(data, DEFAULT_DATA); + if (!missingFieldErrors.service) { + if (data.service === NEW_SERVICE_ID && !newService) { + missingFieldErrors.service = getRequiredFieldError('service'); + } + } + setErrors(missingFieldErrors); + }, [data, newService]); + + async function saveEmailAction() { + setShowErrors(true); + if (!hasErrors(errors)) { + setShowErrors(false); + setIsSaving(true); + const mergedData = { + ...data, + service: data.service === NEW_SERVICE_ID ? newService : data.service, + }; + try { + await createEmailAction(mergedData); + } catch (err) { + setErrors({ + general: err.body.message, + }); + } + } + } + + const serviceOptions = ALERT_EMAIL_SERVICES.map(service => ({ + value: service, + inputDisplay: {service}, + dropdownDisplay: {service}, + })); + + serviceOptions.push({ + value: NEW_SERVICE_ID, + inputDisplay: ( + + {i18n.translate('xpack.monitoring.alerts.migrate.manageAction.addingNewServiceText', { + defaultMessage: 'Adding new service...', + })} + + ), + dropdownDisplay: ( + + {i18n.translate('xpack.monitoring.alerts.migrate.manageAction.addNewServiceText', { + defaultMessage: 'Add new service...', + })} + + ), + }); + + let addNewServiceUi = null; + if (createNewService) { + addNewServiceUi = ( + + + setNewService(e.target.value)} + isInvalid={showErrors} + /> + + ); + } + + return ( + + + {i18n.translate('xpack.monitoring.alerts.migrate.manageAction.serviceHelpText', { + defaultMessage: 'Find out more', + })} + + } + error={errors.service} + isInvalid={showErrors && !!errors.service} + > + + { + if (id === NEW_SERVICE_ID) { + setCreateNewService(true); + setData({ ...data, service: NEW_SERVICE_ID }); + } else { + setCreateNewService(false); + setData({ ...data, service: id }); + } + }} + hasDividers + isInvalid={showErrors && !!errors.service} + /> + {addNewServiceUi} + + + + + setData({ ...data, host: e.target.value })} + isInvalid={showErrors && !!errors.host} + /> + + + + setData({ ...data, port: parseInt(e.target.value, 10) })} + isInvalid={showErrors && !!errors.port} + /> + + + + setData({ ...data, secure: e.target.checked })} + /> + + + + setData({ ...data, from: e.target.value })} + isInvalid={showErrors && !!errors.from} + /> + + + + setData({ ...data, user: e.target.value })} + isInvalid={showErrors && !!errors.user} + /> + + + + setData({ ...data, password: e.target.value })} + isInvalid={showErrors && !!errors.password} + /> + + + + + + + + {isNew ? CREATE_LABEL : SAVE_LABEL} + + + {!action || isNew ? null : ( + + {CANCEL_LABEL} + + )} + + + ); +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx new file mode 100644 index 0000000000000..258a5b68db372 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.test.tsx @@ -0,0 +1,81 @@ +/* + * 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 { shallow } from 'enzyme'; +import { kfetch } from 'ui/kfetch'; +import { AlertsStatus, AlertsStatusProps } from './status'; +import { ALERT_TYPE_PREFIX } from '../../../common/constants'; +import { getSetupModeState } from '../../lib/setup_mode'; +import { mockUseEffects } from '../../jest.helpers'; + +jest.mock('../../lib/setup_mode', () => ({ + getSetupModeState: jest.fn(), + addSetupModeCallback: jest.fn(), + toggleSetupMode: jest.fn(), +})); + +jest.mock('ui/kfetch', () => ({ + kfetch: jest.fn(), +})); + +const defaultProps: AlertsStatusProps = { + clusterUuid: '1adsb23', + emailAddress: 'test@elastic.co', +}; + +describe('Status', () => { + beforeEach(() => { + mockUseEffects(2); + + (getSetupModeState as jest.Mock).mockReturnValue({ + enabled: false, + }); + + (kfetch as jest.Mock).mockImplementation(({ pathname }) => { + if (pathname === '/internal/security/api_key/privileges') { + return { areApiKeysEnabled: true }; + } + return { + data: [], + }; + }); + }); + + it('should render without setup mode', () => { + const component = shallow(); + expect(component).toMatchSnapshot(); + }); + + it('should render a flyout when clicking the link', async () => { + (getSetupModeState as jest.Mock).mockReturnValue({ + enabled: true, + }); + + const component = shallow(); + component.find('EuiLink').simulate('click'); + await component.update(); + expect(component.find('EuiFlyout')).toMatchSnapshot(); + }); + + it('should render a success message if all alerts have been migrated and in setup mode', async () => { + (kfetch as jest.Mock).mockReturnValue({ + data: [ + { + alertTypeId: ALERT_TYPE_PREFIX, + }, + ], + }); + + (getSetupModeState as jest.Mock).mockReturnValue({ + enabled: true, + }); + + const component = shallow(); + await component.update(); + expect(component.find('EuiCallOut')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx new file mode 100644 index 0000000000000..0ee0015ed39a7 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/alerts/status.tsx @@ -0,0 +1,203 @@ +/* + * 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, { Fragment } from 'react'; +import { kfetch } from 'ui/kfetch'; +import { + EuiSpacer, + EuiCallOut, + EuiTitle, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiLink, + EuiText, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { Alert } from '../../../../alerting/server/types'; +import { getSetupModeState, addSetupModeCallback, toggleSetupMode } from '../../lib/setup_mode'; +import { NUMBER_OF_MIGRATED_ALERTS, ALERT_TYPE_PREFIX } from '../../../common/constants'; +import { AlertsConfiguration } from './configuration'; + +export interface AlertsStatusProps { + clusterUuid: string; + emailAddress: string; +} + +export const AlertsStatus: React.FC = (props: AlertsStatusProps) => { + const { emailAddress } = props; + + const [setupModeEnabled, setSetupModeEnabled] = React.useState(getSetupModeState().enabled); + const [kibanaAlerts, setKibanaAlerts] = React.useState([]); + const [showMigrationFlyout, setShowMigrationFlyout] = React.useState(false); + const [isSecurityConfigured, setIsSecurityConfigured] = React.useState(false); + + React.useEffect(() => { + async function fetchAlertsStatus() { + const alerts = await kfetch({ method: 'GET', pathname: `/api/alert/_find` }); + const monitoringAlerts = alerts.data.filter((alert: Alert) => + alert.alertTypeId.startsWith(ALERT_TYPE_PREFIX) + ); + setKibanaAlerts(monitoringAlerts); + } + + fetchAlertsStatus(); + fetchSecurityConfigured(); + }, [setupModeEnabled, showMigrationFlyout]); + + React.useEffect(() => { + if (!setupModeEnabled && showMigrationFlyout) { + setShowMigrationFlyout(false); + } + }, [setupModeEnabled, showMigrationFlyout]); + + async function fetchSecurityConfigured() { + const response = await kfetch({ pathname: '/internal/security/api_key/privileges' }); + setIsSecurityConfigured(response.areApiKeysEnabled); + } + + addSetupModeCallback(() => setSetupModeEnabled(getSetupModeState().enabled)); + + function enterSetupModeAndOpenFlyout() { + toggleSetupMode(true); + setShowMigrationFlyout(true); + } + + function getSecurityConfigurationErrorUi() { + if (isSecurityConfigured) { + return null; + } + + const link = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/security-settings.html#api-key-service-settings`; + return ( + + + +

+ + {i18n.translate( + 'xpack.monitoring.alerts.configuration.securityConfigurationError.docsLinkLabel', + { + defaultMessage: 'docs', + } + )} + + ), + }} + /> +

+
+
+ ); + } + + function renderContent() { + let flyout = null; + if (showMigrationFlyout) { + flyout = ( + setShowMigrationFlyout(false)} aria-labelledby="flyoutTitle"> + + +

+ {i18n.translate('xpack.monitoring.alerts.status.flyoutTitle', { + defaultMessage: 'Monitoring alerts', + })} +

+
+ +

+ {i18n.translate('xpack.monitoring.alerts.status.flyoutSubtitle', { + defaultMessage: 'Configure an email server and email address to receive alerts.', + })} +

+
+ {getSecurityConfigurationErrorUi()} +
+ + setShowMigrationFlyout(false)} + /> + +
+ ); + } + + const allMigrated = kibanaAlerts.length === NUMBER_OF_MIGRATED_ALERTS; + if (allMigrated) { + if (setupModeEnabled) { + return ( + + +

+ + {i18n.translate('xpack.monitoring.alerts.status.manage', { + defaultMessage: 'Want to make changes? Click here.', + })} + +

+
+ {flyout} +
+ ); + } + } else { + return ( + + +

+ + {i18n.translate('xpack.monitoring.alerts.status.needToMigrate', { + defaultMessage: 'Migrate cluster alerts to our new alerting platform.', + })} + +

+
+ {flyout} +
+ ); + } + } + + const content = renderContent(); + if (content) { + return ( + + {content} + + + ); + } + + return null; +}; diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index 33b26c7ec56e0..a8001638f4399 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -4,11 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { Fragment } from 'react'; +import moment from 'moment-timezone'; import { FormattedAlert } from 'plugins/monitoring/components/alerts/formatted_alert'; import { mapSeverity } from 'plugins/monitoring/components/alerts/map_severity'; import { formatTimestampToDuration } from '../../../../common/format_timestamp_to_duration'; -import { CALCULATE_DURATION_SINCE } from '../../../../common/constants'; +import { + CALCULATE_DURATION_SINCE, + KIBANA_ALERTING_ENABLED, + ALERT_TYPE_LICENSE_EXPIRATION, + CALCULATE_DURATION_UNTIL, +} from '../../../../common/constants'; import { formatDateTimeLocal } from '../../../../common/formatting'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -21,6 +27,7 @@ import { EuiText, EuiSpacer, EuiCallOut, + EuiLink, } from '@elastic/eui'; export function AlertsPanel({ alerts, changeUrl }) { @@ -82,9 +89,52 @@ export function AlertsPanel({ alerts, changeUrl }) { ); } - const topAlertItems = alerts.map((item, index) => ( - - )); + const alertsList = KIBANA_ALERTING_ENABLED + ? alerts.map((alert, idx) => { + const callOutProps = mapSeverity(alert.severity); + let message = alert.message + // scan message prefix and replace relative times + // \w: Matches any alphanumeric character from the basic Latin alphabet, including the underscore. Equivalent to [A-Za-z0-9_]. + .replace( + '#relative', + formatTimestampToDuration(alert.expirationTime, CALCULATE_DURATION_UNTIL) + ) + .replace('#absolute', moment.tz(alert.expirationTime, moment.tz.guess()).format('LLL z')); + + if (!alert.isFiring) { + callOutProps.title = i18n.translate( + 'xpack.monitoring.cluster.overview.alertsPanel.severityIconTitle', + { + defaultMessage: '{severityIconTitle} (resolved {time} ago)', + values: { + severityIconTitle: callOutProps.title, + time: formatTimestampToDuration(alert.resolvedMS, CALCULATE_DURATION_SINCE), + }, + } + ); + callOutProps.color = 'success'; + callOutProps.iconType = 'check'; + } else { + if (alert.type === ALERT_TYPE_LICENSE_EXPIRATION) { + message = ( + + {message} +   + Please update your license + + ); + } + } + + return ( + +

{message}

+
+ ); + }) + : alerts.map((item, index) => ( + + )); return (
@@ -109,7 +159,7 @@ export function AlertsPanel({ alerts, changeUrl }) { - {topAlertItems} + {alertsList}
); diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js index cad4bbf411c34..eee51c416d11e 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js @@ -10,15 +10,22 @@ import { KibanaPanel } from './kibana_panel'; import { LogstashPanel } from './logstash_panel'; import { AlertsPanel } from './alerts_panel'; import { BeatsPanel } from './beats_panel'; - import { EuiPage, EuiPageBody, EuiScreenReaderOnly } from '@elastic/eui'; import { ApmPanel } from './apm_panel'; -import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertsStatus } from '../../alerts/status'; +import { + STANDALONE_CLUSTER_CLUSTER_UUID, + KIBANA_ALERTING_ENABLED, +} from '../../../../common/constants'; export function Overview(props) { const isFromStandaloneCluster = props.cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID; + const kibanaAlerts = KIBANA_ALERTING_ENABLED ? ( + + ) : null; + return ( @@ -30,6 +37,9 @@ export function Overview(props) { /> + + {kibanaAlerts} + {!isFromStandaloneCluster ? ( diff --git a/x-pack/legacy/plugins/monitoring/public/jest.helpers.ts b/x-pack/legacy/plugins/monitoring/public/jest.helpers.ts new file mode 100644 index 0000000000000..46ba603d30138 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/jest.helpers.ts @@ -0,0 +1,36 @@ +/* + * 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'; + +/** + * Suppress React 16.8 act() warnings globally. + * The react teams fix won't be out of alpha until 16.9.0. + * https://github.com/facebook/react/issues/14769#issuecomment-514589856 + */ +const consoleError = console.error; // eslint-disable-line no-console +beforeAll(() => { + jest.spyOn(console, 'error').mockImplementation((...args) => { + if (!args[0].includes('Warning: An update to %s inside a test was not wrapped in act')) { + consoleError(...args); + } + }); +}); + +export function mockUseEffects(count = 1) { + const spy = jest.spyOn(React, 'useEffect'); + for (let i = 0; i < count; i++) { + spy.mockImplementationOnce(f => f()); + } +} + +// export function mockUseEffectForDeps(deps, count = 1) { +// const spy = jest.spyOn(React, 'useEffect'); +// for (let i = 0; i < count; i++) { +// spy.mockImplementationOnce((f, depList) => { + +// }); +// } +// } diff --git a/x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.js b/x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.tsx similarity index 94% rename from x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.js rename to x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.tsx index 9a51a88596926..22ce32103c208 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/ajax_error_handler.tsx @@ -7,12 +7,13 @@ import React from 'react'; import { contains } from 'lodash'; import { toastNotifications } from 'ui/notify'; +// @ts-ignore import { formatMsg } from 'ui/notify/lib'; import { EuiButton, EuiSpacer, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { toMountPoint } from '../../../../../../src/plugins/kibana_react/public'; -export function formatMonitoringError(err) { +export function formatMonitoringError(err: any) { // TODO: We should stop using Boom for errors and instead write a custom handler to return richer error objects // then we can do better messages, such as highlighting the Cluster UUID instead of requiring it be part of the message if (err.status && err.status !== -1 && err.data) { @@ -33,10 +34,10 @@ export function formatMonitoringError(err) { return formatMsg(err); } -export function ajaxErrorHandlersProvider($injector) { +export function ajaxErrorHandlersProvider($injector: any) { const kbnUrl = $injector.get('kbnUrl'); - return err => { + return (err: any) => { if (err.status === 403) { // redirect to error message view kbnUrl.redirect('access-denied'); diff --git a/x-pack/legacy/plugins/monitoring/public/lib/form_validation.ts b/x-pack/legacy/plugins/monitoring/public/lib/form_validation.ts new file mode 100644 index 0000000000000..98d56f9790be4 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/lib/form_validation.ts @@ -0,0 +1,48 @@ +/* + * 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 { isString, isNumber, capitalize } from 'lodash'; + +export function getRequiredFieldError(field: string): string { + return i18n.translate('xpack.monitoring.alerts.migrate.manageAction.requiredFieldError', { + defaultMessage: '{field} is a required field.', + values: { + field: capitalize(field), + }, + }); +} + +export function getMissingFieldErrors(data: any, defaultData: any) { + const errors: any = {}; + + for (const key in data) { + if (!data.hasOwnProperty(key)) { + continue; + } + + if (isString(defaultData[key])) { + if (!data[key] || data[key].length === 0) { + errors[key] = getRequiredFieldError(key); + } + } else if (isNumber(defaultData[key])) { + if (isNaN(data[key]) || data[key] === 0) { + errors[key] = getRequiredFieldError(key); + } + } + } + + return errors; +} + +export function hasErrors(errors: any) { + for (const error in errors) { + if (error.length) { + return true; + } + } + return false; +} diff --git a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js index aa931368b34c2..4a2b470f04c72 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.test.js @@ -90,7 +90,7 @@ describe('setup_mode', () => { } catch (err) { error = err; } - expect(error).toEqual( + expect(error.message).toEqual( 'Unable to interact with setup ' + 'mode because the angular injector was not previously set. This needs to be ' + 'set by calling `initSetupModeState`.' @@ -255,9 +255,9 @@ describe('setup_mode', () => { await toggleSetupMode(true); injectorModulesMock.$http.post.mockClear(); await updateSetupModeData(undefined, true); - expect( - injectorModulesMock.$http.post - ).toHaveBeenCalledWith('../api/monitoring/v1/setup/collection/cluster', { ccs: undefined }); + const url = '../api/monitoring/v1/setup/collection/cluster'; + const args = { ccs: undefined }; + expect(injectorModulesMock.$http.post).toHaveBeenCalledWith(url, args); }); }); }); diff --git a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.js b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx similarity index 76% rename from x-pack/legacy/plugins/monitoring/public/lib/setup_mode.js rename to x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx index 41aae01307617..d805c10247b2e 100644 --- a/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.js +++ b/x-pack/legacy/plugins/monitoring/public/lib/setup_mode.tsx @@ -6,31 +6,49 @@ import React from 'react'; import { render } from 'react-dom'; -import { ajaxErrorHandlersProvider } from './ajax_error_handler'; import { get, contains } from 'lodash'; import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; -import { SetupModeEnterButton } from '../components/setup_mode/enter_button'; import { npSetup } from 'ui/new_platform'; +import { PluginsSetup } from 'ui/new_platform/new_platform'; +import { CloudSetup } from '../../../../../plugins/cloud/public'; +import { ajaxErrorHandlersProvider } from './ajax_error_handler'; +import { SetupModeEnterButton } from '../components/setup_mode/enter_button'; + +interface PluginsSetupWithCloud extends PluginsSetup { + cloud: CloudSetup; +} -function isOnPage(hash) { +function isOnPage(hash: string) { return contains(window.location.hash, hash); } -const angularState = { +interface IAngularState { + injector: any; + scope: any; +} + +const angularState: IAngularState = { injector: null, scope: null, }; const checkAngularState = () => { if (!angularState.injector || !angularState.scope) { - throw 'Unable to interact with setup mode because the angular injector was not previously set.' + - ' This needs to be set by calling `initSetupModeState`.'; + throw new Error( + 'Unable to interact with setup mode because the angular injector was not previously set.' + + ' This needs to be set by calling `initSetupModeState`.' + ); } }; -const setupModeState = { +interface ISetupModeState { + enabled: boolean; + data: any; + callbacks: Function[]; +} +const setupModeState: ISetupModeState = { enabled: false, data: null, callbacks: [], @@ -38,7 +56,7 @@ const setupModeState = { export const getSetupModeState = () => setupModeState; -export const setNewlyDiscoveredClusterUuid = clusterUuid => { +export const setNewlyDiscoveredClusterUuid = (clusterUuid: string) => { const globalState = angularState.injector.get('globalState'); const executor = angularState.injector.get('$executor'); angularState.scope.$apply(() => { @@ -48,7 +66,7 @@ export const setNewlyDiscoveredClusterUuid = clusterUuid => { executor.run(); }; -export const fetchCollectionData = async (uuid, fetchWithoutClusterUuid = false) => { +export const fetchCollectionData = async (uuid?: string, fetchWithoutClusterUuid = false) => { checkAngularState(); const http = angularState.injector.get('$http'); @@ -75,19 +93,19 @@ export const fetchCollectionData = async (uuid, fetchWithoutClusterUuid = false) } }; -const notifySetupModeDataChange = oldData => { - setupModeState.callbacks.forEach(cb => cb(oldData)); +const notifySetupModeDataChange = (oldData?: any) => { + setupModeState.callbacks.forEach((cb: Function) => cb(oldData)); }; -export const updateSetupModeData = async (uuid, fetchWithoutClusterUuid = false) => { +export const updateSetupModeData = async (uuid?: string, fetchWithoutClusterUuid = false) => { const oldData = setupModeState.data; const data = await fetchCollectionData(uuid, fetchWithoutClusterUuid); setupModeState.data = data; - const { cloud } = npSetup.plugins; + const { cloud } = npSetup.plugins as PluginsSetupWithCloud; const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); const hasPermissions = get(data, '_meta.hasPermissions', false); if (isCloudEnabled || !hasPermissions) { - let text = null; + let text: string = ''; if (!hasPermissions) { text = i18n.translate('xpack.monitoring.setupMode.notAvailablePermissions', { defaultMessage: 'You do not have the necessary permissions to do this.', @@ -113,9 +131,9 @@ export const updateSetupModeData = async (uuid, fetchWithoutClusterUuid = false) const globalState = angularState.injector.get('globalState'); const clusterUuid = globalState.cluster_uuid; if (!clusterUuid) { - const liveClusterUuid = get(data, '_meta.liveClusterUuid'); + const liveClusterUuid: string = get(data, '_meta.liveClusterUuid'); const migratedEsNodes = Object.values(get(data, 'elasticsearch.byUuid', {})).filter( - node => node.isPartiallyMigrated || node.isFullyMigrated + (node: any) => node.isPartiallyMigrated || node.isFullyMigrated ); if (liveClusterUuid && migratedEsNodes.length > 0) { setNewlyDiscoveredClusterUuid(liveClusterUuid); @@ -140,7 +158,7 @@ export const disableElasticsearchInternalCollection = async () => { } }; -export const toggleSetupMode = inSetupMode => { +export const toggleSetupMode = (inSetupMode: boolean) => { checkAngularState(); const globalState = angularState.injector.get('globalState'); @@ -164,7 +182,7 @@ export const setSetupModeMenuItem = () => { } const globalState = angularState.injector.get('globalState'); - const { cloud } = npSetup.plugins; + const { cloud } = npSetup.plugins as PluginsSetupWithCloud; const isCloudEnabled = !!(cloud && cloud.isCloudEnabled); const enabled = !globalState.inSetupMode && !isCloudEnabled; @@ -174,10 +192,14 @@ export const setSetupModeMenuItem = () => { ); }; -export const initSetupModeState = async ($scope, $injector, callback) => { +export const addSetupModeCallback = (callback: Function) => setupModeState.callbacks.push(callback); + +export const initSetupModeState = async ($scope: any, $injector: any, callback?: Function) => { angularState.scope = $scope; angularState.injector = $injector; - callback && setupModeState.callbacks.push(callback); + if (callback) { + setupModeState.callbacks.push(callback); + } const globalState = $injector.get('globalState'); if (globalState.inSetupMode) { diff --git a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js index 57a7850b6fd53..1bfc76b766457 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/alerts/index.js @@ -24,7 +24,7 @@ function getPageData($injector) { const globalState = $injector.get('globalState'); const $http = $injector.get('$http'); const Private = $injector.get('Private'); - const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/alerts`; + const url = `../api/monitoring/v1/clusters/${globalState.cluster_uuid}/legacy_alerts`; const timeBounds = timefilter.getBounds(); diff --git a/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js b/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js index bec90f3230571..e7107860d61fa 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/cluster/overview/index.js @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Fragment } from 'react'; +import { isEmpty } from 'lodash'; +import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import uiRoutes from 'ui/routes'; import { routeInitProvider } from 'plugins/monitoring/lib/route_init'; @@ -12,7 +14,11 @@ import { MonitoringViewBaseController } from '../../'; import { Overview } from 'plugins/monitoring/components/cluster/overview'; import { I18nContext } from 'ui/i18n'; import { SetupModeRenderer } from '../../../components/renderers'; -import { CODE_PATH_ALL } from '../../../../common/constants'; +import { + CODE_PATH_ALL, + MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, + KIBANA_ALERTING_ENABLED, +} from '../../../../common/constants'; const CODE_PATHS = [CODE_PATH_ALL]; @@ -31,6 +37,7 @@ uiRoutes.when('/overview', { const monitoringClusters = $injector.get('monitoringClusters'); const globalState = $injector.get('globalState'); const showLicenseExpiration = $injector.get('showLicenseExpiration'); + const config = $injector.get('config'); super({ title: i18n.translate('xpack.monitoring.cluster.overviewTitle', { @@ -58,7 +65,16 @@ uiRoutes.when('/overview', { $scope.$watch( () => this.data, - data => { + async data => { + if (isEmpty(data)) { + return; + } + + let emailAddress = chrome.getInjected('monitoringLegacyEmailAddress') || ''; + if (KIBANA_ALERTING_ENABLED) { + emailAddress = config.get(MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS) || emailAddress; + } + this.renderReact( new Promise(resolve => resolve()), + alertInstanceFactory: (id: string) => new AlertInstance(), + savedObjectsClient: {} as jest.Mocked, + }, + params: {}, + state: {}, + spaceId: '', + name: '', + tags: [], + createdBy: null, + updatedBy: null, +}; + +describe('getLicenseExpiration', () => { + const emailAddress = 'foo@foo.com'; + const server: any = { + newPlatform: { + __internals: { + uiSettings: { + asScopedToClient: (): any => ({ + get: () => new Promise(resolve => resolve(emailAddress)), + }), + }, + }, + }, + }; + const getMonitoringCluster: () => void = jest.fn(); + const logger: Logger = { + warn: jest.fn(), + log: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + error: jest.fn(), + fatal: jest.fn(), + info: jest.fn(), + get: jest.fn(), + }; + const getLogger = (): Logger => logger; + const ccrEnabled = false; + + afterEach(() => { + (logger.warn as jest.Mock).mockClear(); + }); + + it('should have the right id and actionGroups', () => { + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + expect(alert.id).toBe(ALERT_TYPE_LICENSE_EXPIRATION); + expect(alert.actionGroups).toEqual(['default']); + }); + + it('should return the state if no license is provided', async () => { + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + + const services: MockServices | AlertServices = { + callCluster: jest.fn(), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + const state = { foo: 1 }; + + const result = await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + }); + + expect(result).toEqual(state); + }); + + it('should log a warning if no email is provided', async () => { + const customServer: any = { + newPlatform: { + __internals: { + uiSettings: { + asScopedToClient: () => ({ + get: () => null, + }), + }, + }, + }, + }; + const alert = getLicenseExpiration(customServer, getMonitoringCluster, getLogger, ccrEnabled); + + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense({ + status: 'good', + type: 'basic', + expiry_date_in_millis: moment() + .add(7, 'days') + .valueOf(), + }) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory: jest.fn(), + savedObjectsClient: savedObjectsClientMock.create(), + }; + + const state = {}; + + await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + }); + + expect((logger.warn as jest.Mock).mock.calls.length).toBe(1); + expect(logger.warn).toHaveBeenCalledWith( + `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` + ); + }); + + it('should fire actions if going to expire', async () => { + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'gold', + expiry_date_in_millis: moment() + .add(7, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, + }; + + const state = {}; + + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + + expect(newState.expiredCheckDateMS > 0).toBe(true); + expect(scheduleActions.mock.calls.length).toBe(1); + expect(scheduleActions.mock.calls[0][1].subject).toBe( + 'NEW X-Pack Monitoring: License Expiration' + ); + expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); + }); + + it('should fire actions if the user fixed their license', async () => { + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'gold', + expiry_date_in_millis: moment() + .add(120, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, + }; + + const state: AlertState = { + [clusterUuid]: { + expiredCheckDateMS: moment() + .subtract(1, 'day') + .valueOf(), + ui: { isFiring: true, severity: 0, message: null, resolvedMS: 0, expirationTime: 0 }, + }, + }; + + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + expect(newState.expiredCheckDateMS).toBe(0); + expect(scheduleActions.mock.calls.length).toBe(1); + expect(scheduleActions.mock.calls[0][1].subject).toBe( + 'RESOLVED X-Pack Monitoring: License Expiration' + ); + expect(scheduleActions.mock.calls[0][1].to).toBe(emailAddress); + }); + + it('should not fire actions for trial license that expire in more than 14 days', async () => { + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'trial', + expiry_date_in_millis: moment() + .add(15, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, + }; + + const state = {}; + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + expect(newState.expiredCheckDateMS).toBe(undefined); + expect(scheduleActions).not.toHaveBeenCalled(); + }); + + it('should fire actions for trial license that in 14 days or less', async () => { + const scheduleActions = jest.fn(); + const alertInstanceFactory = jest.fn( + (id: string): AlertInstance => { + const instance = new AlertInstance(); + instance.scheduleActions = scheduleActions; + return instance; + } + ); + const alert = getLicenseExpiration(server, getMonitoringCluster, getLogger, ccrEnabled); + + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.get.mockReturnValue( + new Promise(resolve => { + const savedObject: SavedObject = { + id: '', + type: '', + references: [], + attributes: { + [MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS]: emailAddress, + }, + }; + resolve(savedObject); + }) + ); + const services = { + callCluster: jest.fn( + (method: string, { filterPath }): Promise => { + return new Promise(resolve => { + if (filterPath.includes('hits.hits._source.license.*')) { + resolve( + fillLicense( + { + status: 'active', + type: 'trial', + expiry_date_in_millis: moment() + .add(13, 'days') + .valueOf(), + }, + clusterUuid + ) + ); + } + resolve({}); + }); + } + ), + alertInstanceFactory, + savedObjectsClient, + }; + + const state = {}; + const result: AlertState = (await alert.executor({ + ...alertExecutorOptions, + services, + params, + state, + })) as AlertState; + + const newState: AlertClusterState = result[clusterUuid] as AlertClusterState; + expect(newState.expiredCheckDateMS > 0).toBe(true); + expect(scheduleActions.mock.calls.length).toBe(1); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/alerts/license_expiration.ts b/x-pack/legacy/plugins/monitoring/server/alerts/license_expiration.ts new file mode 100644 index 0000000000000..197c5c9cdcbc7 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/alerts/license_expiration.ts @@ -0,0 +1,162 @@ +/* + * 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 moment from 'moment-timezone'; +import { get } from 'lodash'; +import { Legacy } from 'kibana'; +import { Logger } from 'src/core/server'; +import { ALERT_TYPE_LICENSE_EXPIRATION, INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; +import { AlertType } from '../../../alerting'; +import { fetchLicenses } from '../lib/alerts/fetch_licenses'; +import { fetchDefaultEmailAddress } from '../lib/alerts/fetch_default_email_address'; +import { fetchClusters } from '../lib/alerts/fetch_clusters'; +import { fetchAvailableCcs } from '../lib/alerts/fetch_available_ccs'; +import { + AlertLicense, + AlertState, + AlertClusterState, + AlertClusterUiState, + LicenseExpirationAlertExecutorOptions, +} from './types'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { executeActions, getUiMessage } from '../lib/alerts/license_expiration.lib'; + +const EXPIRES_DAYS = [60, 30, 14, 7]; + +export const getLicenseExpiration = ( + server: Legacy.Server, + getMonitoringCluster: any, + getLogger: (contexts: string[]) => Logger, + ccsEnabled: boolean +): AlertType => { + async function getCallCluster(services: any): Promise { + const monitoringCluster = await getMonitoringCluster(); + if (!monitoringCluster) { + return services.callCluster; + } + + return monitoringCluster.callCluster; + } + + const logger = getLogger([ALERT_TYPE_LICENSE_EXPIRATION]); + return { + id: ALERT_TYPE_LICENSE_EXPIRATION, + name: 'Monitoring Alert - License Expiration', + actionGroups: ['default'], + async executor({ + services, + params, + state, + }: LicenseExpirationAlertExecutorOptions): Promise { + logger.debug( + `Firing alert with params: ${JSON.stringify(params)} and state: ${JSON.stringify(state)}` + ); + + const callCluster = await getCallCluster(services); + + // Support CCS use cases by querying to find available remote clusters + // and then adding those to the index pattern we are searching against + let esIndexPattern = INDEX_PATTERN_ELASTICSEARCH; + if (ccsEnabled) { + const availableCcs = await fetchAvailableCcs(callCluster); + if (availableCcs.length > 0) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + } + + const clusters = await fetchClusters(callCluster, esIndexPattern); + + // Fetch licensing information from cluster_stats documents + const licenses: AlertLicense[] = await fetchLicenses(callCluster, clusters, esIndexPattern); + if (licenses.length === 0) { + logger.warn(`No license found for ${ALERT_TYPE_LICENSE_EXPIRATION}.`); + return state; + } + + const uiSettings = server.newPlatform.__internals.uiSettings.asScopedToClient( + services.savedObjectsClient + ); + const dateFormat: string = await uiSettings.get('dateFormat'); + const timezone: string = await uiSettings.get('dateFormat:tz'); + const emailAddress = await fetchDefaultEmailAddress(uiSettings); + if (!emailAddress) { + // TODO: we can do more here + logger.warn( + `Unable to send email for ${ALERT_TYPE_LICENSE_EXPIRATION} because there is no email configured.` + ); + return; + } + + const result: AlertState = { ...state }; + + for (const license of licenses) { + const licenseState: AlertClusterState = state[license.clusterUuid] || {}; + const $expiry = moment.utc(license.expiryDateMS); + let isExpired = false; + let severity = 0; + + if (license.status !== 'active') { + isExpired = true; + severity = 2001; + } else if (license.expiryDateMS) { + for (let i = EXPIRES_DAYS.length - 1; i >= 0; i--) { + if (license.type === 'trial' && i < 2) { + break; + } + + const $fromNow = moment.utc().add(EXPIRES_DAYS[i], 'days'); + if ($fromNow.isAfter($expiry)) { + isExpired = true; + severity = 1000 * i; + break; + } + } + } + + const ui: AlertClusterUiState = get(licenseState, 'ui', { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + expirationTime: 0, + }); + let resolved = ui.resolvedMS; + let message = ui.message; + let expiredCheckDate = licenseState.expiredCheckDateMS; + const instance = services.alertInstanceFactory(ALERT_TYPE_LICENSE_EXPIRATION); + + if (isExpired) { + if (!licenseState.expiredCheckDateMS) { + logger.debug(`License will expire soon, sending email`); + executeActions(instance, license, $expiry, dateFormat, emailAddress); + expiredCheckDate = moment().valueOf(); + } + message = getUiMessage(license, timezone); + resolved = 0; + } else if (!isExpired && licenseState.expiredCheckDateMS) { + logger.debug(`License expiration has been resolved, sending email`); + executeActions(instance, license, $expiry, dateFormat, emailAddress, true); + expiredCheckDate = 0; + message = getUiMessage(license, timezone, true); + resolved = moment().valueOf(); + } + + result[license.clusterUuid] = { + expiredCheckDateMS: expiredCheckDate, + ui: { + message, + expirationTime: license.expiryDateMS, + isFiring: expiredCheckDate > 0, + severity, + resolvedMS: resolved, + }, + }; + } + + return result; + }, + }; +}; diff --git a/x-pack/legacy/plugins/monitoring/server/alerts/types.d.ts b/x-pack/legacy/plugins/monitoring/server/alerts/types.d.ts new file mode 100644 index 0000000000000..6346ca00dabbd --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/alerts/types.d.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Moment } from 'moment'; +import { AlertExecutorOptions } from '../../../alerting'; + +export interface AlertLicense { + status: string; + type: string; + expiryDateMS: number; + clusterUuid: string; + clusterName: string; +} + +export interface AlertState { + [clusterUuid: string]: AlertClusterState; +} + +export interface AlertClusterState { + expiredCheckDateMS: number | Moment; + ui: AlertClusterUiState; +} + +export interface AlertClusterUiState { + isFiring: boolean; + severity: number; + message: string | null; + resolvedMS: number; + expirationTime: number; +} + +export interface AlertCluster { + clusterUuid: string; +} + +export interface LicenseExpirationAlertExecutorOptions extends AlertExecutorOptions { + state: AlertState; +} + +export interface AlertParams { + dateFormat: string; + timezone: string; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts new file mode 100644 index 0000000000000..4398b2dd675ec --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.test.ts @@ -0,0 +1,36 @@ +/* + * 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 { fetchAvailableCcs } from './fetch_available_ccs'; + +describe('fetchAvailableCcs', () => { + it('should call the `cluster.remoteInfo` api', async () => { + const callCluster = jest.fn(); + await fetchAvailableCcs(callCluster); + expect(callCluster).toHaveBeenCalledWith('cluster.remoteInfo'); + }); + + it('should return clusters that are connected', async () => { + const connectedRemote = 'myRemote'; + const callCluster = jest.fn().mockImplementation(() => ({ + [connectedRemote]: { + connected: true, + }, + })); + const result = await fetchAvailableCcs(callCluster); + expect(result).toEqual([connectedRemote]); + }); + + it('should not return clusters that are connected', async () => { + const disconnectedRemote = 'myRemote'; + const callCluster = jest.fn().mockImplementation(() => ({ + [disconnectedRemote]: { + connected: false, + }, + })); + const result = await fetchAvailableCcs(callCluster); + expect(result.length).toBe(0); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.ts new file mode 100644 index 0000000000000..34efaff93f34c --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_available_ccs.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export async function fetchAvailableCcs(callCluster: any): Promise { + const availableCcs = []; + const response = await callCluster('cluster.remoteInfo'); + for (const remoteName in response) { + if (!response.hasOwnProperty(remoteName)) { + continue; + } + const remoteInfo = response[remoteName]; + if (remoteInfo.connected) { + availableCcs.push(remoteName); + } + } + return availableCcs; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts new file mode 100644 index 0000000000000..78eb9773df15f --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.test.ts @@ -0,0 +1,33 @@ +/* + * 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 { fetchClusters } from './fetch_clusters'; + +describe('fetchClusters', () => { + it('return a list of clusters', async () => { + const callCluster = jest.fn().mockImplementation(() => ({ + aggregations: { + clusters: { + buckets: [ + { + key: 'clusterA', + }, + ], + }, + }, + })); + const index = '.monitoring-es-*'; + const result = await fetchClusters(callCluster, index); + expect(result).toEqual([{ clusterUuid: 'clusterA' }]); + }); + + it('should limit the time period in the query', async () => { + const callCluster = jest.fn(); + const index = '.monitoring-es-*'; + await fetchClusters(callCluster, index); + const params = callCluster.mock.calls[0][1]; + expect(params.body.query.bool.filter[1].range.timestamp.gte).toBe('now-2m'); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.ts new file mode 100644 index 0000000000000..8ef7339618a2c --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_clusters.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { get } from 'lodash'; +import { AlertCluster } from '../../alerts/types'; + +interface AggregationResult { + key: string; +} + +export async function fetchClusters(callCluster: any, index: string): Promise { + const params = { + index, + filterPath: 'aggregations.clusters.buckets', + body: { + size: 0, + query: { + bool: { + filter: [ + { + term: { + type: 'cluster_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-2m', + }, + }, + }, + ], + }, + }, + aggs: { + clusters: { + terms: { + field: 'cluster_uuid', + size: 1000, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + return get(response, 'aggregations.clusters.buckets', []).map((bucket: AggregationResult) => ({ + clusterUuid: bucket.key, + })); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.test.ts new file mode 100644 index 0000000000000..25b09b956038a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.test.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { fetchDefaultEmailAddress } from './fetch_default_email_address'; +import { uiSettingsServiceMock } from '../../../../../../../src/core/server/mocks'; + +describe('fetchDefaultEmailAddress', () => { + it('get the email address', async () => { + const email = 'test@test.com'; + const uiSettingsClient = uiSettingsServiceMock.createClient(); + uiSettingsClient.get.mockResolvedValue(email); + const result = await fetchDefaultEmailAddress(uiSettingsClient); + expect(result).toBe(email); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.ts new file mode 100644 index 0000000000000..88e4199a88256 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_default_email_address.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { IUiSettingsClient } from 'src/core/server'; +import { MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS } from '../../../common/constants'; + +export async function fetchDefaultEmailAddress( + uiSettingsClient: IUiSettingsClient +): Promise { + return await uiSettingsClient.get(MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts new file mode 100644 index 0000000000000..dd6c074e68b1f --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { fetchLicenses } from './fetch_licenses'; + +describe('fetchLicenses', () => { + it('return a list of licenses', async () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + license, + cluster_name: clusterName, + cluster_uuid: clusterUuid, + }, + }, + ], + }, + })); + const clusters = [{ clusterUuid }]; + const index = '.monitoring-es-*'; + const result = await fetchLicenses(callCluster, clusters, index); + expect(result).toEqual([ + { + status: license.status, + type: license.type, + expiryDateMS: license.expiry_date_in_millis, + clusterUuid, + clusterName, + }, + ]); + }); + + it('should only search for the clusters provided', async () => { + const clusterUuid = 'clusterA'; + const callCluster = jest.fn(); + const clusters = [{ clusterUuid }]; + const index = '.monitoring-es-*'; + await fetchLicenses(callCluster, clusters, index); + const params = callCluster.mock.calls[0][1]; + expect(params.body.query.bool.filter[0].terms.cluster_uuid).toEqual([clusterUuid]); + }); + + it('should limit the time period in the query', async () => { + const clusterUuid = 'clusterA'; + const callCluster = jest.fn(); + const clusters = [{ clusterUuid }]; + const index = '.monitoring-es-*'; + await fetchLicenses(callCluster, clusters, index); + const params = callCluster.mock.calls[0][1]; + expect(params.body.query.bool.filter[2].range.timestamp.gte).toBe('now-2m'); + }); + + it('should give priority to the metadata name', async () => { + const clusterName = 'MyCluster'; + const clusterUuid = 'clusterA'; + const license = { + status: 'active', + expiry_date_in_millis: 1579532493876, + type: 'basic', + }; + const callCluster = jest.fn().mockImplementation(() => ({ + hits: { + hits: [ + { + _source: { + license, + cluster_name: 'fakeName', + cluster_uuid: clusterUuid, + cluster_settings: { + cluster: { + metadata: { + display_name: clusterName, + }, + }, + }, + }, + }, + ], + }, + })); + const clusters = [{ clusterUuid }]; + const index = '.monitoring-es-*'; + const result = await fetchLicenses(callCluster, clusters, index); + expect(result).toEqual([ + { + status: license.status, + type: license.type, + expiryDateMS: license.expiry_date_in_millis, + clusterUuid, + clusterName, + }, + ]); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.ts new file mode 100644 index 0000000000000..31a68e8aa9c3e --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_licenses.ts @@ -0,0 +1,67 @@ +/* + * 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 { AlertLicense, AlertCluster } from '../../alerts/types'; + +export async function fetchLicenses( + callCluster: any, + clusters: AlertCluster[], + index: string +): Promise { + const params = { + index, + filterPath: [ + 'hits.hits._source.license.*', + 'hits.hits._source.cluster_settings.cluster.metadata.display_name', + 'hits.hits._source.cluster_uuid', + 'hits.hits._source.cluster_name', + ], + body: { + size: 1, + sort: [{ timestamp: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + terms: { + cluster_uuid: clusters.map(cluster => cluster.clusterUuid), + }, + }, + { + term: { + type: 'cluster_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-2m', + }, + }, + }, + ], + }, + }, + }, + }; + + const response = await callCluster('search', params); + return get(response, 'hits.hits', []).map((hit: any) => { + const clusterName: string = + get(hit, '_source.cluster_settings.cluster.metadata.display_name') || + get(hit, '_source.cluster_name') || + get(hit, '_source.cluster_uuid'); + const rawLicense: any = get(hit, '_source.license', {}); + const license: AlertLicense = { + status: rawLicense.status, + type: rawLicense.type, + expiryDateMS: rawLicense.expiry_date_in_millis, + clusterUuid: get(hit, '_source.cluster_uuid'), + clusterName, + }; + return license; + }); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_status.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_status.ts new file mode 100644 index 0000000000000..9f7c1d5a994d2 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/fetch_status.ts @@ -0,0 +1,87 @@ +/* + * 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 moment from 'moment'; +import { get } from 'lodash'; +import { AlertClusterState } from '../../alerts/types'; +import { ALERT_TYPES, LOGGING_TAG } from '../../../common/constants'; + +export async function fetchStatus( + callCluster: any, + start: number, + end: number, + clusterUuid: string, + server: any +): Promise { + // TODO: this shouldn't query task manager directly but rather + // use an api exposed by the alerting/actions plugin + // See https://github.com/elastic/kibana/issues/48442 + const statuses = await Promise.all( + ALERT_TYPES.map( + type => + new Promise(async (resolve, reject) => { + try { + const params = { + index: '.kibana_task_manager', + filterPath: ['hits.hits._source.task.state'], + body: { + size: 1, + sort: [{ updated_at: { order: 'desc' } }], + query: { + bool: { + filter: [ + { + term: { + 'task.taskType': `alerting:${type}`, + }, + }, + ], + }, + }, + }, + }; + + const response = await callCluster('search', params); + const state = get(response, 'hits.hits[0]._source.task.state', '{}'); + const clusterState: AlertClusterState = get( + JSON.parse(state), + `alertTypeState.${clusterUuid}`, + { + expiredCheckDateMS: 0, + ui: { + isFiring: false, + message: null, + severity: 0, + resolvedMS: 0, + expirationTime: 0, + }, + } + ); + const isInBetween = moment(clusterState.ui.resolvedMS).isBetween(start, end); + if (clusterState.ui.isFiring || isInBetween) { + return resolve({ + type, + ...clusterState.ui, + }); + } + return resolve(false); + } catch (err) { + const reason = get(err, 'body.error.type'); + if (reason === 'index_not_found_exception') { + server.log( + ['error', LOGGING_TAG], + `Unable to fetch alerts. Alerts depends on task manager, which has not been started yet.` + ); + } else { + server.log(['error', LOGGING_TAG], err.message); + } + return resolve(false); + } + }) + ) + ); + + return statuses.filter(Boolean); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.test.ts new file mode 100644 index 0000000000000..a5eb104986161 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.test.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getCcsIndexPattern } from './get_ccs_index_pattern'; + +describe('getCcsIndexPattern', () => { + it('should return an index pattern including remotes', () => { + const remotes = ['Remote1', 'Remote2']; + const index = '.monitoring-es-*'; + const result = getCcsIndexPattern(index, remotes); + expect(result).toBe('.monitoring-es-*,Remote1:.monitoring-es-*,Remote2:.monitoring-es-*'); + }); + + it('should return an index pattern from multiple index patterns including remotes', () => { + const remotes = ['Remote1', 'Remote2']; + const index = '.monitoring-es-*,.monitoring-kibana-*'; + const result = getCcsIndexPattern(index, remotes); + expect(result).toBe( + '.monitoring-es-*,.monitoring-kibana-*,Remote1:.monitoring-es-*,Remote2:.monitoring-es-*,Remote1:.monitoring-kibana-*,Remote2:.monitoring-kibana-*' + ); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts new file mode 100644 index 0000000000000..b562fde2a0810 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/get_ccs_index_pattern.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export function getCcsIndexPattern(indexPattern: string, remotes: string[]): string { + return `${indexPattern},${indexPattern + .split(',') + .map(pattern => { + return remotes.map(remoteName => `${remoteName}:${pattern}`).join(','); + }) + .join(',')}`; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.test.ts new file mode 100644 index 0000000000000..1a2eb1e44be84 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.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 moment from 'moment-timezone'; +import { executeActions, getUiMessage } from './license_expiration.lib'; + +describe('licenseExpiration lib', () => { + describe('executeActions', () => { + const clusterName = 'clusterA'; + const instance: any = { scheduleActions: jest.fn() }; + const license: any = { clusterName }; + const $expiry = moment('2020-01-20'); + const dateFormat = 'dddd, MMMM Do YYYY, h:mm:ss a'; + const emailAddress = 'test@test.com'; + + beforeEach(() => { + instance.scheduleActions.mockClear(); + }); + + it('should schedule actions when firing', () => { + executeActions(instance, license, $expiry, dateFormat, emailAddress, false); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'NEW X-Pack Monitoring: License Expiration', + message: `Cluster '${clusterName}' license is going to expire on Monday, January 20th 2020, 12:00:00 am. Please update your license.`, + to: emailAddress, + }); + }); + + it('should schedule actions when resolved', () => { + executeActions(instance, license, $expiry, dateFormat, emailAddress, true); + expect(instance.scheduleActions).toHaveBeenCalledWith('default', { + subject: 'RESOLVED X-Pack Monitoring: License Expiration', + message: `This cluster alert has been resolved: Cluster '${clusterName}' license was going to expire on Monday, January 20th 2020, 12:00:00 am.`, + to: emailAddress, + }); + }); + }); + + describe('getUiMessage', () => { + const timezone = 'Europe/London'; + const license: any = { expiryDateMS: moment.tz('2020-01-20 08:00:00', timezone).utc() }; + + it('should return a message when firing', () => { + const message = getUiMessage(license, timezone, false); + expect(message).toBe(`This cluster's license is going to expire in #relative at #absolute.`); + }); + + it('should return a message when resolved', () => { + const message = getUiMessage(license, timezone, true); + expect(message).toBe(`This cluster's license is active.`); + }); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts b/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts new file mode 100644 index 0000000000000..8a75fc1fbbd82 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/alerts/license_expiration.lib.ts @@ -0,0 +1,58 @@ +/* + * 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 { Moment } from 'moment-timezone'; +import { i18n } from '@kbn/i18n'; +import { AlertInstance } from '../../../../alerting/server/alert_instance'; +import { AlertLicense } from '../../alerts/types'; + +const RESOLVED_SUBJECT = i18n.translate( + 'xpack.monitoring.alerts.licenseExpiration.resolvedSubject', + { + defaultMessage: 'RESOLVED X-Pack Monitoring: License Expiration', + } +); + +const NEW_SUBJECT = i18n.translate('xpack.monitoring.alerts.licenseExpiration.newSubject', { + defaultMessage: 'NEW X-Pack Monitoring: License Expiration', +}); + +export function executeActions( + instance: AlertInstance, + license: AlertLicense, + $expiry: Moment, + dateFormat: string, + emailAddress: string, + resolved: boolean = false +) { + if (resolved) { + instance.scheduleActions('default', { + subject: RESOLVED_SUBJECT, + message: `This cluster alert has been resolved: Cluster '${ + license.clusterName + }' license was going to expire on ${$expiry.format(dateFormat)}.`, + to: emailAddress, + }); + } else { + instance.scheduleActions('default', { + subject: NEW_SUBJECT, + message: `Cluster '${license.clusterName}' license is going to expire on ${$expiry.format( + dateFormat + )}. Please update your license.`, + to: emailAddress, + }); + } +} + +export function getUiMessage(license: AlertLicense, timezone: string, resolved: boolean = false) { + if (resolved) { + return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.resolvedMessage', { + defaultMessage: `This cluster's license is active.`, + }); + } + return i18n.translate('xpack.monitoring.alerts.licenseExpiration.ui.firingMessage', { + defaultMessage: `This cluster's license is going to expire in #relative at #absolute.`, + }); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index 2b080a5c333fc..a5426dc04545e 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -16,6 +16,7 @@ import { getBeatsForClusters } from '../beats'; import { alertsClustersAggregation } from '../../cluster_alerts/alerts_clusters_aggregation'; import { alertsClusterSearch } from '../../cluster_alerts/alerts_cluster_search'; import { checkLicense as checkLicenseForAlerts } from '../../cluster_alerts/check_license'; +import { fetchStatus } from '../alerts/fetch_status'; import { getClustersSummary } from './get_clusters_summary'; import { CLUSTER_ALERTS_SEARCH_SIZE, @@ -27,6 +28,7 @@ import { CODE_PATH_LOGSTASH, CODE_PATH_BEATS, CODE_PATH_APM, + KIBANA_ALERTING_ENABLED, } from '../../../common/constants'; import { getApmsForClusters } from '../apm/get_apms_for_clusters'; import { i18n } from '@kbn/i18n'; @@ -99,15 +101,31 @@ export async function getClustersFromRequest( if (mlJobs !== null) { cluster.ml = { jobs: mlJobs }; } - const alerts = isInCodePath(codePaths, [CODE_PATH_ALERTS]) - ? await alertsClusterSearch(req, alertsIndex, cluster, checkLicenseForAlerts, { + + if (isInCodePath(codePaths, [CODE_PATH_ALERTS])) { + if (KIBANA_ALERTING_ENABLED) { + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + const callCluster = (...args) => callWithRequest(req, ...args); + cluster.alerts = await fetchStatus( + callCluster, start, end, - size: CLUSTER_ALERTS_SEARCH_SIZE, - }) - : null; - if (alerts) { - cluster.alerts = alerts; + cluster.cluster_uuid, + req.server + ); + } else { + cluster.alerts = await alertsClusterSearch( + req, + alertsIndex, + cluster, + checkLicenseForAlerts, + { + start, + end, + size: CLUSTER_ALERTS_SEARCH_SIZE, + } + ); + } } cluster.logs = isInCodePath(codePaths, [CODE_PATH_LOGS]) diff --git a/x-pack/legacy/plugins/monitoring/server/lib/get_date_format.js b/x-pack/legacy/plugins/monitoring/server/lib/get_date_format.js new file mode 100644 index 0000000000000..89cbf20d9b56f --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/get_date_format.js @@ -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. + */ + +export async function getDateFormat(req) { + return await req.getUiSettingsService().get('dateFormat'); +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js index 5f52e0c6a983b..a12b48510a6ff 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/setup/collection/get_collection_status.js @@ -348,7 +348,6 @@ export const getCollectionStatus = async ( }, }; } - const liveClusterUuid = skipLiveData ? null : await getLiveElasticsearchClusterUuid(req); const isLiveCluster = !clusterUuid || liveClusterUuid === clusterUuid; diff --git a/x-pack/legacy/plugins/monitoring/server/plugin.js b/x-pack/legacy/plugins/monitoring/server/plugin.js index ef346e95ad075..50e5319a0f526 100644 --- a/x-pack/legacy/plugins/monitoring/server/plugin.js +++ b/x-pack/legacy/plugins/monitoring/server/plugin.js @@ -5,12 +5,17 @@ */ import { i18n } from '@kbn/i18n'; -import { LOGGING_TAG, KIBANA_MONITORING_LOGGING_TAG } from '../common/constants'; +import { + LOGGING_TAG, + KIBANA_MONITORING_LOGGING_TAG, + KIBANA_ALERTING_ENABLED, +} from '../common/constants'; import { requireUIRoutes } from './routes'; import { instantiateClient } from './es_client/instantiate_client'; import { initMonitoringXpackInfo } from './init_monitoring_xpack_info'; import { initBulkUploader, registerCollectors } from './kibana_monitoring'; import { registerMonitoringCollection } from './telemetry_collection'; +import { getLicenseExpiration } from './alerts/license_expiration'; import { parseElasticsearchConfig } from './es_client/parse_elasticsearch_config'; export class Plugin { @@ -133,5 +138,37 @@ export class Plugin { showCgroupMetricsLogstash: config.get('monitoring.ui.container.logstash.enabled'), // Note, not currently used, but see https://github.com/elastic/x-pack-kibana/issues/1559 part 2 }; }); + + if (KIBANA_ALERTING_ENABLED && plugins.alerting) { + // this is not ready right away but we need to register alerts right away + async function getMonitoringCluster() { + const configs = config.get('xpack.monitoring.elasticsearch'); + if (configs.hosts) { + const monitoringCluster = plugins.elasticsearch.getCluster('monitoring'); + const { username, password } = configs; + const fakeRequest = { + headers: { + authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, + }, + }; + return { + callCluster: (...args) => monitoringCluster.callWithRequest(fakeRequest, ...args), + }; + } + return null; + } + + function getLogger(contexts) { + return core.logger.get('plugins', LOGGING_TAG, ...contexts); + } + plugins.alerting.setup.registerType( + getLicenseExpiration( + core._hapi, + getMonitoringCluster, + getLogger, + config.get('xpack.monitoring.ccs.enabled') + ) + ); + } } } diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/alerts.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/alerts.js new file mode 100644 index 0000000000000..f87683effe437 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/alerts.js @@ -0,0 +1,89 @@ +/* + * 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 Joi from 'joi'; +import { isFunction } from 'lodash'; +import { + ALERT_TYPE_LICENSE_EXPIRATION, + MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, +} from '../../../../../common/constants'; + +async function createAlerts(req, alertsClient, { selectedEmailActionId }) { + const createdAlerts = []; + + // Create alerts + const ALERT_TYPES = { + [ALERT_TYPE_LICENSE_EXPIRATION]: { + schedule: { interval: '10s' }, + actions: [ + { + group: 'default', + id: selectedEmailActionId, + params: { + subject: '{{context.subject}}', + message: `{{context.message}}`, + to: ['{{context.to}}'], + }, + }, + ], + }, + }; + + for (const alertTypeId of Object.keys(ALERT_TYPES)) { + const existingAlert = await alertsClient.find({ + options: { + search: alertTypeId, + }, + }); + if (existingAlert.total === 1) { + await alertsClient.delete({ id: existingAlert.data[0].id }); + } + + const result = await alertsClient.create({ + data: { + enabled: true, + alertTypeId, + ...ALERT_TYPES[alertTypeId], + }, + }); + createdAlerts.push(result); + } + + return createdAlerts; +} + +async function saveEmailAddress(emailAddress, uiSettingsService) { + await uiSettingsService.set(MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, emailAddress); +} + +export function createKibanaAlertsRoute(server) { + server.route({ + method: 'POST', + path: '/api/monitoring/v1/alerts', + config: { + validate: { + payload: Joi.object({ + selectedEmailActionId: Joi.string().required(), + emailAddress: Joi.string().required(), + }), + }, + }, + async handler(req, headers) { + const { emailAddress, selectedEmailActionId } = req.payload; + const alertsClient = isFunction(req.getAlertsClient) ? req.getAlertsClient() : null; + if (!alertsClient) { + return headers.response().code(404); + } + + const [alerts, emailResponse] = await Promise.all([ + createAlerts(req, alertsClient, { ...req.params, selectedEmailActionId }), + saveEmailAddress(emailAddress, req.getUiSettingsService()), + ]); + + return { alerts, emailResponse }; + }, + }); +} diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/index.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/index.js index cdcd776b349fc..246cdfde97cff 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/index.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/index.js @@ -4,54 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import Joi from 'joi'; -import { alertsClusterSearch } from '../../../../cluster_alerts/alerts_cluster_search'; -import { checkLicense } from '../../../../cluster_alerts/check_license'; -import { getClusterLicense } from '../../../../lib/cluster/get_cluster_license'; -import { prefixIndexPattern } from '../../../../lib/ccs_utils'; -import { INDEX_PATTERN_ELASTICSEARCH, INDEX_ALERTS } from '../../../../../common/constants'; - -/* - * Cluster Alerts route. - */ -export function clusterAlertsRoute(server) { - server.route({ - method: 'POST', - path: '/api/monitoring/v1/clusters/{clusterUuid}/alerts', - config: { - validate: { - params: Joi.object({ - clusterUuid: Joi.string().required(), - }), - payload: Joi.object({ - ccs: Joi.string().optional(), - timeRange: Joi.object({ - min: Joi.date().required(), - max: Joi.date().required(), - }).required(), - }), - }, - }, - handler(req) { - const config = server.config(); - const ccs = req.payload.ccs; - const clusterUuid = req.params.clusterUuid; - const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); - const alertsIndex = prefixIndexPattern(config, INDEX_ALERTS, ccs); - const options = { - start: req.payload.timeRange.min, - end: req.payload.timeRange.max, - }; - - return getClusterLicense(req, esIndexPattern, clusterUuid).then(license => - alertsClusterSearch( - req, - alertsIndex, - { cluster_uuid: clusterUuid, license }, - checkLicense, - options - ) - ); - }, - }); -} +export * from './legacy_alerts'; +export * from './alerts'; diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js new file mode 100644 index 0000000000000..a3049f0f3e2d2 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/alerts/legacy_alerts.js @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; +import { alertsClusterSearch } from '../../../../cluster_alerts/alerts_cluster_search'; +import { checkLicense } from '../../../../cluster_alerts/check_license'; +import { getClusterLicense } from '../../../../lib/cluster/get_cluster_license'; +import { prefixIndexPattern } from '../../../../lib/ccs_utils'; +import { INDEX_PATTERN_ELASTICSEARCH, INDEX_ALERTS } from '../../../../../common/constants'; + +/* + * Cluster Alerts route. + */ +export function legacyClusterAlertsRoute(server) { + server.route({ + method: 'POST', + path: '/api/monitoring/v1/clusters/{clusterUuid}/legacy_alerts', + config: { + validate: { + params: Joi.object({ + clusterUuid: Joi.string().required(), + }), + payload: Joi.object({ + ccs: Joi.string().optional(), + timeRange: Joi.object({ + min: Joi.date().required(), + max: Joi.date().required(), + }).required(), + }), + }, + }, + handler(req) { + const config = server.config(); + const ccs = req.payload.ccs; + const clusterUuid = req.params.clusterUuid; + const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); + const alertsIndex = prefixIndexPattern(config, INDEX_ALERTS, ccs); + const options = { + start: req.payload.timeRange.min, + end: req.payload.timeRange.max, + }; + + return getClusterLicense(req, esIndexPattern, clusterUuid).then(license => + alertsClusterSearch( + req, + alertsIndex, + { cluster_uuid: clusterUuid, license }, + checkLicense, + options + ) + ); + }, + }); +} diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/ui.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/ui.js index baffbfd5f3f6f..de0213ec84689 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/ui.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/ui.js @@ -6,7 +6,7 @@ // all routes for the app export { checkAccessRoute } from './check_access'; -export { clusterAlertsRoute } from './alerts/'; +export * from './alerts/'; export { beatsDetailRoute, beatsListingRoute, beatsOverviewRoute } from './beats'; export { clusterRoute, clustersRoute } from './cluster'; export { diff --git a/x-pack/legacy/plugins/monitoring/ui_exports.js b/x-pack/legacy/plugins/monitoring/ui_exports.js index 9251deb673bd1..49f167b0f1b10 100644 --- a/x-pack/legacy/plugins/monitoring/ui_exports.js +++ b/x-pack/legacy/plugins/monitoring/ui_exports.js @@ -6,6 +6,10 @@ import { i18n } from '@kbn/i18n'; import { resolve } from 'path'; +import { + MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS, + KIBANA_ALERTING_ENABLED, +} from './common/constants'; import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; /** @@ -14,28 +18,48 @@ import { DEFAULT_APP_CATEGORIES } from '../../../../src/core/utils'; * app (injectDefaultVars and hacks) * @return {Object} data per Kibana plugin uiExport schema */ -export const getUiExports = () => ({ - app: { - title: i18n.translate('xpack.monitoring.stackMonitoringTitle', { - defaultMessage: 'Stack Monitoring', - }), - order: 9002, - description: i18n.translate('xpack.monitoring.uiExportsDescription', { - defaultMessage: 'Monitoring for Elastic Stack', - }), - icon: 'plugins/monitoring/icons/monitoring.svg', - euiIconType: 'monitoringApp', - linkToLastSubUrl: false, - main: 'plugins/monitoring/monitoring', - category: DEFAULT_APP_CATEGORIES.management, - }, - injectDefaultVars(server) { - const config = server.config(); - return { - monitoringUiEnabled: config.get('monitoring.ui.enabled'), +export const getUiExports = () => { + const uiSettingDefaults = {}; + if (KIBANA_ALERTING_ENABLED) { + uiSettingDefaults[MONITORING_CONFIG_ALERTING_EMAIL_ADDRESS] = { + name: i18n.translate('xpack.monitoring.alertingEmailAddress.name', { + defaultMessage: 'Alerting email address', + }), + value: '', + description: i18n.translate('xpack.monitoring.alertingEmailAddress.description', { + defaultMessage: `The default email address to receive alerts from Stack Monitoring`, + }), + category: ['monitoring'], }; - }, - hacks: ['plugins/monitoring/hacks/toggle_app_link_in_nav'], - home: ['plugins/monitoring/register_feature'], - styleSheetPaths: resolve(__dirname, 'public/index.scss'), -}); + } + + return { + app: { + title: i18n.translate('xpack.monitoring.stackMonitoringTitle', { + defaultMessage: 'Stack Monitoring', + }), + order: 9002, + description: i18n.translate('xpack.monitoring.uiExportsDescription', { + defaultMessage: 'Monitoring for Elastic Stack', + }), + icon: 'plugins/monitoring/icons/monitoring.svg', + euiIconType: 'monitoringApp', + linkToLastSubUrl: false, + main: 'plugins/monitoring/monitoring', + category: DEFAULT_APP_CATEGORIES.management, + }, + injectDefaultVars(server) { + const config = server.config(); + return { + monitoringUiEnabled: config.get('monitoring.ui.enabled'), + monitoringLegacyEmailAddress: config.get( + 'monitoring.cluster_alerts.email_notifications.email_address' + ), + }; + }, + uiSettingDefaults, + hacks: ['plugins/monitoring/hacks/toggle_app_link_in_nav'], + home: ['plugins/monitoring/register_feature'], + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + }; +}; diff --git a/x-pack/plugins/actions/common/types.ts b/x-pack/plugins/actions/common/types.ts index 784125b83859d..fbd7404a2f15e 100644 --- a/x-pack/plugins/actions/common/types.ts +++ b/x-pack/plugins/actions/common/types.ts @@ -9,3 +9,10 @@ export interface ActionType { name: string; enabled: boolean; } + +export interface ActionResult { + id: string; + actionTypeId: string; + name: string; + config: Record; +} From d5ba32ff085cbbe4f9e0478ba261a6a2d2af129c Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 3 Feb 2020 13:11:48 -0800 Subject: [PATCH 16/60] [APM] Fixes maxTraceItems in waterfall trace and related error queries (#56111) * Addresses #55494 by combining waterfall trace and related error queries. Makes sure that maxTraceItems is only respected for Transactions and Spans. * addressed feedback back separating the combined query into 1 for transactions/spans and 1 for errors + errorCount Co-authored-by: Elastic Machine --- .../Marks/__test__/get_error_marks.test.ts | 15 +- .../Marks/get_error_marks.ts | 9 +- .../WaterfallContainer/Waterfall/index.tsx | 6 +- .../waterfall_helpers.test.ts.snap | 141 +++++++++--------- .../waterfall_helpers.test.ts | 17 ++- .../waterfall_helpers/waterfall_helpers.ts | 91 +++++++++-- .../plugins/apm/public/hooks/useWaterfall.ts | 2 +- .../errors/__snapshots__/queries.test.ts.snap | 64 -------- .../get_trace_errors_per_transaction.ts | 65 -------- .../apm/server/lib/errors/queries.test.ts | 9 -- .../traces/__snapshots__/queries.test.ts.snap | 50 +++---- .../apm/server/lib/traces/get_trace.ts | 9 +- .../apm/server/lib/traces/get_trace_items.ts | 80 +++++++++- x-pack/legacy/plugins/apm/typings/common.d.ts | 4 + 14 files changed, 267 insertions(+), 295 deletions(-) delete mode 100644 x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts index 8fd8edd7f8a72..b7e83073a205b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IWaterfallItem } from '../../Waterfall/waterfall_helpers/waterfall_helpers'; +import { IWaterfallError } from '../../Waterfall/waterfall_helpers/waterfall_helpers'; import { getErrorMarks } from '../get_error_marks'; describe('getErrorMarks', () => { @@ -12,13 +12,6 @@ describe('getErrorMarks', () => { it('when items are missing', () => { expect(getErrorMarks([], {})).toEqual([]); }); - it('when any error is available', () => { - const items = [ - { docType: 'span' }, - { docType: 'transaction' } - ] as IWaterfallItem[]; - expect(getErrorMarks(items, {})).toEqual([]); - }); }); it('returns error marks', () => { @@ -29,14 +22,13 @@ describe('getErrorMarks', () => { skew: 5, doc: { error: { id: 1 }, service: { name: 'opbeans-java' } } } as unknown, - { docType: 'transaction' }, { docType: 'error', offset: 50, skew: 0, doc: { error: { id: 2 }, service: { name: 'opbeans-node' } } } as unknown - ] as IWaterfallItem[]; + ] as IWaterfallError[]; expect( getErrorMarks(items, { 'opbeans-java': 'red', 'opbeans-node': 'blue' }) ).toEqual([ @@ -67,14 +59,13 @@ describe('getErrorMarks', () => { skew: 5, doc: { error: { id: 1 }, service: { name: 'opbeans-java' } } } as unknown, - { docType: 'transaction' }, { docType: 'error', offset: 50, skew: 0, doc: { error: { id: 2 }, service: { name: 'opbeans-node' } } } as unknown - ] as IWaterfallItem[]; + ] as IWaterfallError[]; expect(getErrorMarks(items, {})).toEqual([ { type: 'errorMark', diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts index f1f0163a49d10..e2b00c13c5c1f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts @@ -6,7 +6,6 @@ import { isEmpty } from 'lodash'; import { ErrorRaw } from '../../../../../../../typings/es_schemas/raw/ErrorRaw'; import { - IWaterfallItem, IWaterfallError, IServiceColors } from '../Waterfall/waterfall_helpers/waterfall_helpers'; @@ -19,16 +18,14 @@ export interface ErrorMark extends Mark { } export const getErrorMarks = ( - items: IWaterfallItem[], + errorItems: IWaterfallError[], serviceColors: IServiceColors ): ErrorMark[] => { - if (isEmpty(items)) { + if (isEmpty(errorItems)) { return []; } - return (items.filter( - item => item.docType === 'error' - ) as IWaterfallError[]).map(error => ({ + return errorItems.map(error => ({ type: 'errorMark', offset: error.offset + error.skew, verticalLine: false, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx index b48fc1cf7ca27..4f584786f2f9a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx @@ -80,13 +80,9 @@ export const Waterfall: React.FC = ({ const { serviceColors, duration } = waterfall; const agentMarks = getAgentMarks(waterfall.entryTransaction); - const errorMarks = getErrorMarks(waterfall.items, serviceColors); + const errorMarks = getErrorMarks(waterfall.errorItems, serviceColors); const renderWaterfallItem = (item: IWaterfallItem) => { - if (item.docType === 'error') { - return null; - } - const errorCount = item.docType === 'transaction' ? waterfall.errorsPerTransaction[item.doc.transaction.id] diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index 5b1b9be33c375..c9b29e8692f44 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -24,6 +24,77 @@ Object { "name": "GET /api", }, }, + "errorItems": Array [ + Object { + "doc": Object { + "agent": Object { + "name": "ruby", + "version": "2", + }, + "error": Object { + "grouping_key": "errorGroupingKey1", + "id": "error1", + "log": Object { + "message": "error message", + }, + }, + "parent": Object { + "id": "myTransactionId1", + }, + "processor": Object { + "event": "error", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795810000, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId1", + }, + }, + "docType": "error", + "duration": 0, + "id": "error1", + "offset": 25994, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, + }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, + }, + "parentId": "myTransactionId1", + "skew": 0, + }, + ], "errorsCount": 1, "errorsPerTransaction": Object { "myTransactionId1": 2, @@ -716,75 +787,6 @@ Object { "parentId": "mySpanIdA", "skew": 0, }, - Object { - "doc": Object { - "agent": Object { - "name": "ruby", - "version": "2", - }, - "error": Object { - "grouping_key": "errorGroupingKey1", - "id": "error1", - "log": Object { - "message": "error message", - }, - }, - "parent": Object { - "id": "myTransactionId1", - }, - "processor": Object { - "event": "error", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "timestamp": Object { - "us": 1549324795810000, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId1", - }, - }, - "docType": "error", - "duration": 0, - "id": "error1", - "offset": 25994, - "parent": Object { - "doc": Object { - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-node", - }, - "timestamp": Object { - "us": 1549324795784006, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 49660, - }, - "id": "myTransactionId1", - "name": "GET /api", - }, - }, - "docType": "transaction", - "duration": 49660, - "id": "myTransactionId1", - "offset": 0, - "parent": undefined, - "parentId": undefined, - "skew": 0, - }, - "parentId": "myTransactionId1", - "skew": 0, - }, ], "rootTransaction": Object { "processor": Object { @@ -848,6 +850,7 @@ Object { "name": "Api::ProductsController#index", }, }, + "errorItems": Array [], "errorsCount": 0, "errorsPerTransaction": Object { "myTransactionId1": 2, diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts index 426842bc02f51..6b13b93200c61 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts @@ -12,7 +12,8 @@ import { getOrderedWaterfallItems, getWaterfall, IWaterfallItem, - IWaterfallTransaction + IWaterfallTransaction, + IWaterfallError } from './waterfall_helpers'; import { APMError } from '../../../../../../../../typings/es_schemas/ui/APMError'; @@ -100,7 +101,9 @@ describe('waterfall_helpers', () => { } }, timestamp: { us: 1549324795823304 } - } as unknown) as Transaction, + } as unknown) as Transaction + ]; + const errorDocs = [ ({ processor: { event: 'error' }, parent: { id: 'myTransactionId1' }, @@ -130,14 +133,15 @@ describe('waterfall_helpers', () => { }; const waterfall = getWaterfall( { - trace: { items: hits, exceedsMax: false }, + trace: { items: hits, errorDocs, exceedsMax: false }, errorsPerTransaction }, entryTransactionId ); - expect(waterfall.items.length).toBe(7); + expect(waterfall.items.length).toBe(6); expect(waterfall.items[0].id).toBe('myTransactionId1'); + expect(waterfall.errorItems.length).toBe(1); expect(waterfall.errorsCount).toEqual(1); expect(waterfall).toMatchSnapshot(); }); @@ -150,7 +154,7 @@ describe('waterfall_helpers', () => { }; const waterfall = getWaterfall( { - trace: { items: hits, exceedsMax: false }, + trace: { items: hits, errorDocs, exceedsMax: false }, errorsPerTransaction }, entryTransactionId @@ -158,6 +162,7 @@ describe('waterfall_helpers', () => { expect(waterfall.items.length).toBe(4); expect(waterfall.items[0].id).toBe('myTransactionId2'); + expect(waterfall.errorItems.length).toBe(0); expect(waterfall.errorsCount).toEqual(0); expect(waterfall).toMatchSnapshot(); }); @@ -386,7 +391,7 @@ describe('waterfall_helpers', () => { it('should return parent skew for errors', () => { const child = { docType: 'error' - } as IWaterfallItem; + } as IWaterfallError; const parent = { docType: 'transaction', diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 1af6cddb3ba4a..3b52163aa7fa4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -24,6 +24,8 @@ interface IWaterfallGroup { [key: string]: IWaterfallItem[]; } +const ROOT_ID = 'root'; + export interface IWaterfall { entryTransaction?: Transaction; rootTransaction?: Transaction; @@ -36,6 +38,7 @@ export interface IWaterfall { errorsPerTransaction: TraceAPIResponse['errorsPerTransaction']; errorsCount: number; serviceColors: IServiceColors; + errorItems: IWaterfallError[]; } interface IWaterfallItemBase { @@ -70,10 +73,7 @@ export type IWaterfallTransaction = IWaterfallItemBase< export type IWaterfallSpan = IWaterfallItemBase; export type IWaterfallError = IWaterfallItemBase; -export type IWaterfallItem = - | IWaterfallTransaction - | IWaterfallSpan - | IWaterfallError; +export type IWaterfallItem = IWaterfallTransaction | IWaterfallSpan; function getTransactionItem(transaction: Transaction): IWaterfallTransaction { return { @@ -99,20 +99,34 @@ function getSpanItem(span: Span): IWaterfallSpan { }; } -function getErrorItem(error: APMError): IWaterfallError { - return { +function getErrorItem( + error: APMError, + items: IWaterfallItem[], + entryWaterfallTransaction?: IWaterfallTransaction +): IWaterfallError { + const entryTimestamp = entryWaterfallTransaction?.doc.timestamp.us ?? 0; + const parent = items.find( + waterfallItem => waterfallItem.id === error.parent?.id + ); + const errorItem: IWaterfallError = { docType: 'error', doc: error, id: error.error.id, - parentId: error.parent?.id, - offset: 0, + parent, + parentId: parent?.id, + offset: error.timestamp.us - entryTimestamp, skew: 0, duration: 0 }; + + return { + ...errorItem, + skew: getClockSkew(errorItem, parent) + }; } export function getClockSkew( - item: IWaterfallItem, + item: IWaterfallItem | IWaterfallError, parentItem?: IWaterfallItem ) { if (!parentItem) { @@ -218,13 +232,11 @@ const getWaterfallItems = (items: TraceAPIResponse['trace']['items']) => return getSpanItem(item as Span); case 'transaction': return getTransactionItem(item as Transaction); - case 'error': - return getErrorItem(item as APMError); } }); const getChildrenGroupedByParentId = (waterfallItems: IWaterfallItem[]) => - groupBy(waterfallItems, item => (item.parentId ? item.parentId : 'root')); + groupBy(waterfallItems, item => (item.parentId ? item.parentId : ROOT_ID)); const getEntryWaterfallTransaction = ( entryTransactionId: string, @@ -234,6 +246,48 @@ const getEntryWaterfallTransaction = ( item => item.docType === 'transaction' && item.id === entryTransactionId ) as IWaterfallTransaction; +function isInEntryTransaction( + parentIdLookup: Map, + entryTransactionId: string, + currentId: string +): boolean { + if (currentId === entryTransactionId) { + return true; + } + const parentId = parentIdLookup.get(currentId); + if (parentId) { + return isInEntryTransaction(parentIdLookup, entryTransactionId, parentId); + } + return false; +} + +function getWaterfallErrors( + errorDocs: TraceAPIResponse['trace']['errorDocs'], + items: IWaterfallItem[], + entryWaterfallTransaction?: IWaterfallTransaction +) { + const errorItems = errorDocs.map(errorDoc => + getErrorItem(errorDoc, items, entryWaterfallTransaction) + ); + if (!entryWaterfallTransaction) { + return errorItems; + } + const parentIdLookup = [...items, ...errorItems].reduce( + (map, { id, parentId }) => { + map.set(id, parentId ?? ROOT_ID); + return map; + }, + new Map() + ); + return errorItems.filter(errorItem => + isInEntryTransaction( + parentIdLookup, + entryWaterfallTransaction?.id, + errorItem.id + ) + ); +} + export function getWaterfall( { trace, errorsPerTransaction }: TraceAPIResponse, entryTransactionId?: Transaction['transaction']['id'] @@ -244,7 +298,8 @@ export function getWaterfall( items: [], errorsPerTransaction, errorsCount: sum(Object.values(errorsPerTransaction)), - serviceColors: {} + serviceColors: {}, + errorItems: [] }; } @@ -261,6 +316,11 @@ export function getWaterfall( childrenByParentId, entryWaterfallTransaction ); + const errorItems = getWaterfallErrors( + trace.errorDocs, + items, + entryWaterfallTransaction + ); const rootTransaction = getRootTransaction(childrenByParentId); const duration = getWaterfallDuration(items); @@ -274,7 +334,8 @@ export function getWaterfall( duration, items, errorsPerTransaction, - errorsCount: items.filter(item => item.docType === 'error').length, - serviceColors + errorsCount: errorItems.length, + serviceColors, + errorItems }; } diff --git a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts index f3f10c0e46d9b..8d623a0268222 100644 --- a/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/legacy/plugins/apm/public/hooks/useWaterfall.ts @@ -11,7 +11,7 @@ import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWith const INITIAL_DATA = { root: undefined, - trace: { items: [], exceedsMax: false }, + trace: { items: [], exceedsMax: false, errorDocs: [] }, errorsPerTransaction: {} }; diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap index 88d8edd17454a..a2629366dd6d9 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/errors/__snapshots__/queries.test.ts.snap @@ -205,67 +205,3 @@ Object { "index": "myIndex", } `; - -exports[`error queries fetches trace errors 1`] = ` -Object { - "body": Object { - "aggs": Object { - "transactions": Object { - "terms": Object { - "execution_hint": "map", - "field": "transaction.id", - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "trace.id": "foo", - }, - }, - Object { - "term": Object { - "processor.event": "error", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - "should": Array [ - Object { - "bool": Object { - "must_not": Array [ - Object { - "exists": Object { - "field": "error.log.level", - }, - }, - ], - }, - }, - Object { - "terms": Object { - "error.log.level": Array [ - "critical", - "error", - "fatal", - ], - }, - }, - ], - }, - }, - "size": 0, - }, - "index": "myIndex", -} -`; diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts b/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts deleted file mode 100644 index 6027693be5180..0000000000000 --- a/x-pack/legacy/plugins/apm/server/lib/errors/get_trace_errors_per_transaction.ts +++ /dev/null @@ -1,65 +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 { - ERROR_LOG_LEVEL, - PROCESSOR_EVENT, - TRACE_ID, - TRANSACTION_ID -} from '../../../common/elasticsearch_fieldnames'; -import { rangeFilter } from '../helpers/range_filter'; -import { Setup, SetupTimeRange } from '../helpers/setup_request'; - -export interface ErrorsPerTransaction { - [transactionId: string]: number; -} - -const includedLogLevels = ['critical', 'error', 'fatal']; - -export async function getTraceErrorsPerTransaction( - traceId: string, - setup: Setup & SetupTimeRange -): Promise { - const { start, end, client, indices } = setup; - - const params = { - index: indices['apm_oss.errorIndices'], - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [TRACE_ID]: traceId } }, - { term: { [PROCESSOR_EVENT]: 'error' } }, - { range: rangeFilter(start, end) } - ], - should: [ - { bool: { must_not: [{ exists: { field: ERROR_LOG_LEVEL } }] } }, - { terms: { [ERROR_LOG_LEVEL]: includedLogLevels } } - ] - } - }, - aggs: { - transactions: { - terms: { - field: TRANSACTION_ID, - // high cardinality - execution_hint: 'map' - } - } - } - } - } as const; - - const resp = await client.search(params); - return (resp.aggregations?.transactions.buckets || []).reduce( - (acc, bucket) => ({ - ...acc, - [bucket.key]: bucket.doc_count - }), - {} - ); -} diff --git a/x-pack/legacy/plugins/apm/server/lib/errors/queries.test.ts b/x-pack/legacy/plugins/apm/server/lib/errors/queries.test.ts index 2b1704d9424e4..f1e5d31efd4bd 100644 --- a/x-pack/legacy/plugins/apm/server/lib/errors/queries.test.ts +++ b/x-pack/legacy/plugins/apm/server/lib/errors/queries.test.ts @@ -6,7 +6,6 @@ import { getErrorGroup } from './get_error_group'; import { getErrorGroups } from './get_error_groups'; -import { getTraceErrorsPerTransaction } from './get_trace_errors_per_transaction'; import { SearchParamsMock, inspectSearchParams @@ -56,12 +55,4 @@ describe('error queries', () => { expect(mock.params).toMatchSnapshot(); }); - - it('fetches trace errors', async () => { - mock = await inspectSearchParams(setup => - getTraceErrorsPerTransaction('foo', setup) - ); - - expect(mock.params).toMatchSnapshot(); - }); }); diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap b/x-pack/legacy/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap index a2828e1d74920..0a9f9d38b2be7 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap +++ b/x-pack/legacy/plugins/apm/server/lib/traces/__snapshots__/queries.test.ts.snap @@ -3,6 +3,15 @@ exports[`trace queries fetches a trace 1`] = ` Object { "body": Object { + "aggs": Object { + "by_transaction_id": Object { + "terms": Object { + "execution_hint": "map", + "field": "transaction.id", + "size": "myIndex", + }, + }, + }, "query": Object { "bool": Object { "filter": Array [ @@ -12,12 +21,8 @@ Object { }, }, Object { - "terms": Object { - "processor.event": Array [ - "span", - "transaction", - "error", - ], + "term": Object { + "processor.event": "error", }, }, Object { @@ -30,36 +35,19 @@ Object { }, }, ], - "should": Object { - "exists": Object { - "field": "parent.id", + "must_not": Object { + "terms": Object { + "error.log.level": Array [ + "debug", + "info", + "warning", + ], }, }, }, }, "size": "myIndex", - "sort": Array [ - Object { - "_score": Object { - "order": "asc", - }, - }, - Object { - "transaction.duration.us": Object { - "order": "desc", - }, - }, - Object { - "span.duration.us": Object { - "order": "desc", - }, - }, - ], - "track_total_hits": true, }, - "index": Array [ - "myIndex", - "myIndex", - ], + "index": "myIndex", } `; diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts index e38ce56edde80..a1b9270e0d7b3 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace.ts @@ -5,16 +5,15 @@ */ import { PromiseReturnType } from '../../../typings/common'; -import { getTraceErrorsPerTransaction } from '../errors/get_trace_errors_per_transaction'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; import { getTraceItems } from './get_trace_items'; export type TraceAPIResponse = PromiseReturnType; export async function getTrace(traceId: string, setup: Setup & SetupTimeRange) { - const [trace, errorsPerTransaction] = await Promise.all([ - getTraceItems(traceId, setup), - getTraceErrorsPerTransaction(traceId, setup) - ]); + const { errorsPerTransaction, ...trace } = await getTraceItems( + traceId, + setup + ); return { trace, diff --git a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts index 8118b6acaee39..9d3e0d6db7f16 100644 --- a/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts +++ b/x-pack/legacy/plugins/apm/server/lib/traces/get_trace_items.ts @@ -9,13 +9,20 @@ import { TRACE_ID, PARENT_ID, TRANSACTION_DURATION, - SPAN_DURATION + SPAN_DURATION, + TRANSACTION_ID, + ERROR_LOG_LEVEL } from '../../../common/elasticsearch_fieldnames'; import { Span } from '../../../typings/es_schemas/ui/Span'; import { Transaction } from '../../../typings/es_schemas/ui/Transaction'; import { APMError } from '../../../typings/es_schemas/ui/APMError'; import { rangeFilter } from '../helpers/range_filter'; import { Setup, SetupTimeRange } from '../helpers/setup_request'; +import { PromiseValueType } from '../../../typings/common'; + +interface ErrorsPerTransaction { + [transactionId: string]: number; +} export async function getTraceItems( traceId: string, @@ -23,8 +30,36 @@ export async function getTraceItems( ) { const { start, end, client, config, indices } = setup; const maxTraceItems = config['xpack.apm.ui.maxTraceItems']; + const excludedLogLevels = ['debug', 'info', 'warning']; - const params = { + const errorResponsePromise = client.search({ + index: indices['apm_oss.errorIndices'], + body: { + size: maxTraceItems, + query: { + bool: { + filter: [ + { term: { [TRACE_ID]: traceId } }, + { term: { [PROCESSOR_EVENT]: 'error' } }, + { range: rangeFilter(start, end) } + ], + must_not: { terms: { [ERROR_LOG_LEVEL]: excludedLogLevels } } + } + }, + aggs: { + by_transaction_id: { + terms: { + field: TRANSACTION_ID, + size: maxTraceItems, + // high cardinality + execution_hint: 'map' as const + } + } + } + } + }); + + const traceResponsePromise = client.search({ index: [ indices['apm_oss.spanIndices'], indices['apm_oss.transactionIndices'] @@ -35,7 +70,7 @@ export async function getTraceItems( bool: { filter: [ { term: { [TRACE_ID]: traceId } }, - { terms: { [PROCESSOR_EVENT]: ['span', 'transaction', 'error'] } }, + { terms: { [PROCESSOR_EVENT]: ['span', 'transaction'] } }, { range: rangeFilter(start, end) } ], should: { @@ -50,12 +85,43 @@ export async function getTraceItems( ], track_total_hits: true } - }; + }); + + const [errorResponse, traceResponse]: [ + // explicit intermediary types to avoid TS "excessively deep" error + PromiseValueType, + PromiseValueType + // @ts-ignore + ] = await Promise.all([errorResponsePromise, traceResponsePromise]); + + const exceedsMax = traceResponse.hits.total.value > maxTraceItems; - const resp = await client.search(params); + const items = (traceResponse.hits.hits as Array<{ + _source: Transaction | Span; + }>).map(hit => hit._source); + + const errorFrequencies: { + errorsPerTransaction: ErrorsPerTransaction; + errorDocs: APMError[]; + } = { + errorDocs: errorResponse.hits.hits.map( + ({ _source }) => _source as APMError + ), + errorsPerTransaction: + errorResponse.aggregations?.by_transaction_id.buckets.reduce( + (acc, current) => { + return { + ...acc, + [current.key]: current.doc_count + }; + }, + {} as ErrorsPerTransaction + ) ?? {} + }; return { - items: resp.hits.hits.map(hit => hit._source), - exceedsMax: resp.hits.total.value > maxTraceItems + items, + exceedsMax, + ...errorFrequencies }; } diff --git a/x-pack/legacy/plugins/apm/typings/common.d.ts b/x-pack/legacy/plugins/apm/typings/common.d.ts index b9064980bd657..1e718f818246c 100644 --- a/x-pack/legacy/plugins/apm/typings/common.d.ts +++ b/x-pack/legacy/plugins/apm/typings/common.d.ts @@ -22,6 +22,10 @@ type AllowUnknownObjectProperties = T extends object } : T; +export type PromiseValueType = Value extends Promise + ? Value + : Value; + export type PromiseReturnType = Func extends ( ...args: any[] ) => Promise From 077d24de10a79f8a747782d54d1c574a876bed32 Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 3 Feb 2020 13:14:42 -0800 Subject: [PATCH 17/60] Closes #55502. Fixes firefox SVG error by preventing tooltip and marks (#56578) from rendering if there are no values in the Plot. Co-authored-by: Elastic Machine --- .../components/shared/charts/CustomPlot/InteractivePlot.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/InteractivePlot.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/InteractivePlot.js index bc758c7288e96..69b73ad6a0c0f 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/InteractivePlot.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/InteractivePlot.js @@ -66,9 +66,13 @@ class InteractivePlot extends PureComponent { const tooltipPoints = this.getTooltipPoints(hoverX); const markPoints = this.getMarkPoints(hoverX); - const { x, yTickValues } = plotValues; + const { x, xTickValues, yTickValues } = plotValues; const yValueMiddle = yTickValues[1]; + if (isEmpty(xTickValues)) { + return ; + } + return ( {hoverX && ( From e08df006c3a3d49ec8612c35298d2a68be12fced Mon Sep 17 00:00:00 2001 From: Oliver Gupte Date: Mon, 3 Feb 2020 13:19:23 -0800 Subject: [PATCH 18/60] [APM] Fix initial error sort field (#56577) * Closes #52840. Changes the initial sorting field from `latestOccurrenceAt` -> `occurrenceCount` * update jest snapshots Co-authored-by: Elastic Machine --- .../__test__/__snapshots__/List.test.tsx.snap | 68 +++++++++---------- .../app/ErrorGroupOverview/List/index.tsx | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap index 492d28206f3dd..a45357121354f 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/__snapshots__/List.test.tsx.snap @@ -43,7 +43,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` } initialPageSize={25} initialSortDirection="desc" - initialSortField="latestOccurrenceAt" + initialSortField="occurrenceCount" items={Array []} noItemsMessage="No errors were found" sortItems={false} @@ -190,7 +190,7 @@ exports[`ErrorGroupOverview -> List should render empty state 1`] = ` List should render empty state 1`] = ` } >
"`; +exports[`VisLegend Component Legend closed should match the snapshot 1`] = `"
"`; -exports[`VisLegend Component Legend open should match the snapshot 1`] = `"
"`; +exports[`VisLegend Component Legend open should match the snapshot 1`] = `"
"`; diff --git a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap index 18f84f41d5d99..307c0760de7ba 100644 --- a/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap +++ b/src/plugins/kibana_react/public/saved_objects/__snapshots__/saved_object_save_modal.test.tsx.snap @@ -8,7 +8,6 @@ exports[`SavedObjectSaveModal should render matching snapshot 1`] = ` diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json index d96febee7b06d..d320b57ee59e6 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.1", + "@elastic/eui": "18.3.0", "react": "^16.12.0", "react-dom": "^16.12.0" } diff --git a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json index 170cc77ca37cc..27a8c1fab6c8e 100644 --- a/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/kbn_tp_custom_visualizations/package.json @@ -7,7 +7,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.1", + "@elastic/eui": "18.3.0", "react": "^16.12.0" } } diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json index 85c76071d1e94..51bb7240dd7c4 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.1", + "@elastic/eui": "18.3.0", "react": "^16.12.0" }, "scripts": { diff --git a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json index ade93c9f50099..9ee0e3de51d8b 100644 --- a/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/kbn_tp_sample_panel_action/package.json @@ -8,7 +8,7 @@ }, "license": "Apache-2.0", "dependencies": { - "@elastic/eui": "18.2.1", + "@elastic/eui": "18.3.0", "react": "^16.12.0" }, "scripts": { diff --git a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap index b2c503806c385..260d7de3aefd4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__snapshots__/index.test.tsx.snap @@ -62,11 +62,7 @@ exports[`DetailView should render TabContent 1`] = ` `; exports[`DetailView should render tabs 1`] = ` - + - <path d="M13.6 12.186l-1.357-1.358c-.025-.025-.058-.034-.084-.056.53-.794.84-1.746.84-2.773a4.977 4.977 0 00-.84-2.772c.026-.02.059-.03.084-.056L13.6 3.813a6.96 6.96 0 010 8.373zM8 15A6.956 6.956 0 013.814 13.6l1.358-1.358c.025-.025.034-.057.055-.084C6.02 12.688 6.974 13 8 13a4.978 4.978 0 002.773-.84c.02.026.03.058.056.083l1.357 1.358A6.956 6.956 0 018 15zm-5.601-2.813a6.963 6.963 0 010-8.373l1.359 1.358c.024.025.057.035.084.056A4.97 4.97 0 003 8c0 1.027.31 1.98.842 2.773-.027.022-.06.031-.084.056l-1.36 1.358zm5.6-.187A4 4 0 118 4a4 4 0 010 8zM8 1c1.573 0 3.019.525 4.187 1.4l-1.357 1.358c-.025.025-.035.057-.056.084A4.979 4.979 0 008 3a4.979 4.979 0 00-2.773.842c-.021-.027-.03-.059-.055-.084L3.814 2.4A6.957 6.957 0 018 1zm0-1a8.001 8.001 0 10.003 16.002A8.001 8.001 0 008 0z" fill-rule="evenodd" diff --git a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap index 48e442ce734cf..5f5f3a2d40f95 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/TransactionActionMenu/__test__/__snapshots__/TransactionActionMenu.test.tsx.snap @@ -26,7 +26,6 @@ exports[`TransactionActionMenu component should match the snapshot 1`] = ` width="16" xmlns="http://www.w3.org/2000/svg" > - <title /> <path d="M13.069 5.157L8.384 9.768a.546.546 0 01-.768 0L2.93 5.158a.552.552 0 00-.771 0 .53.53 0 000 .759l4.684 4.61c.641.631 1.672.63 2.312 0l4.684-4.61a.53.53 0 000-.76.552.552 0 00-.771 0z" fill-rule="non-zero" diff --git a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot index c1cb45123f04b..b394fc30c8d60 100644 --- a/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot +++ b/x-pack/legacy/plugins/canvas/public/components/font_picker/__snapshots__/font_picker.stories.storyshot @@ -26,7 +26,8 @@ exports[`Storyshots components/FontPicker default 1`] = ` className="euiScreenReaderOnly" id="generated-id" > - Select an option: , is selected + Select an option: + , is selected </span> <button aria-haspopup="true" @@ -37,9 +38,7 @@ exports[`Storyshots components/FontPicker default 1`] = ` onKeyDown={[Function]} role="option" type="button" - > - - </button> + /> <div className="euiFormControlLayoutIcons euiFormControlLayoutIcons--right" > diff --git a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap index acd68622f1af0..e5e13671057bd 100644 --- a/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap +++ b/x-pack/legacy/plugins/canvas/shareable_runtime/api/__tests__/__snapshots__/shareable.test.tsx.snap @@ -9,7 +9,7 @@ exports[`Canvas Shareable Workpad API Placed successfully with default propertie </style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div><div class=\\"root\\" style=\\"height: 48px;\\"><div class=\\"root\\"><div class=\\"slideContainer\\"><div class=\\"root\\" style=\\"height: 100px; width: 150px;\\"><div class=\\"preview\\" style=\\"height: 720px; width: 1080px;\\"><div id=\\"page-7186b301-f8a7-4c65-8b89-38d68d31cfc4\\" class=\\"root\\" style=\\"height: 720px; width: 1080px; background: rgb(119, 119, 119);\\"><div class=\\"canvasPositionable canvasInteractable\\" style=\\"width: 1082px; height: 205.37748344370857px; margin-left: -541px; margin-top: -102.68874172185429px; position: absolute;\\"><div class=\\"root\\"><div class=\\"container s2042575598\\" style=\\"overflow: hidden;\\"><style type=\\"text/css\\">.s2042575598 .canvasRenderEl h1 { font-size: 150px; text-align: center; color: #d3d3d3; } -</style><div class=\\"content\\"><div class=\\"renderContainer\\"><div data-renderer=\\"markdown\\" class=\\"render\\"><div>markdown mock</div></div></div></div></div></div></div></div></div></div></div></div><div class=\\"bar\\" style=\\"bottom: 0px;\\"><div class=\\"euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem title\\"><div class=\\"euiFlexGroup euiFlexGroup--gutterSmall euiFlexGroup--alignItemsCenter euiFlexGroup--directionRow euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiLink euiLink--primary\\" href=\\"https://www.elastic.co\\" rel=\\"\\" title=\\"Powered by Elastic.co\\"><svg width=\\"32\\" height=\\"32\\" viewBox=\\"0 0 32 32\\" xmlns=\\"http://www.w3.org/2000/svg\\" class=\\"euiIcon euiIcon--large euiIcon-isLoaded\\" focusable=\\"false\\" role=\\"img\\" aria-hidden=\\"true\\"><title>
My Canvas Workpad