Upgrade io-ts and fp-ts (#44753)

* Upgrade io-ts and fp-ts

* Add io-ts/fp-ts as top level dependencies

* Merge fp-ts changes from actions plugin

* Use getOrElse instead of fold
This commit is contained in:
Dario Gieselaar 2019-09-06 22:40:51 +02:00 committed by Nathan L Smith
parent c7112d06a0
commit c01a5793ac
47 changed files with 404 additions and 213 deletions

View file

@ -157,6 +157,7 @@
"expiry-js": "0.1.7",
"file-loader": "4.2.0",
"font-awesome": "4.7.0",
"fp-ts": "^2.0.5",
"getos": "^3.1.0",
"glob": "^7.1.2",
"glob-all": "^3.1.0",
@ -173,6 +174,7 @@
"https-proxy-agent": "^2.2.2",
"inert": "^5.1.0",
"inline-style": "^2.0.0",
"io-ts": "^2.0.1",
"joi": "^13.5.2",
"jquery": "^3.4.1",
"js-yaml": "3.13.1",

View file

@ -5,9 +5,10 @@
*/
import { i18n } from '@kbn/i18n';
import { tryCatch, fromNullable } from 'fp-ts/lib/Option';
import { tryCatch, fromNullable, isSome, map, mapNullable, getOrElse } from 'fp-ts/lib/Option';
import { URL } from 'url';
import { curry } from 'lodash';
import { pipe } from 'fp-ts/lib/pipeable';
export enum WhitelistedHosts {
Any = '*',
@ -47,20 +48,24 @@ function doesValueWhitelistAnyHostname(whitelistedHostname: string): boolean {
function isWhitelisted({ whitelistedHosts }: ActionsKibanaConfig, hostname: string): boolean {
return (
Array.isArray(whitelistedHosts) &&
fromNullable(
whitelistedHosts.find(
whitelistedHostname =>
doesValueWhitelistAnyHostname(whitelistedHostname) || whitelistedHostname === hostname
isSome(
fromNullable(
whitelistedHosts.find(
whitelistedHostname =>
doesValueWhitelistAnyHostname(whitelistedHostname) || whitelistedHostname === hostname
)
)
).isSome()
)
);
}
function isWhitelistedHostnameInUri(config: ActionsKibanaConfig, uri: string): boolean {
return tryCatch(() => new URL(uri))
.map(url => url.hostname)
.mapNullable(hostname => isWhitelisted(config, hostname))
.getOrElse(false);
return pipe(
tryCatch(() => new URL(uri)),
map(url => url.hostname),
mapNullable(hostname => isWhitelisted(config, hostname)),
getOrElse(() => false)
);
}
export function getActionsConfigurationUtilities(

View file

@ -4,10 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { fromNullable, Option } from 'fp-ts/lib/Option';
import { fromNullable, Option, map, filter } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
export function getRetryAfterIntervalFromHeaders(headers: Record<string, string>): Option<number> {
return fromNullable(headers['retry-after'])
.map(retryAfter => parseInt(retryAfter, 10))
.filter(retryAfter => !isNaN(retryAfter));
return pipe(
fromNullable(headers['retry-after']),
map(retryAfter => parseInt(retryAfter, 10)),
filter(retryAfter => !isNaN(retryAfter))
);
}

View file

@ -7,6 +7,8 @@
import { i18n } from '@kbn/i18n';
import { schema, TypeOf } from '@kbn/config-schema';
import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, getOrElse } from 'fp-ts/lib/Option';
import { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header';
import {
@ -79,9 +81,11 @@ async function slackExecutor(
// special handling for rate limiting
if (status === 429) {
return getRetryAfterIntervalFromHeaders(headers)
.map(retry => retryResultSeconds(id, err.message, retry))
.getOrElse(retryResult(id, err.message));
return pipe(
getRetryAfterIntervalFromHeaders(headers),
map(retry => retryResultSeconds(id, err.message, retry)),
getOrElse(() => retryResult(id, err.message))
);
}
return errorResult(id, `${err.message} - ${statusText}`);

View file

@ -7,6 +7,8 @@ import { i18n } from '@kbn/i18n';
import { curry } from 'lodash';
import axios, { AxiosError, AxiosResponse } from 'axios';
import { schema, TypeOf } from '@kbn/config-schema';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, getOrElse } from 'fp-ts/lib/Option';
import { getRetryAfterIntervalFromHeaders } from './lib/http_rersponse_retry_header';
import { nullableType } from './lib/nullable';
import { isOk, promiseResult, Result } from './lib/result_type';
@ -124,9 +126,11 @@ export async function executor(
// special handling for rate limiting
if (status === 429) {
return getRetryAfterIntervalFromHeaders(responseHeaders)
.map(retry => retryResultSeconds(id, message, retry))
.getOrElse(retryResult(id, message));
return pipe(
getRetryAfterIntervalFromHeaders(responseHeaders),
map(retry => retryResultSeconds(id, message, retry)),
getOrElse(() => retryResult(id, message))
);
}
return errorResultInvalid(id, message);
}

View file

@ -5,12 +5,13 @@
*/
import { dateAsStringRt } from './index';
import { isLeft, isRight } from 'fp-ts/lib/Either';
describe('dateAsStringRt', () => {
it('validates whether a string is a valid date', () => {
expect(dateAsStringRt.decode(1566299881499).isLeft()).toBe(true);
expect(isLeft(dateAsStringRt.decode(1566299881499))).toBe(true);
expect(dateAsStringRt.decode('2019-08-20T11:18:31.407Z').isRight()).toBe(
expect(isRight(dateAsStringRt.decode('2019-08-20T11:18:31.407Z'))).toBe(
true
);
});
@ -18,6 +19,10 @@ describe('dateAsStringRt', () => {
it('returns the string it was given', () => {
const either = dateAsStringRt.decode('2019-08-20T11:18:31.407Z');
expect(either.value).toBe('2019-08-20T11:18:31.407Z');
if (isRight(either)) {
expect(either.right).toBe('2019-08-20T11:18:31.407Z');
} else {
fail();
}
});
});

View file

@ -6,20 +6,37 @@
import * as t from 'io-ts';
import { jsonRt } from './index';
import { isRight, Either, isLeft, fold } from 'fp-ts/lib/Either';
import { Right } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
function getValueOrThrow<TEither extends Either<any, any>>(
either: TEither
): Right<TEither> {
const value = pipe(
either,
fold(() => {
throw new Error('cannot get right value of left');
}, identity)
);
return value as Right<TEither>;
}
describe('jsonRt', () => {
it('validates json', () => {
expect(jsonRt.decode('{}').isRight()).toBe(true);
expect(jsonRt.decode('[]').isRight()).toBe(true);
expect(jsonRt.decode('true').isRight()).toBe(true);
expect(jsonRt.decode({}).isLeft()).toBe(true);
expect(jsonRt.decode('foo').isLeft()).toBe(true);
expect(isRight(jsonRt.decode('{}'))).toBe(true);
expect(isRight(jsonRt.decode('[]'))).toBe(true);
expect(isRight(jsonRt.decode('true'))).toBe(true);
expect(isLeft(jsonRt.decode({}))).toBe(true);
expect(isLeft(jsonRt.decode('foo'))).toBe(true);
});
it('returns parsed json when decoding', () => {
expect(jsonRt.decode('{}').value).toEqual({});
expect(jsonRt.decode('[]').value).toEqual([]);
expect(jsonRt.decode('true').value).toEqual(true);
expect(getValueOrThrow(jsonRt.decode('{}'))).toEqual({});
expect(getValueOrThrow(jsonRt.decode('[]'))).toEqual([]);
expect(getValueOrThrow(jsonRt.decode('true'))).toEqual(true);
});
it('is pipable', () => {
@ -31,10 +48,10 @@ describe('jsonRt', () => {
const valid = piped.decode(JSON.stringify(validInput));
const invalid = piped.decode(JSON.stringify(invalidInput));
expect(valid.isRight()).toBe(true);
expect(valid.value).toEqual(validInput);
expect(isRight(valid)).toBe(true);
expect(getValueOrThrow(valid)).toEqual(validInput);
expect(invalid.isLeft()).toBe(true);
expect(isLeft(invalid)).toBe(true);
});
it('returns strings when encoding', () => {

View file

@ -4,28 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { transactionSampleRateRt } from './index';
import { isRight } from 'fp-ts/lib/Either';
describe('transactionSampleRateRt', () => {
it('accepts both strings and numbers as values', () => {
expect(transactionSampleRateRt.decode('0.5').isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.5).isRight()).toBe(true);
expect(isRight(transactionSampleRateRt.decode('0.5'))).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0.5))).toBe(true);
});
it('checks if the number falls within 0, 1', () => {
expect(transactionSampleRateRt.decode(0).isRight()).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0))).toBe(true);
expect(transactionSampleRateRt.decode(0.5).isRight()).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0.5))).toBe(true);
expect(transactionSampleRateRt.decode(-0.1).isRight()).toBe(false);
expect(transactionSampleRateRt.decode(1.1).isRight()).toBe(false);
expect(isRight(transactionSampleRateRt.decode(-0.1))).toBe(false);
expect(isRight(transactionSampleRateRt.decode(1.1))).toBe(false);
expect(transactionSampleRateRt.decode(NaN).isRight()).toBe(false);
expect(isRight(transactionSampleRateRt.decode(NaN))).toBe(false);
});
it('checks whether the number of decimals is 3', () => {
expect(transactionSampleRateRt.decode(1).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.99).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.999).isRight()).toBe(true);
expect(transactionSampleRateRt.decode(0.998).isRight()).toBe(true);
expect(isRight(transactionSampleRateRt.decode(1))).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0.99))).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0.999))).toBe(true);
expect(isRight(transactionSampleRateRt.decode(0.998))).toBe(true);
});
});

View file

@ -22,6 +22,7 @@ import React, { useState } from 'react';
import { toastNotifications } from 'ui/notify';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { isRight } from 'fp-ts/lib/Either';
import { transactionSampleRateRt } from '../../../../../common/runtime_types/transaction_sample_rate_rt';
import { AddSettingFlyoutBody } from './AddSettingFlyoutBody';
import { Config } from '../SettingsList';
@ -79,9 +80,7 @@ export function AddSettingsFlyout({
{ preservePreviousData: false }
);
const isSampleRateValid = transactionSampleRateRt
.decode(sampleRate)
.isRight();
const isSampleRateValid = isRight(transactionSampleRateRt.decode(sampleRate));
const isSelectedEnvironmentValid = environments.some(
env =>

View file

@ -221,7 +221,9 @@ describe('createApi', () => {
});
it('validates query parameters', () => {
const { handler, route } = initApi({ query: t.type({ bar: t.string }) });
const { handler, route } = initApi({
query: t.type({ bar: t.string })
});
expect(() =>
route.handler({

View file

@ -3,12 +3,13 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { merge, pick, omit } from 'lodash';
import { merge, pick, omit, difference } from 'lodash';
import Boom from 'boom';
import { InternalCoreSetup } from 'src/core/server';
import { Request, ResponseToolkit } from 'hapi';
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { isLeft } from 'fp-ts/lib/Either';
import {
ServerAPI,
RouteFactoryFn,
@ -18,6 +19,8 @@ import {
} from '../typings';
import { jsonRt } from '../../../common/runtime_types/json_rt';
const debugRt = t.partial({ _debug: jsonRt.pipe(t.boolean) });
export function createApi() {
const factoryFns: Array<RouteFactoryFn<any, any, any, any>> = [];
const api: ServerAPI<{}> = {
@ -36,21 +39,16 @@ export function createApi() {
any
>;
const bodyRt = params.body;
const fallbackBodyRt = bodyRt || t.null;
const rts = {
// add _debug query parameter to all routes
query: params.query
? t.exact(
t.intersection([
params.query,
t.partial({ _debug: jsonRt.pipe(t.boolean) })
])
)
: t.union([
t.strict({}),
t.strict({ _debug: jsonRt.pipe(t.boolean) })
]),
path: params.path || t.strict({}),
body: params.body || t.null
? t.exact(t.intersection([params.query, debugRt]))
: t.exact(debugRt),
path: params.path ? t.exact(params.path) : t.strict({}),
body: bodyRt && 'props' in bodyRt ? t.exact(bodyRt) : fallbackBodyRt
};
server.route(
@ -74,25 +72,31 @@ export function createApi() {
keyof typeof rts
>).reduce(
(acc, key) => {
let codec = rts[key];
const codec = rts[key];
const value = paramMap[key];
// Use exact props where possible (only possible for types with props)
if ('props' in codec) {
codec = t.exact(codec);
}
const result = codec.decode(value);
if (result.isLeft()) {
if (isLeft(result)) {
throw Boom.badRequest(PathReporter.report(result)[0]);
}
const strippedKeys = difference(
Object.keys(value || {}),
Object.keys(result.right || {})
);
if (strippedKeys.length) {
throw Boom.badRequest(
`Unknown keys specified: ${strippedKeys}`
);
}
// hide _debug from route handlers
const parsedValue =
key === 'query'
? omit(result.value, '_debug')
: result.value;
? omit(result.right, '_debug')
: result.right;
return {
...acc,

View file

@ -13,7 +13,7 @@ import { PickByValue, Optional } from 'utility-types';
export interface Params {
query?: t.HasProps;
path?: t.HasProps;
body?: t.Any;
body?: t.Any | t.HasProps;
}
type DecodeParams<TParams extends Params | undefined> = {

View file

@ -9,7 +9,10 @@ import { omit } from 'lodash';
import { setupRequest, Setup } from '../lib/helpers/setup_request';
import { getEnvironments } from '../lib/ui_filters/get_environments';
import { Projection } from '../../common/projections/typings';
import { localUIFilterNames } from '../lib/ui_filters/local_ui_filters/config';
import {
localUIFilterNames,
LocalUIFilterName
} from '../lib/ui_filters/local_ui_filters/config';
import { getUiFiltersES } from '../lib/helpers/convert_ui_filters/get_ui_filters_es';
import { getLocalUIFilters } from '../lib/ui_filters/local_ui_filters';
import { getServicesProjection } from '../../common/projections/services';
@ -40,7 +43,11 @@ export const uiFiltersEnvironmentsRoute = createRoute(() => ({
const filterNamesRt = t.type({
filterNames: jsonRt.pipe(
t.array(t.union(localUIFilterNames.map(name => t.literal(name))))
t.array(
t.keyof(Object.fromEntries(
localUIFilterNames.map(filterName => [filterName, null])
) as Record<LocalUIFilterName, null>)
)
)
});

View file

@ -5,6 +5,7 @@
*/
import * as t from 'io-ts';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { isLeft } from 'fp-ts/lib/Either';
import { configBlockSchemas } from './config_schemas';
import { ConfigurationBlock, createConfigurationBlockInterface } from './domain_types';
@ -29,7 +30,9 @@ export const validateConfigurationBlocks = (configurationBlocks: ConfigurationBl
const interfaceConfig = blockSchema.configs.reduce(
(props, config) => {
if (config.options) {
props[config.id] = t.union(config.options.map(opt => t.literal(opt.value)));
props[config.id] = t.keyof(Object.fromEntries(
config.options.map(opt => [opt.value, null])
) as Record<string, null>);
} else if (config.validation) {
props[config.id] = validationMap[config.validation];
}
@ -46,7 +49,7 @@ export const validateConfigurationBlocks = (configurationBlocks: ConfigurationBl
const validationResults = runtimeInterface.decode(block);
if (validationResults.isLeft()) {
if (isLeft(validationResults)) {
throw new Error(
`configuration_blocks validation error, configuration_blocks at index ${index} is invalid. ${
PathReporter.report(validationResults)[0]

View file

@ -13,9 +13,9 @@ export const OutputTypesArray = ['elasticsearch', 'logstash', 'kafka', 'redis'];
// We can also pass in optional params to create spacific runtime checks that
// can be used to validate blocs on the API and UI
export const createConfigurationBlockInterface = (
configType: t.LiteralType<string> | t.UnionType<Array<t.LiteralType<string>>> = t.union(
configBlockSchemas.map(s => t.literal(s.id))
),
configType: t.LiteralType<string> | t.KeyofC<Record<string, null>> = t.keyof(Object.fromEntries(
configBlockSchemas.map(s => [s.id, null])
) as Record<string, null>),
beatConfigInterface: t.Mixed = t.Dictionary
) =>
t.interface(

View file

@ -5,6 +5,7 @@
*/
import * as t from 'io-ts';
import { isRight } from 'fp-ts/lib/Either';
export class DateFromStringType extends t.Type<Date, string, t.mixed> {
// eslint-disable-next-line
@ -15,10 +16,10 @@ export class DateFromStringType extends t.Type<Date, string, t.mixed> {
(u): u is Date => u instanceof Date,
(u, c) => {
const validation = t.string.validate(u, c);
if (validation.isLeft()) {
if (!isRight(validation)) {
return validation as any;
} else {
const s = validation.value;
const s = validation.right;
const d = new Date(s);
return isNaN(d.getTime()) ? t.failure(s, c) : t.success(d);
}

View file

@ -39,7 +39,7 @@ export interface FrameworkAdapter {
export const RuntimeFrameworkInfo = t.type({
basePath: t.string,
license: t.type({
type: t.union(LICENSES.map(s => t.literal(s))),
type: t.keyof(Object.fromEntries(LICENSES.map(s => [s, null])) as Record<string, null>),
expired: t.boolean,
expiry_date_in_millis: t.number,
}),

View file

@ -10,6 +10,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { UIRoutes } from 'ui/routes';
import { isLeft } from 'fp-ts/lib/Either';
import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types';
import {
FrameworkAdapter,
@ -87,7 +88,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
}
const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked);
if (assertData.isLeft()) {
if (isLeft(assertData)) {
throw new Error(
`Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}`
);
@ -98,7 +99,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise;
const assertUser = RuntimeFrameworkUser.decode(this.shieldUser);
if (assertUser.isLeft()) {
if (isLeft(assertUser)) {
throw new Error(
`Error parsing user info in ${this.PLUGIN_ID}, ${PathReporter.report(assertUser)[0]}`
);

View file

@ -75,9 +75,14 @@ export const RuntimeFrameworkInfo = t.interface(
version: t.string,
}),
license: t.type({
type: t.union(
['oss', 'trial', 'standard', 'basic', 'gold', 'platinum'].map(s => t.literal(s))
),
type: t.keyof({
oss: null,
trial: null,
standard: null,
basic: null,
gold: null,
platinum: null,
}),
expired: t.boolean,
expiry_date_in_millis: t.number,
}),

View file

@ -7,6 +7,7 @@
import { ResponseToolkit } from 'hapi';
import { PathReporter } from 'io-ts/lib/PathReporter';
import { get } from 'lodash';
import { isLeft } from 'fp-ts/lib/Either';
// @ts-ignore
import { mirrorPluginStatus } from '../../../../../../server/lib/mirror_plugin_status';
import {
@ -137,7 +138,7 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter {
return null;
}
const assertKibanaUser = RuntimeKibanaUser.decode(user);
if (assertKibanaUser.isLeft()) {
if (isLeft(assertKibanaUser)) {
throw new Error(
`Error parsing user info in ${this.PLUGIN_ID}, ${
PathReporter.report(assertKibanaUser)[0]
@ -186,7 +187,7 @@ export class KibanaBackendFrameworkAdapter implements BackendFrameworkAdapter {
}
const assertData = RuntimeFrameworkInfo.decode(xpackInfoUnpacked);
if (assertData.isLeft()) {
if (isLeft(assertData)) {
throw new Error(
`Error parsing xpack info in ${this.PLUGIN_ID}, ${PathReporter.report(assertData)[0]}`
);

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { PathReporter } from 'io-ts/lib/PathReporter';
import { isLeft } from 'fp-ts/lib/Either';
import { BeatEvent, RuntimeBeatEvent } from '../../common/domain_types';
import { BeatEventsAdapter } from './adapters/events/adapter_types';
import { FrameworkUser } from './adapters/framework/adapter_types';
@ -20,7 +21,7 @@ export class BeatEventsLib {
): Promise<Array<{ success: boolean; reason?: string }>> => {
return events.map((event, i) => {
const assertData = RuntimeBeatEvent.decode(event);
if (assertData.isLeft()) {
if (isLeft(assertData)) {
if (events.length - 1 === i) {
this.beats
.update(user, beatId, {

View file

@ -11,6 +11,7 @@ import { PathReporter } from 'io-ts/lib/PathReporter';
* you may not use this file except in compliance with the Elastic License.
*/
import Joi from 'joi';
import { isLeft } from 'fp-ts/lib/Either';
import { REQUIRED_LICENSES } from '../../../common/constants';
import {
ConfigurationBlock,
@ -35,7 +36,7 @@ export const upsertConfigurationRoute = (libs: CMServerLibs) => ({
const result = await Promise.all<any>(
request.payload.map(async (block: ConfigurationBlock) => {
const assertData = createConfigurationBlockInterface().decode(block);
if (assertData.isLeft()) {
if (isLeft(assertData)) {
return {
error: `Error parsing block info, ${PathReporter.report(assertData)[0]}`,
};

View file

@ -6,8 +6,11 @@
import * as rt from 'io-ts';
import { kfetch } from 'ui/kfetch';
import { getJobId } from '../../../../../common/log_analysis';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import { getJobId } from '../../../../../common/log_analysis';
export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => {
const response = await kfetch({
@ -19,7 +22,10 @@ export const callJobsSummaryAPI = async (spaceId: string, sourceId: string) => {
})
),
});
return fetchJobStatusResponsePayloadRT.decode(response).getOrElseL(throwErrors(createPlainError));
return pipe(
fetchJobStatusResponsePayloadRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};
export const fetchJobStatusRequestPayloadRT = rt.type({

View file

@ -7,8 +7,11 @@
import * as rt from 'io-ts';
import { kfetch } from 'ui/kfetch';
import { getJobIdPrefix } from '../../../../../common/log_analysis';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { throwErrors, createPlainError } from '../../../../../common/runtime_types';
import { getJobIdPrefix } from '../../../../../common/log_analysis';
const MODULE_ID = 'logs_ui_analysis';
@ -56,7 +59,10 @@ export const callSetupMlModuleAPI = async (
),
});
return setupMlModuleResponsePayloadRT.decode(response).getOrElseL(throwErrors(createPlainError));
return pipe(
setupMlModuleResponsePayloadRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};
const setupMlModuleTimeParamsRT = rt.partial({

View file

@ -8,6 +8,9 @@ import createContainer from 'constate-latest';
import { useMemo, useState, useEffect } from 'react';
import { kfetch } from 'ui/kfetch';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { useTrackedPromise } from '../../../utils/use_tracked_promise';
import {
getMlCapabilitiesResponsePayloadRT,
@ -29,9 +32,10 @@ export const useLogAnalysisCapabilities = () => {
pathname: '/api/ml/ml_capabilities',
});
return getMlCapabilitiesResponsePayloadRT
.decode(rawResponse)
.getOrElseL(throwErrors(createPlainError));
return pipe(
getMlCapabilitiesResponsePayloadRT.decode(rawResponse),
fold(throwErrors(createPlainError), identity)
);
},
onResolve: response => {
setMlCapabilities(response);

View file

@ -6,6 +6,9 @@
import { useEffect } from 'react';
import * as rt from 'io-ts';
import { identity, constant } from 'fp-ts/lib/function';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { useUrlState } from '../../../utils/use_url_state';
const autoRefreshRT = rt.union([
@ -33,7 +36,11 @@ export const useLogAnalysisResultsUrlState = () => {
startTime: 'now-2w',
endTime: 'now',
},
decodeUrlState: (value: unknown) => urlTimeRangeRT.decode(value).getOrElse(undefined),
decodeUrlState: (value: unknown) =>
pipe(
urlTimeRangeRT.decode(value),
fold(constant(undefined), identity)
),
encodeUrlState: urlTimeRangeRT.encode,
urlStateKey: TIME_RANGE_URL_STATE_KEY,
});
@ -47,7 +54,11 @@ export const useLogAnalysisResultsUrlState = () => {
isPaused: false,
interval: 30000,
},
decodeUrlState: (value: unknown) => autoRefreshRT.decode(value).getOrElse(undefined),
decodeUrlState: (value: unknown) =>
pipe(
autoRefreshRT.decode(value),
fold(constant(undefined), identity)
),
encodeUrlState: autoRefreshRT.encode,
urlStateKey: AUTOREFRESH_URL_STATE_KEY,
});

View file

@ -7,6 +7,9 @@
import { useMemo, useState } from 'react';
import { kfetch } from 'ui/kfetch';
import { fold } from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import {
getLogEntryRateRequestPayloadRT,
getLogEntryRateSuccessReponsePayloadRT,
@ -53,9 +56,10 @@ export const useLogEntryRate = ({
});
},
onResolve: response => {
const { data } = getLogEntryRateSuccessReponsePayloadRT
.decode(response)
.getOrElseL(throwErrors(createPlainError));
const { data } = pipe(
getLogEntryRateSuccessReponsePayloadRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
setLogEntryRate(data);
},

View file

@ -5,6 +5,9 @@
*/
import { useEffect } from 'react';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { InfraNodeType } from '../../graphql/types';
import { InfraMetricLayout } from '../../pages/metrics/layouts/types';
import { InfraMetadata, InfraMetadataRT } from '../../../common/http_api/metadata_api';
@ -19,7 +22,10 @@ export function useMetadata(
sourceId: string
) {
const decodeResponse = (response: any) => {
return InfraMetadataRT.decode(response).getOrElseL(throwErrors(createPlainError));
return pipe(
InfraMetadataRT.decode(response),
fold(throwErrors(createPlainError), identity)
);
};
const { error, loading, response, makeRequest } = useHTTPRequest<InfraMetadata>(

View file

@ -10,6 +10,7 @@ import { isNumber } from 'lodash';
import moment from 'moment';
import dateMath from '@elastic/datemath';
import * as rt from 'io-ts';
import { isRight } from 'fp-ts/lib/Either';
import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state';
import { InfraTimerangeInput } from '../../graphql/types';
@ -159,12 +160,13 @@ const MetricsTimeRT = rt.type({
const mapToTimeUrlState = (value: any) => {
const result = MetricsTimeRT.decode(value);
if (result.isRight()) {
const to = isNumber(result.value.to) ? moment(result.value.to).toISOString() : result.value.to;
const from = isNumber(result.value.from)
? moment(result.value.from).toISOString()
: result.value.from;
return { ...result.value, from, to };
if (isRight(result)) {
const resultValue = result.right;
const to = isNumber(resultValue.to) ? moment(resultValue.to).toISOString() : resultValue.to;
const from = isNumber(resultValue.from)
? moment(resultValue.from).toISOString()
: resultValue.from;
return { ...resultValue, from, to };
}
return undefined;
};

View file

@ -79,7 +79,10 @@ function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOption
const MetricOptional = t.partial({
field: t.string,
rate: t.boolean,
color: t.union(values(MetricsExplorerColor).map(c => t.literal(c as string))),
color: t.keyof(Object.fromEntries(values(MetricsExplorerColor).map(c => [c, null])) as Record<
string,
null
>),
label: t.string,
});
@ -110,8 +113,12 @@ function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOption
function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerChartOptions {
const ChartOptions = t.type({
yAxisMode: t.union(values(MetricsExplorerYAxisMode).map(v => t.literal(v as string))),
type: t.union(values(MetricsExplorerChartType).map(v => t.literal(v as string))),
yAxisMode: t.keyof(Object.fromEntries(
values(MetricsExplorerYAxisMode).map(v => [v, null])
) as Record<string, null>),
type: t.keyof(Object.fromEntries(
values(MetricsExplorerChartType).map(v => [v, null])
) as Record<string, null>),
stack: t.boolean,
});
const result = ChartOptions.decode(subject);

View file

@ -6,6 +6,9 @@
import * as runtimeTypes from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { constant, identity } from 'fp-ts/lib/function';
import { useUrlState, replaceStateKeyInQueryString } from '../../utils/use_url_state';
const SOURCE_ID_URL_STATE_KEY = 'sourceId';
@ -25,4 +28,7 @@ export const replaceSourceIdInQueryString = (sourceId: string) =>
const sourceIdRuntimeType = runtimeTypes.union([runtimeTypes.string, runtimeTypes.undefined]);
const encodeSourceIdUrlState = sourceIdRuntimeType.encode;
const decodeSourceIdUrlState = (value: unknown) =>
sourceIdRuntimeType.decode(value).getOrElse(undefined);
pipe(
sourceIdRuntimeType.decode(value),
fold(constant(undefined), identity)
);

View file

@ -6,6 +6,9 @@
import { failure } from 'io-ts/lib/PathReporter';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import {
InfraLogEntryColumn,
InfraLogEntryFieldColumn,
@ -182,11 +185,12 @@ export const createLogEntriesResolvers = (libs: {
}));
},
async logItem(source, args, { req }) {
const sourceConfiguration = SourceConfigurationRuntimeType.decode(
source.configuration
).getOrElseL(errors => {
throw new Error(failure(errors).join('\n'));
});
const sourceConfiguration = pipe(
SourceConfigurationRuntimeType.decode(source.configuration),
fold(errors => {
throw new Error(failure(errors).join('\n'));
}, identity)
);
return await libs.logEntries.getLogItem(req, args.id, sourceConfiguration);
},

View file

@ -6,6 +6,9 @@
import { failure } from 'io-ts/lib/PathReporter';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'rxjs';
import { InfraSourceResolvers } from '../../graphql/types';
import { InfraMetricsDomain } from '../../lib/domains/metrics_domain';
import { SourceConfigurationRuntimeType } from '../../lib/sources';
@ -31,11 +34,12 @@ export const createMetricResolvers = (
} => ({
InfraSource: {
async metrics(source, args, { req }) {
const sourceConfiguration = SourceConfigurationRuntimeType.decode(
source.configuration
).getOrElseL(errors => {
throw new Error(failure(errors).join('\n'));
});
const sourceConfiguration = pipe(
SourceConfigurationRuntimeType.decode(source.configuration),
fold(errors => {
throw new Error(failure(errors).join('\n'));
}, identity)
);
UsageCollector.countNode(args.nodeType);
const options = {

View file

@ -7,6 +7,9 @@
import { UserInputError } from 'apollo-server-errors';
import { failure } from 'io-ts/lib/PathReporter';
import { identity } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import {
InfraSourceLogColumn,
InfraSourceResolvers,
@ -184,8 +187,11 @@ const compactObject = <T>(obj: T): CompactObject<T> =>
const decodeLogColumns = (logColumns?: UpdateSourceLogColumnInput[] | null) =>
logColumns
? logColumns.map(logColumn =>
SavedSourceConfigurationColumnRuntimeType.decode(logColumn).getOrElseL(errors => {
throw new UserInputError(failure(errors).join('\n'));
})
pipe(
SavedSourceConfigurationColumnRuntimeType.decode(logColumn),
fold(errors => {
throw new UserInputError(failure(errors).join('\n'));
}, identity)
)
)
: undefined;

View file

@ -12,7 +12,9 @@ import first from 'lodash/fp/first';
import get from 'lodash/fp/get';
import has from 'lodash/fp/has';
import zip from 'lodash/fp/zip';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity, constant } from 'fp-ts/lib/function';
import { compareTimeKeys, isTimeKey, TimeKey } from '../../../../common/time';
import { JsonObject } from '../../../../common/typed_json';
import {
@ -165,13 +167,15 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
const response = await this.framework.callWithRequest<any, {}>(request, 'search', query);
return LogSummaryResponseRuntimeType.decode(response)
.map(logSummaryResponse =>
return pipe(
LogSummaryResponseRuntimeType.decode(response),
map(logSummaryResponse =>
logSummaryResponse.aggregations.count_by_date.buckets.map(
convertDateRangeBucketToSummaryBucket
)
)
.getOrElse([]);
),
fold(constant([]), identity)
);
}
public async getLogItem(

View file

@ -6,6 +6,9 @@
import * as rt from 'io-ts';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { getJobId } from '../../../common/log_analysis';
import { throwErrors, createPlainError } from '../../../common/runtime_types';
import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../adapters/framework';
@ -132,10 +135,11 @@ export class InfraLogAnalysis {
);
}
const mlModelPlotBuckets = logRateModelPlotResponseRT
.decode(mlModelPlotResponse)
.map(response => response.aggregations.timestamp_buckets.buckets)
.getOrElseL(throwErrors(createPlainError));
const mlModelPlotBuckets = pipe(
logRateModelPlotResponseRT.decode(mlModelPlotResponse),
map(response => response.aggregations.timestamp_buckets.buckets),
fold(throwErrors(createPlainError), identity)
);
return mlModelPlotBuckets.map(bucket => ({
anomalies: bucket.filter_records.top_hits_record.hits.hits.map(({ _source: record }) => ({

View file

@ -8,6 +8,9 @@ import * as runtimeTypes from 'io-ts';
import { failure } from 'io-ts/lib/PathReporter';
import { Legacy } from 'kibana';
import { identity, constant } from 'fp-ts/lib/function';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { Pick3 } from '../../../common/utility_types';
import { InfraConfigurationAdapter } from '../adapters/configuration';
import { InfraFrameworkRequest, internalInfraFrameworkRequest } from '../adapters/framework';
@ -182,15 +185,17 @@ export class InfraSources {
private async getStaticDefaultSourceConfiguration() {
const staticConfiguration = await this.libs.configuration.get();
const staticSourceConfiguration = runtimeTypes
.type({
sources: runtimeTypes.type({
default: StaticSourceConfigurationRuntimeType,
}),
})
.decode(staticConfiguration)
.map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration)
.getOrElse({});
const staticSourceConfiguration = pipe(
runtimeTypes
.type({
sources: runtimeTypes.type({
default: StaticSourceConfigurationRuntimeType,
}),
})
.decode(staticConfiguration),
map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration),
fold(constant({}), identity)
);
return mergeSourceConfiguration(defaultSourceConfiguration, staticSourceConfiguration);
}
@ -238,14 +243,16 @@ const mergeSourceConfiguration = (
);
const convertSavedObjectToSavedSourceConfiguration = (savedObject: unknown) =>
SourceConfigurationSavedObjectRuntimeType.decode(savedObject)
.map(savedSourceConfiguration => ({
pipe(
SourceConfigurationSavedObjectRuntimeType.decode(savedObject),
map(savedSourceConfiguration => ({
id: savedSourceConfiguration.id,
version: savedSourceConfiguration.version,
updatedAt: savedSourceConfiguration.updated_at,
origin: 'stored' as 'stored',
configuration: savedSourceConfiguration.attributes,
}))
.getOrElseL(errors => {
})),
fold(errors => {
throw new Error(failure(errors).join('\n'));
});
}, identity)
);

View file

@ -8,17 +8,22 @@
import * as runtimeTypes from 'io-ts';
import moment from 'moment';
import { pipe } from 'fp-ts/lib/pipeable';
import { chain } from 'fp-ts/lib/Either';
export const TimestampFromString = new runtimeTypes.Type<number, string>(
'TimestampFromString',
(input): input is number => typeof input === 'number',
(input, context) =>
runtimeTypes.string.validate(input, context).chain(stringInput => {
const momentValue = moment(stringInput);
return momentValue.isValid()
? runtimeTypes.success(momentValue.valueOf())
: runtimeTypes.failure(stringInput, context);
}),
pipe(
runtimeTypes.string.validate(input, context),
chain(stringInput => {
const momentValue = moment(stringInput);
return momentValue.isValid()
? runtimeTypes.success(momentValue.valueOf())
: runtimeTypes.failure(stringInput, context);
})
),
output => new Date(output).toISOString()
);

View file

@ -6,6 +6,9 @@
import Boom from 'boom';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { InfraBackendLibs } from '../../../lib/infra_types';
import {
LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
@ -23,9 +26,10 @@ export const initLogAnalysisGetLogEntryRateRoute = ({
method: 'POST',
path: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
handler: async (req, res) => {
const payload = getLogEntryRateRequestPayloadRT
.decode(req.payload)
.getOrElseL(throwErrors(Boom.badRequest));
const payload = pipe(
getLogEntryRateRequestPayloadRT.decode(req.payload),
fold(throwErrors(Boom.badRequest), identity)
);
const logEntryRateBuckets = await logAnalysis
.getLogEntryRateBuckets(

View file

@ -6,6 +6,9 @@
import Boom, { boomify } from 'boom';
import { get } from 'lodash';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import {
InfraMetadata,
InfraMetadataWrappedRequest,
@ -29,9 +32,10 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
path: '/api/infra/metadata',
handler: async req => {
try {
const { nodeId, nodeType, sourceId } = InfraMetadataRequestRT.decode(
req.payload
).getOrElseL(throwErrors(Boom.badRequest));
const { nodeId, nodeType, sourceId } = pipe(
InfraMetadataRequestRT.decode(req.payload),
fold(throwErrors(Boom.badRequest), identity)
);
const { configuration } = await libs.sources.getSourceConfiguration(req, sourceId);
const metricsMetadata = await getMetricMetadata(
@ -60,12 +64,15 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
const id = metricsMetadata.id;
const name = metricsMetadata.name || id;
return InfraMetadataRT.decode({
id,
name,
features: [...metricFeatures, ...cloudMetricsFeatures, ...apmMetricFeatures],
info,
}).getOrElseL(throwErrors(Boom.badImplementation));
return pipe(
InfraMetadataRT.decode({
id,
name,
features: [...metricFeatures, ...cloudMetricsFeatures, ...apmMetricFeatures],
info,
}),
fold(throwErrors(Boom.badImplementation), identity)
);
} catch (error) {
throw boomify(error);
}

View file

@ -12,6 +12,9 @@ import uuid from 'uuid';
import { SavedObjectsFindOptions } from 'src/core/server';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { Pick3 } from '../../../common/utility_types';
import {
PageInfoNote,
@ -215,16 +218,18 @@ const convertSavedObjectToSavedNote = (
savedObject: unknown,
timelineVersion?: string | undefined | null
): NoteSavedObject =>
NoteSavedObjectRuntimeType.decode(savedObject)
.map(savedNote => ({
pipe(
NoteSavedObjectRuntimeType.decode(savedObject),
map(savedNote => ({
noteId: savedNote.id,
version: savedNote.version,
timelineVersion,
...savedNote.attributes,
}))
.getOrElseL(errors => {
})),
fold(errors => {
throw new Error(failure(errors).join('\n'));
});
}, identity)
);
// we have to use any here because the SavedObjectAttributes interface is like below
// export interface SavedObjectAttributes {

View file

@ -11,6 +11,9 @@ import { getOr } from 'lodash/fp';
import { SavedObjectsFindOptions } from 'src/core/server';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { Pick3 } from '../../../common/utility_types';
import { FrameworkRequest, internalFrameworkRequest } from '../framework';
import {
@ -203,16 +206,18 @@ const convertSavedObjectToSavedPinnedEvent = (
savedObject: unknown,
timelineVersion?: string | undefined | null
): PinnedEventSavedObject =>
PinnedEventSavedObjectRuntimeType.decode(savedObject)
.map(savedPinnedEvent => ({
pipe(
PinnedEventSavedObjectRuntimeType.decode(savedObject),
map(savedPinnedEvent => ({
pinnedEventId: savedPinnedEvent.id,
version: savedPinnedEvent.version,
timelineVersion,
...savedPinnedEvent.attributes,
}))
.getOrElseL(errors => {
})),
fold(errors => {
throw new Error(failure(errors).join('\n'));
});
}, identity)
);
// we have to use any here because the SavedObjectAttributes interface is like below
// export interface SavedObjectAttributes {

View file

@ -5,16 +5,23 @@
*/
import { failure } from 'io-ts/lib/PathReporter';
import { pipe } from 'fp-ts/lib/pipeable';
import { map, fold } from 'fp-ts/lib/Either';
import { identity } from 'fp-ts/lib/function';
import { TimelineSavedObjectRuntimeType, TimelineSavedObject } from './types';
export const convertSavedObjectToSavedTimeline = (savedObject: unknown): TimelineSavedObject => {
return TimelineSavedObjectRuntimeType.decode(savedObject)
.map(savedTimeline => ({
const timeline = pipe(
TimelineSavedObjectRuntimeType.decode(savedObject),
map(savedTimeline => ({
savedObjectId: savedTimeline.id,
version: savedTimeline.version,
...savedTimeline.attributes,
}))
.getOrElseL(errors => {
})),
fold(errors => {
throw new Error(failure(errors).join('\n'));
});
}, identity)
);
return timeline;
};

View file

@ -261,7 +261,6 @@
"immer": "^1.5.0",
"inline-style": "^2.0.0",
"intl": "^1.2.5",
"io-ts": "^1.4.2",
"isbinaryfile": "4.0.2",
"isomorphic-git": "0.55.5",
"joi": "^13.5.2",

View file

@ -6,7 +6,9 @@
import expect from '@kbn/expect';
import Joi from 'joi';
import Hapi, { Util } from 'hapi';
import { fromNullable } from 'fp-ts/lib/Option';
import { fromNullable, map, filter, getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
import { constant } from 'fp-ts/lib/function';
interface WebhookRequest extends Hapi.Request {
payload: string;
@ -19,16 +21,18 @@ export async function initPlugin(server: Hapi.Server, path: string) {
) {
const scheme = {
async authenticate(request: Hapi.Request, h: Hapi.ResponseToolkit) {
const credentials = fromNullable(request.headers.authorization)
.map(authorization => authorization.split(/\s+/))
.filter(parts => parts.length > 1)
.map(parts => Buffer.from(parts[1], 'base64').toString())
.filter(credentialsPart => credentialsPart.indexOf(':') !== -1)
.map(credentialsPart => {
const credentials = pipe(
fromNullable(request.headers.authorization),
map(authorization => authorization.split(/\s+/)),
filter(parts => parts.length > 1),
map(parts => Buffer.from(parts[1], 'base64').toString()),
filter(credentialsPart => credentialsPart.indexOf(':') !== -1),
map(credentialsPart => {
const [username, password] = credentialsPart.split(':');
return { username, password };
})
.getOrElse({ username: '', password: '' });
}),
getOrElse(constant({ username: '', password: '' }))
);
return h.authenticated({ credentials });
},

View file

@ -6,6 +6,9 @@
import expect from '@kbn/expect';
import { pipe } from 'fp-ts/lib/pipeable';
import { identity } from 'fp-ts/lib/function';
import { fold } from 'fp-ts/lib/Either';
import {
LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
getLogEntryRateRequestPayloadRT,
@ -55,9 +58,10 @@ export default ({ getService }: FtrProviderContext) => {
)
.expect(200);
const logEntryRateBuckets = getLogEntryRateSuccessReponsePayloadRT
.decode(body)
.getOrElseL(throwErrors(createPlainError));
const logEntryRateBuckets = pipe(
getLogEntryRateSuccessReponsePayloadRT.decode(body),
fold(throwErrors(createPlainError), identity)
);
expect(logEntryRateBuckets.data.bucketDuration).to.be(15 * 60 * 1000);
expect(logEntryRateBuckets.data.histogramBuckets).to.not.be.empty();
@ -84,9 +88,10 @@ export default ({ getService }: FtrProviderContext) => {
)
.expect(200);
const logEntryRateBuckets = getLogEntryRateSuccessReponsePayloadRT
.decode(body)
.getOrElseL(throwErrors(createPlainError));
const logEntryRateBuckets = pipe(
getLogEntryRateSuccessReponsePayloadRT.decode(body),
fold(throwErrors(createPlainError), identity)
);
expect(logEntryRateBuckets.data.bucketDuration).to.be(15 * 60 * 1000);
expect(logEntryRateBuckets.data.histogramBuckets).to.be.empty();

View file

@ -6281,10 +6281,10 @@ base64-arraybuffer@0.1.5:
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8"
integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg=
base64-js@0.0.2, base64-js@^1.2.1, base64-js@^1.2.3:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==
base64-js@0.0.2, base64-js@^1.2.1, base64-js@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base64-js@0.0.8:
version "0.0.8"
@ -6296,10 +6296,10 @@ base64-js@^1.0.2, base64-js@^1.1.2:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
integrity sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==
base64-js@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
base64-js@^1.2.3:
version "1.3.0"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==
base64id@1.0.0:
version "1.0.0"
@ -12637,6 +12637,11 @@ fp-ts@^1.14.2:
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.3.tgz#5064afc4bee8ddcaea567479bfc62d527e015825"
integrity sha512-r4gHfAWaRrYPsmdzRl1U9CkpbdOi8fPg5F5KiazAadENz5DKdWEaCDPl2Tf92fvkZGD/ekZ3EHu3gtXIVcsXtA==
fp-ts@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.0.5.tgz#9560d8a6a4f53cbda9f9b31ed8d1458e41939e07"
integrity sha512-opI5r+rVlpZE7Rhk0YtqsrmxGkbIw0dRNqGca8FEAMMnjomXotG+R9QkLQg20onx7R8qhepAn4CCOP8usma/Xw==
fragment-cache@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
@ -15423,12 +15428,10 @@ invert-kv@^2.0.0:
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
io-ts@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-1.4.2.tgz#d3cb1ef7d7ba68d59af85d839a728aad7f4b1c28"
integrity sha512-U4uw8jjj8jYZip7zHgBj40GW0DpYdVi1i0J3anezp2ytYHDg7+cKc7iIFlIyCh+NLwMxzwu6OQ/b9S61KUjPGg==
dependencies:
fp-ts "^1.0.0"
io-ts@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.0.1.tgz#1261c12f915c2f48d16393a36966636b48a45aa1"
integrity sha512-RezD+WcCfW4VkMkEcQWL/Nmy/nqsWTvTYg7oUmTGzglvSSV2P9h2z1PVeREPFf0GWNzruYleAt1XCMQZSg1xxQ==
ip-regex@^2.1.0:
version "2.1.0"