From 05edbdac78fae883fada5a5744fdf0f2d5402f41 Mon Sep 17 00:00:00 2001 From: patrykkopycinski Date: Tue, 4 Feb 2020 11:22:40 +0100 Subject: [PATCH 01/49] [SIEM] Add eslint-plugin-react-perf (#55960) Co-authored-by: Elastic Machine --- .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 Date: Tue, 4 Feb 2020 12:38:15 +0100 Subject: [PATCH 02/49] 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; } +type MixedLoggingConfig = LegacyLoggingConfig & Partial; + /** * 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; + 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; + + 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; */ 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; + 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) { + return logMock.mock.calls.map(([message]) => message).map(normalizePlatformLogging); +} + +export function getLegacyPlatformLogsFromMock(stdoutMock: jest.SpyInstance) { + 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 = 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 Date: Tue, 4 Feb 2020 13:00:08 +0100 Subject: [PATCH 03/49] [SIEM] Enable flow_target_select_connected unit tests (#55618) Co-authored-by: Elastic Machine --- .../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( - + + + ); - 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( - + + + ); - 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 = ({ flowTarget }) => { +export const FlowTargetSelectConnectedComponent: React.FC = ({ flowTarget }) => { const history = useHistory(); const location = useLocation(); From 661bb6b438138889f45270517cc8779fb20f2cab Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 4 Feb 2020 13:08:52 +0100 Subject: [PATCH 04/49] [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; +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; +export interface SortDirectionNumeric { + order: SortDirection; + numeric_type?: 'double' | 'long' | 'date' | 'date_nanos'; +} + +export type EsQuerySortValue = Record; export interface SearchSourceFields { type?: string; From d73e15f4147d1de9a7ee15517cbf6a90f4d4b213 Mon Sep 17 00:00:00 2001 From: Matthias Wilhelm Date: Tue, 4 Feb 2020 14:16:41 +0100 Subject: [PATCH 05/49] [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 = $( `