mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
fix: be explicit when specifying time & replace moment with date-fns (#1072)
This commit is contained in:
parent
55bec22754
commit
b47e228181
@ -99,7 +99,6 @@
|
|||||||
"log4js": "^6.0.0",
|
"log4js": "^6.0.0",
|
||||||
"memoizee": "^0.4.15",
|
"memoizee": "^0.4.15",
|
||||||
"mime": "^2.4.2",
|
"mime": "^2.4.2",
|
||||||
"moment": "^2.24.0",
|
|
||||||
"multer": "^1.4.1",
|
"multer": "^1.4.1",
|
||||||
"mustache": "^4.1.0",
|
"mustache": "^4.1.0",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
|
@ -19,6 +19,7 @@ import {
|
|||||||
import { getDefaultLogProvider, LogLevel, validateLogProvider } from './logger';
|
import { getDefaultLogProvider, LogLevel, validateLogProvider } from './logger';
|
||||||
import { defaultCustomAuthDenyAll } from './default-custom-auth-deny-all';
|
import { defaultCustomAuthDenyAll } from './default-custom-auth-deny-all';
|
||||||
import { formatBaseUri } from './util/format-base-uri';
|
import { formatBaseUri } from './util/format-base-uri';
|
||||||
|
import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const safeToUpper = (s: string) => (s ? s.toUpperCase() : s);
|
const safeToUpper = (s: string) => (s ? s.toUpperCase() : s);
|
||||||
|
|
||||||
@ -94,13 +95,13 @@ const defaultDbOptions: IDBOption = {
|
|||||||
: { rejectUnauthorized: false },
|
: { rejectUnauthorized: false },
|
||||||
driver: 'postgres',
|
driver: 'postgres',
|
||||||
version: process.env.DATABASE_VERSION,
|
version: process.env.DATABASE_VERSION,
|
||||||
acquireConnectionTimeout: 30000,
|
acquireConnectionTimeout: secondsToMilliseconds(30),
|
||||||
pool: {
|
pool: {
|
||||||
min: safeNumber(process.env.DATABASE_POOL_MIN, 0),
|
min: safeNumber(process.env.DATABASE_POOL_MIN, 0),
|
||||||
max: safeNumber(process.env.DATABASE_POOL_MAX, 4),
|
max: safeNumber(process.env.DATABASE_POOL_MAX, 4),
|
||||||
idleTimeoutMillis: safeNumber(
|
idleTimeoutMillis: safeNumber(
|
||||||
process.env.DATABASE_POOL_IDLE_TIMEOUT_MS,
|
process.env.DATABASE_POOL_IDLE_TIMEOUT_MS,
|
||||||
30000,
|
secondsToMilliseconds(30),
|
||||||
),
|
),
|
||||||
propagateCreateError: false,
|
propagateCreateError: false,
|
||||||
},
|
},
|
||||||
@ -120,8 +121,8 @@ const defaultServerOption: IServerOption = {
|
|||||||
baseUriPath: formatBaseUri(process.env.BASE_URI_PATH),
|
baseUriPath: formatBaseUri(process.env.BASE_URI_PATH),
|
||||||
unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242',
|
unleashUrl: process.env.UNLEASH_URL || 'http://localhost:4242',
|
||||||
serverMetrics: true,
|
serverMetrics: true,
|
||||||
keepAliveTimeout: 60 * 1000,
|
keepAliveTimeout: minutesToMilliseconds(1),
|
||||||
headersTimeout: 61 * 1000,
|
headersTimeout: secondsToMilliseconds(61),
|
||||||
enableRequestLogger: false,
|
enableRequestLogger: false,
|
||||||
gracefulShutdownEnable: safeBoolean(
|
gracefulShutdownEnable: safeBoolean(
|
||||||
process.env.GRACEFUL_SHUTDOWN_ENABLE,
|
process.env.GRACEFUL_SHUTDOWN_ENABLE,
|
||||||
@ -129,7 +130,7 @@ const defaultServerOption: IServerOption = {
|
|||||||
),
|
),
|
||||||
gracefulShutdownTimeout: safeNumber(
|
gracefulShutdownTimeout: safeNumber(
|
||||||
process.env.GRACEFUL_SHUTDOWN_TIMEOUT,
|
process.env.GRACEFUL_SHUTDOWN_TIMEOUT,
|
||||||
1000,
|
secondsToMilliseconds(1),
|
||||||
),
|
),
|
||||||
secret: process.env.UNLEASH_SECRET || 'super-secret',
|
secret: process.env.UNLEASH_SECRET || 'super-secret',
|
||||||
};
|
};
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Logger, LogProvider } from '../logger';
|
import { Logger, LogProvider } from '../logger';
|
||||||
import Timeout = NodeJS.Timeout;
|
|
||||||
import {
|
import {
|
||||||
IClientInstance,
|
IClientInstance,
|
||||||
IClientInstanceStore,
|
IClientInstanceStore,
|
||||||
INewClientInstance,
|
INewClientInstance,
|
||||||
} from '../types/stores/client-instance-store';
|
} from '../types/stores/client-instance-store';
|
||||||
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
|
import Timeout = NodeJS.Timeout;
|
||||||
|
|
||||||
const metricsHelper = require('../util/metrics-helper');
|
const metricsHelper = require('../util/metrics-helper');
|
||||||
const { DB_TIME } = require('../metric-events');
|
const { DB_TIME } = require('../metric-events');
|
||||||
@ -22,8 +23,6 @@ const COLUMNS = [
|
|||||||
];
|
];
|
||||||
const TABLE = 'client_instances';
|
const TABLE = 'client_instances';
|
||||||
|
|
||||||
const ONE_DAY = 24 * 61 * 60 * 1000;
|
|
||||||
|
|
||||||
const mapRow = (row) => ({
|
const mapRow = (row) => ({
|
||||||
appName: row.app_name,
|
appName: row.app_name,
|
||||||
instanceId: row.instance_id,
|
instanceId: row.instance_id,
|
||||||
@ -65,7 +64,7 @@ export default class ClientInstanceStore implements IClientInstanceStore {
|
|||||||
});
|
});
|
||||||
const clearer = () => this._removeInstancesOlderThanTwoDays();
|
const clearer = () => this._removeInstancesOlderThanTwoDays();
|
||||||
setTimeout(clearer, 10).unref();
|
setTimeout(clearer, 10).unref();
|
||||||
this.timer = setInterval(clearer, ONE_DAY).unref();
|
this.timer = setInterval(clearer, hoursToMilliseconds(24)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
async _removeInstancesOlderThanTwoDays(): Promise<void> {
|
async _removeInstancesOlderThanTwoDays(): Promise<void> {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import { Logger, LogProvider } from '../logger';
|
import { Logger, LogProvider } from '../logger';
|
||||||
import { IClientMetric } from '../types/stores/client-metrics-db';
|
import { IClientMetric } from '../types/stores/client-metrics-db';
|
||||||
|
import { minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const METRICS_COLUMNS = ['id', 'created_at', 'metrics'];
|
const METRICS_COLUMNS = ['id', 'created_at', 'metrics'];
|
||||||
const TABLE = 'client_metrics';
|
const TABLE = 'client_metrics';
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
|
||||||
|
|
||||||
const mapRow = (row) => ({
|
const mapRow = (row) => ({
|
||||||
id: row.id,
|
id: row.id,
|
||||||
createdAt: row.created_at,
|
createdAt: row.created_at,
|
||||||
@ -24,7 +23,7 @@ export class ClientMetricsDb {
|
|||||||
// Clear old metrics regularly
|
// Clear old metrics regularly
|
||||||
const clearer = () => this.removeMetricsOlderThanOneHour();
|
const clearer = () => this.removeMetricsOlderThanOneHour();
|
||||||
setTimeout(clearer, 10).unref();
|
setTimeout(clearer, 10).unref();
|
||||||
this.timer = setInterval(clearer, ONE_MINUTE).unref();
|
this.timer = setInterval(clearer, minutesToMilliseconds(1)).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeMetricsOlderThanOneHour(): Promise<void> {
|
async removeMetricsOlderThanOneHour(): Promise<void> {
|
||||||
|
@ -5,8 +5,7 @@ import { DB_TIME } from '../metric-events';
|
|||||||
import { ClientMetricsDb } from './client-metrics-db';
|
import { ClientMetricsDb } from './client-metrics-db';
|
||||||
import { IClientMetric } from '../types/stores/client-metrics-db';
|
import { IClientMetric } from '../types/stores/client-metrics-db';
|
||||||
import { IClientMetricsStore } from '../types/stores/client-metrics-store';
|
import { IClientMetricsStore } from '../types/stores/client-metrics-store';
|
||||||
|
import { secondsToMilliseconds } from 'date-fns';
|
||||||
const TEN_SECONDS = 10 * 1000;
|
|
||||||
|
|
||||||
export class ClientMetricsStore
|
export class ClientMetricsStore
|
||||||
extends EventEmitter
|
extends EventEmitter
|
||||||
@ -24,7 +23,7 @@ export class ClientMetricsStore
|
|||||||
private metricsDb: ClientMetricsDb,
|
private metricsDb: ClientMetricsDb,
|
||||||
eventBus: EventEmitter,
|
eventBus: EventEmitter,
|
||||||
getLogger: LogProvider,
|
getLogger: LogProvider,
|
||||||
pollInterval = TEN_SECONDS,
|
pollInterval = secondsToMilliseconds(10),
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.logger = getLogger('client-metrics-store.ts.js');
|
this.logger = getLogger('client-metrics-store.ts.js');
|
||||||
|
@ -3,6 +3,7 @@ import { Knex } from 'knex';
|
|||||||
import { Logger, LogProvider } from '../logger';
|
import { Logger, LogProvider } from '../logger';
|
||||||
import NotFoundError from '../error/notfound-error';
|
import NotFoundError from '../error/notfound-error';
|
||||||
import { ISession, ISessionStore } from '../types/stores/session-store';
|
import { ISession, ISessionStore } from '../types/stores/session-store';
|
||||||
|
import { addDays } from 'date-fns';
|
||||||
|
|
||||||
const TABLE = 'unleash_session';
|
const TABLE = 'unleash_session';
|
||||||
|
|
||||||
@ -72,7 +73,7 @@ export default class SessionStore implements ISessionStore {
|
|||||||
.insert({
|
.insert({
|
||||||
sid: data.sid,
|
sid: data.sid,
|
||||||
sess: JSON.stringify(data.sess),
|
sess: JSON.stringify(data.sess),
|
||||||
expired: data.expired || new Date(Date.now() + 86400000),
|
expired: data.expired || addDays(Date.now(), 1),
|
||||||
})
|
})
|
||||||
.returning<ISessionRow>(['sid', 'sess', 'created_at', 'expired']);
|
.returning<ISessionRow>(['sid', 'sess', 'created_at', 'expired']);
|
||||||
if (row) {
|
if (row) {
|
||||||
|
@ -3,19 +3,17 @@ import EventEmitter from 'events';
|
|||||||
import { Knex } from 'knex';
|
import { Knex } from 'knex';
|
||||||
import * as events from './metric-events';
|
import * as events from './metric-events';
|
||||||
import {
|
import {
|
||||||
FEATURE_CREATED,
|
|
||||||
FEATURE_UPDATED,
|
|
||||||
FEATURE_ARCHIVED,
|
|
||||||
FEATURE_REVIVED,
|
|
||||||
DB_POOL_UPDATE,
|
DB_POOL_UPDATE,
|
||||||
|
FEATURE_ARCHIVED,
|
||||||
|
FEATURE_CREATED,
|
||||||
|
FEATURE_REVIVED,
|
||||||
|
FEATURE_UPDATED,
|
||||||
} from './types/events';
|
} from './types/events';
|
||||||
import { IUnleashConfig } from './types/option';
|
import { IUnleashConfig } from './types/option';
|
||||||
import { IUnleashStores } from './types/stores';
|
import { IUnleashStores } from './types/stores';
|
||||||
|
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
|
||||||
import Timer = NodeJS.Timer;
|
import Timer = NodeJS.Timer;
|
||||||
|
|
||||||
const TWO_HOURS = 2 * 60 * 60 * 1000;
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
|
||||||
|
|
||||||
export default class MetricsMonitor {
|
export default class MetricsMonitor {
|
||||||
timer?: Timer;
|
timer?: Timer;
|
||||||
|
|
||||||
@ -111,7 +109,7 @@ export default class MetricsMonitor {
|
|||||||
collectStaticCounters();
|
collectStaticCounters();
|
||||||
this.timer = setInterval(
|
this.timer = setInterval(
|
||||||
() => collectStaticCounters(),
|
() => collectStaticCounters(),
|
||||||
TWO_HOURS,
|
hoursToMilliseconds(2),
|
||||||
).unref();
|
).unref();
|
||||||
|
|
||||||
eventBus.on(
|
eventBus.on(
|
||||||
@ -199,7 +197,7 @@ export default class MetricsMonitor {
|
|||||||
this.registerPoolMetrics(db.client.pool, eventBus);
|
this.registerPoolMetrics(db.client.pool, eventBus);
|
||||||
this.poolMetricsTimer = setInterval(
|
this.poolMetricsTimer = setInterval(
|
||||||
() => this.registerPoolMetrics(db.client.pool, eventBus),
|
() => this.registerPoolMetrics(db.client.pool, eventBus),
|
||||||
ONE_MINUTE,
|
minutesToMilliseconds(1),
|
||||||
);
|
);
|
||||||
this.poolMetricsTimer.unref();
|
this.poolMetricsTimer.unref();
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
|
import { hoursToSeconds } from 'date-fns';
|
||||||
|
|
||||||
const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
|
const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
|
||||||
if (config.secureHeaders) {
|
if (config.secureHeaders) {
|
||||||
return helmet({
|
return helmet({
|
||||||
hsts: {
|
hsts: {
|
||||||
maxAge: 63072000,
|
maxAge: hoursToSeconds(24 * 365 * 2), // 2 non-leap years
|
||||||
includeSubDomains: true,
|
includeSubDomains: true,
|
||||||
preload: true,
|
preload: true,
|
||||||
},
|
},
|
||||||
|
@ -3,16 +3,16 @@ import session from 'express-session';
|
|||||||
import knexSessionStore from 'connect-session-knex';
|
import knexSessionStore from 'connect-session-knex';
|
||||||
import { RequestHandler } from 'express';
|
import { RequestHandler } from 'express';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const TWO_DAYS = 48 * 60 * 60 * 1000;
|
|
||||||
const HOUR = 60 * 60 * 1000;
|
|
||||||
function sessionDb(
|
function sessionDb(
|
||||||
config: Pick<IUnleashConfig, 'session' | 'server' | 'secureHeaders'>,
|
config: Pick<IUnleashConfig, 'session' | 'server' | 'secureHeaders'>,
|
||||||
knex: Knex,
|
knex: Knex,
|
||||||
): RequestHandler {
|
): RequestHandler {
|
||||||
let store;
|
let store;
|
||||||
const { db } = config.session;
|
const { db } = config.session;
|
||||||
const age = config.session.ttlHours * HOUR || TWO_DAYS;
|
const age =
|
||||||
|
hoursToMilliseconds(config.session.ttlHours) || hoursToMilliseconds(48);
|
||||||
const KnexSessionStore = knexSessionStore(session);
|
const KnexSessionStore = knexSessionStore(session);
|
||||||
if (db) {
|
if (db) {
|
||||||
store = new KnexSessionStore({
|
store = new KnexSessionStore({
|
||||||
@ -41,4 +41,5 @@ function sessionDb(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default sessionDb;
|
export default sessionDb;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as mime from 'mime';
|
import * as mime from 'mime';
|
||||||
import YAML from 'js-yaml';
|
import YAML from 'js-yaml';
|
||||||
import moment from 'moment';
|
|
||||||
import multer from 'multer';
|
import multer from 'multer';
|
||||||
|
import { format as formatDate } from 'date-fns';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import Controller from '../controller';
|
import Controller from '../controller';
|
||||||
import { ADMIN } from '../../types/permissions';
|
import { ADMIN } from '../../types/permissions';
|
||||||
@ -88,7 +88,7 @@ class StateController extends Controller {
|
|||||||
includeTags,
|
includeTags,
|
||||||
includeEnvironments,
|
includeEnvironments,
|
||||||
});
|
});
|
||||||
const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
|
const timestamp = formatDate(Date.now(), 'yyyy-MM-dd_HH-mm-ss');
|
||||||
if (format === 'yaml') {
|
if (format === 'yaml') {
|
||||||
if (downloadFile) {
|
if (downloadFile) {
|
||||||
res.attachment(`export-${timestamp}.yml`);
|
res.attachment(`export-${timestamp}.yml`);
|
||||||
|
@ -6,6 +6,7 @@ import getApp from '../../app';
|
|||||||
import { createServices } from '../../services';
|
import { createServices } from '../../services';
|
||||||
import FeatureController from './feature';
|
import FeatureController from './feature';
|
||||||
import { createTestConfig } from '../../../test/config/test-config';
|
import { createTestConfig } from '../../../test/config/test-config';
|
||||||
|
import { secondsToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const eventBus = new EventEmitter();
|
const eventBus = new EventEmitter();
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ test('if caching is enabled should memoize', async () => {
|
|||||||
experimental: {
|
experimental: {
|
||||||
clientFeatureMemoize: {
|
clientFeatureMemoize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
maxAge: 10000,
|
maxAge: secondsToMilliseconds(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -100,7 +101,7 @@ test('if caching is not enabled all calls goes to service', async () => {
|
|||||||
experimental: {
|
experimental: {
|
||||||
clientFeatureMemoize: {
|
clientFeatureMemoize: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
maxAge: 10000,
|
maxAge: secondsToMilliseconds(10),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -12,11 +12,10 @@ import { IAddon, IAddonDto, IAddonStore } from '../types/stores/addon-store';
|
|||||||
import { IUnleashStores } from '../types/stores';
|
import { IUnleashStores } from '../types/stores';
|
||||||
import { IUnleashConfig } from '../types/option';
|
import { IUnleashConfig } from '../types/option';
|
||||||
import { IAddonDefinition } from '../types/model';
|
import { IAddonDefinition } from '../types/model';
|
||||||
|
import { minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]);
|
const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]);
|
||||||
|
|
||||||
const ADDONS_CACHE_TIME = 60 * 1000; // 60s
|
|
||||||
|
|
||||||
const MASKED_VALUE = '*****';
|
const MASKED_VALUE = '*****';
|
||||||
|
|
||||||
interface ISensitiveParams {
|
interface ISensitiveParams {
|
||||||
@ -75,7 +74,7 @@ export default class AddonService {
|
|||||||
async () => addonStore.getAll({ enabled: true }),
|
async () => addonStore.getAll({ enabled: true }),
|
||||||
{
|
{
|
||||||
promise: true,
|
promise: true,
|
||||||
maxAge: ADDONS_CACHE_TIME,
|
maxAge: minutesToMilliseconds(1),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,7 @@ import {
|
|||||||
import { IApiTokenStore } from '../types/stores/api-token-store';
|
import { IApiTokenStore } from '../types/stores/api-token-store';
|
||||||
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
|
import { FOREIGN_KEY_VIOLATION } from '../error/db-error';
|
||||||
import BadDataError from '../error/bad-data-error';
|
import BadDataError from '../error/bad-data-error';
|
||||||
|
import { minutesToMilliseconds } from 'date-fns';
|
||||||
const ONE_MINUTE = 60_000;
|
|
||||||
|
|
||||||
export class ApiTokenService {
|
export class ApiTokenService {
|
||||||
private store: IApiTokenStore;
|
private store: IApiTokenStore;
|
||||||
@ -34,7 +33,7 @@ export class ApiTokenService {
|
|||||||
this.fetchActiveTokens();
|
this.fetchActiveTokens();
|
||||||
this.timer = setInterval(
|
this.timer = setInterval(
|
||||||
() => this.fetchActiveTokens(),
|
() => this.fetchActiveTokens(),
|
||||||
ONE_MINUTE,
|
minutesToMilliseconds(1),
|
||||||
).unref();
|
).unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,7 @@ import {
|
|||||||
IClientMetricsStoreV2,
|
IClientMetricsStoreV2,
|
||||||
} from '../../types/stores/client-metrics-store-v2';
|
} from '../../types/stores/client-metrics-store-v2';
|
||||||
import { clientMetricsSchema } from './client-metrics-schema';
|
import { clientMetricsSchema } from './client-metrics-schema';
|
||||||
|
import { hoursToMilliseconds, minutesToMilliseconds } from 'date-fns';
|
||||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
const ONE_DAY = 24 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export default class ClientMetricsServiceV2 {
|
export default class ClientMetricsServiceV2 {
|
||||||
private timer: NodeJS.Timeout;
|
private timer: NodeJS.Timeout;
|
||||||
@ -24,7 +22,7 @@ export default class ClientMetricsServiceV2 {
|
|||||||
constructor(
|
constructor(
|
||||||
{ clientMetricsStoreV2 }: Pick<IUnleashStores, 'clientMetricsStoreV2'>,
|
{ clientMetricsStoreV2 }: Pick<IUnleashStores, 'clientMetricsStoreV2'>,
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||||
bulkInterval = FIVE_MINUTES,
|
bulkInterval = minutesToMilliseconds(5),
|
||||||
) {
|
) {
|
||||||
this.clientMetricsStoreV2 = clientMetricsStoreV2;
|
this.clientMetricsStoreV2 = clientMetricsStoreV2;
|
||||||
|
|
||||||
@ -33,7 +31,7 @@ export default class ClientMetricsServiceV2 {
|
|||||||
this.bulkInterval = bulkInterval;
|
this.bulkInterval = bulkInterval;
|
||||||
this.timer = setInterval(() => {
|
this.timer = setInterval(() => {
|
||||||
this.clientMetricsStoreV2.clearMetrics(48);
|
this.clientMetricsStoreV2.clearMetrics(48);
|
||||||
}, ONE_DAY);
|
}, hoursToMilliseconds(24));
|
||||||
this.timer.unref();
|
this.timer.unref();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,51 @@
|
|||||||
import EventEmitter from 'events';
|
import EventEmitter from 'events';
|
||||||
import moment from 'moment';
|
|
||||||
import ClientMetricsService from './index';
|
import ClientMetricsService from './index';
|
||||||
import getLogger from '../../../test/fixtures/no-logger';
|
import getLogger from '../../../test/fixtures/no-logger';
|
||||||
import { IClientApp } from '../../types/model';
|
import { IClientApp } from '../../types/model';
|
||||||
|
import {
|
||||||
|
addHours,
|
||||||
|
addMinutes,
|
||||||
|
hoursToMilliseconds,
|
||||||
|
minutesToMilliseconds,
|
||||||
|
secondsToMilliseconds,
|
||||||
|
subHours,
|
||||||
|
subMinutes,
|
||||||
|
subSeconds,
|
||||||
|
} from 'date-fns';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A utility to wait for any pending promises in the test subject code.
|
||||||
|
* For instance, if the test needs to wait for a timeout/interval handler,
|
||||||
|
* and that handler does something async, advancing the timers is not enough:
|
||||||
|
* We have to explicitly wait for the second promise.
|
||||||
|
* For more info, see https://stackoverflow.com/a/51045733/2868829
|
||||||
|
*
|
||||||
|
* Usage in test code after advancing timers, but before making assertions:
|
||||||
|
*
|
||||||
|
* test('hello', async () => {
|
||||||
|
* jest.useFakeTimers('modern');
|
||||||
|
*
|
||||||
|
* // Schedule a timeout with a callback that does something async
|
||||||
|
* // before calling our spy
|
||||||
|
* const spy = jest.fn();
|
||||||
|
* setTimeout(async () => {
|
||||||
|
* await Promise.resolve();
|
||||||
|
* spy();
|
||||||
|
* }, 1000);
|
||||||
|
*
|
||||||
|
* expect(spy).not.toHaveBeenCalled();
|
||||||
|
*
|
||||||
|
* jest.advanceTimersByTime(1500);
|
||||||
|
* await flushPromises(); // this is required to make it work!
|
||||||
|
*
|
||||||
|
* expect(spy).toHaveBeenCalledTimes(1);
|
||||||
|
*
|
||||||
|
* jest.useRealTimers();
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
function flushPromises() {
|
||||||
|
return Promise.resolve(setImmediate);
|
||||||
|
}
|
||||||
|
|
||||||
const appName = 'appName';
|
const appName = 'appName';
|
||||||
const instanceId = 'instanceId';
|
const instanceId = 'instanceId';
|
||||||
@ -40,8 +83,8 @@ test('data should expire', () => {
|
|||||||
appName,
|
appName,
|
||||||
instanceId,
|
instanceId,
|
||||||
bucket: {
|
bucket: {
|
||||||
start: Date.now() - 2000,
|
start: subSeconds(Date.now(), 2),
|
||||||
stop: Date.now() - 1000,
|
stop: subSeconds(Date.now(), 1),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggleX: {
|
toggleX: {
|
||||||
yes: 123,
|
yes: 123,
|
||||||
@ -61,11 +104,11 @@ test('data should expire', () => {
|
|||||||
lastMinExpires++;
|
lastMinExpires++;
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.advanceTimersByTime(60 * 1000);
|
jest.advanceTimersByTime(minutesToMilliseconds(1));
|
||||||
expect(lastMinExpires).toBe(1);
|
expect(lastMinExpires).toBe(1);
|
||||||
expect(lastHourExpires).toBe(0);
|
expect(lastHourExpires).toBe(0);
|
||||||
|
|
||||||
jest.advanceTimersByTime(60 * 60 * 1000);
|
jest.advanceTimersByTime(hoursToMilliseconds(1));
|
||||||
expect(lastMinExpires).toBe(1);
|
expect(lastMinExpires).toBe(1);
|
||||||
expect(lastHourExpires).toBe(1);
|
expect(lastHourExpires).toBe(1);
|
||||||
|
|
||||||
@ -201,36 +244,36 @@ test('should have correct values for lastMinute', () => {
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
const input = [
|
const input = [
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(1, 'hour'),
|
start: subHours(now, 1),
|
||||||
stop: moment(now).subtract(59, 'minutes'),
|
stop: subMinutes(now, 59),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(30, 'minutes'),
|
start: subMinutes(now, 30),
|
||||||
stop: moment(now).subtract(29, 'minutes'),
|
stop: subMinutes(now, 29),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(2, 'minutes'),
|
start: subMinutes(now, 2),
|
||||||
stop: moment(now).subtract(1, 'minutes'),
|
stop: subMinutes(now, 1),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(2, 'minutes'),
|
start: subMinutes(now, 2),
|
||||||
stop: moment(now).subtract(59, 'seconds'),
|
stop: subSeconds(now, 59),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now),
|
start: now,
|
||||||
stop: moment(now).subtract(30, 'seconds'),
|
stop: subSeconds(now, 30),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
@ -252,11 +295,11 @@ test('should have correct values for lastMinute', () => {
|
|||||||
let c = metrics.getTogglesMetrics();
|
let c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastMinute.toggle).toEqual({ yes: 20, no: 20 });
|
expect(c.lastMinute.toggle).toEqual({ yes: 20, no: 20 });
|
||||||
|
|
||||||
jest.advanceTimersByTime(10 * 1000);
|
jest.advanceTimersByTime(10_000);
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastMinute.toggle).toEqual({ yes: 10, no: 10 });
|
expect(c.lastMinute.toggle).toEqual({ yes: 10, no: 10 });
|
||||||
|
|
||||||
jest.advanceTimersByTime(20 * 1000);
|
jest.advanceTimersByTime(20_000);
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastMinute.toggle).toEqual({ yes: 0, no: 0 });
|
expect(c.lastMinute.toggle).toEqual({ yes: 0, no: 0 });
|
||||||
|
|
||||||
@ -270,32 +313,32 @@ test('should have correct values for lastHour', () => {
|
|||||||
const clientMetricsStore = new EventEmitter();
|
const clientMetricsStore = new EventEmitter();
|
||||||
const metrics = createMetricsService(clientMetricsStore);
|
const metrics = createMetricsService(clientMetricsStore);
|
||||||
|
|
||||||
const now = new Date();
|
const now = Date.now();
|
||||||
const input = [
|
const input = [
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(1, 'hour'),
|
start: subHours(now, 1),
|
||||||
stop: moment(now).subtract(59, 'minutes'),
|
stop: subMinutes(now, 59),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(30, 'minutes'),
|
start: subMinutes(now, 30),
|
||||||
stop: moment(now).subtract(29, 'minutes'),
|
stop: subMinutes(now, 29),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).subtract(15, 'minutes'),
|
start: subMinutes(now, 15),
|
||||||
stop: moment(now).subtract(14, 'minutes'),
|
stop: subMinutes(now, 14),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 10, no: 10 },
|
toggle: { yes: 10, no: 10 },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: moment(now).add(59, 'minutes'),
|
start: addMinutes(now, 59),
|
||||||
stop: moment(now).add(1, 'hour'),
|
stop: addHours(now, 1),
|
||||||
toggles: {
|
toggles: {
|
||||||
toggle: { yes: 11, no: 11 },
|
toggle: { yes: 11, no: 11 },
|
||||||
},
|
},
|
||||||
@ -318,27 +361,27 @@ test('should have correct values for lastHour', () => {
|
|||||||
let c = metrics.getTogglesMetrics();
|
let c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
|
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
|
||||||
|
|
||||||
jest.advanceTimersByTime(10 * 1000);
|
jest.advanceTimersByTime(10_000);
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
|
expect(c.lastHour.toggle).toEqual({ yes: 41, no: 41 });
|
||||||
|
|
||||||
// at 30
|
// at 30
|
||||||
jest.advanceTimersByTime(30 * 60 * 1000);
|
jest.advanceTimersByTime(minutesToMilliseconds(30));
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 31, no: 31 });
|
expect(c.lastHour.toggle).toEqual({ yes: 31, no: 31 });
|
||||||
|
|
||||||
// at 45
|
// at 45
|
||||||
jest.advanceTimersByTime(15 * 60 * 1000);
|
jest.advanceTimersByTime(minutesToMilliseconds(15));
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 21, no: 21 });
|
expect(c.lastHour.toggle).toEqual({ yes: 21, no: 21 });
|
||||||
|
|
||||||
// at 1:15
|
// at 1:15
|
||||||
jest.advanceTimersByTime(30 * 60 * 1000);
|
jest.advanceTimersByTime(minutesToMilliseconds(30));
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 11, no: 11 });
|
expect(c.lastHour.toggle).toEqual({ yes: 11, no: 11 });
|
||||||
|
|
||||||
// at 2:00
|
// at 2:00
|
||||||
jest.advanceTimersByTime(45 * 60 * 1000);
|
jest.advanceTimersByTime(minutesToMilliseconds(45));
|
||||||
c = metrics.getTogglesMetrics();
|
c = metrics.getTogglesMetrics();
|
||||||
expect(c.lastHour.toggle).toEqual({ yes: 0, no: 0 });
|
expect(c.lastHour.toggle).toEqual({ yes: 0, no: 0 });
|
||||||
|
|
||||||
@ -421,7 +464,8 @@ test('Multiple registrations of same appname and instanceid within same time per
|
|||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await jest.advanceTimersByTime(7 * 1000);
|
jest.advanceTimersByTime(7000);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
expect(appStoreSpy).toHaveBeenCalledTimes(1);
|
expect(appStoreSpy).toHaveBeenCalledTimes(1);
|
||||||
expect(bulkSpy).toHaveBeenCalledTimes(1);
|
expect(bulkSpy).toHaveBeenCalledTimes(1);
|
||||||
@ -448,6 +492,7 @@ test('Multiple unique clients causes multiple registrations', async () => {
|
|||||||
const clientInstanceStore: any = {
|
const clientInstanceStore: any = {
|
||||||
bulkUpsert: bulkSpy,
|
bulkUpsert: bulkSpy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const clientMetrics = new ClientMetricsService(
|
const clientMetrics = new ClientMetricsService(
|
||||||
{
|
{
|
||||||
clientMetricsStore,
|
clientMetricsStore,
|
||||||
@ -479,10 +524,9 @@ test('Multiple unique clients causes multiple registrations', async () => {
|
|||||||
await clientMetrics.registerClient(client2, '127.0.0.1');
|
await clientMetrics.registerClient(client2, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client2, '127.0.0.1');
|
await clientMetrics.registerClient(client2, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client2, '127.0.0.1');
|
await clientMetrics.registerClient(client2, '127.0.0.1');
|
||||||
await jest.advanceTimersByTime(7 * 1000);
|
|
||||||
|
|
||||||
expect(appStoreSpy).toHaveBeenCalledTimes(1);
|
jest.advanceTimersByTime(7000);
|
||||||
expect(bulkSpy).toHaveBeenCalledTimes(1);
|
await flushPromises();
|
||||||
|
|
||||||
const registrations = appStoreSpy.mock.calls[0][0];
|
const registrations = appStoreSpy.mock.calls[0][0];
|
||||||
|
|
||||||
@ -501,7 +545,7 @@ test('Same client registered outside of dedup interval will be registered twice'
|
|||||||
bulkUpsert: bulkSpy,
|
bulkUpsert: bulkSpy,
|
||||||
};
|
};
|
||||||
|
|
||||||
const bulkInterval = 2000;
|
const bulkInterval = secondsToMilliseconds(2);
|
||||||
|
|
||||||
const clientMetrics = new ClientMetricsService(
|
const clientMetrics = new ClientMetricsService(
|
||||||
{
|
{
|
||||||
@ -525,11 +569,16 @@ test('Same client registered outside of dedup interval will be registered twice'
|
|||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await jest.advanceTimersByTime(3 * 1000);
|
|
||||||
|
jest.advanceTimersByTime(3000);
|
||||||
|
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await clientMetrics.registerClient(client1, '127.0.0.1');
|
await clientMetrics.registerClient(client1, '127.0.0.1');
|
||||||
await jest.advanceTimersByTime(3 * 1000);
|
|
||||||
|
jest.advanceTimersByTime(3000);
|
||||||
|
await flushPromises();
|
||||||
|
|
||||||
expect(appStoreSpy).toHaveBeenCalledTimes(2);
|
expect(appStoreSpy).toHaveBeenCalledTimes(2);
|
||||||
expect(bulkSpy).toHaveBeenCalledTimes(2);
|
expect(bulkSpy).toHaveBeenCalledTimes(2);
|
||||||
|
|
||||||
@ -552,8 +601,7 @@ test('No registrations during a time period will not call stores', async () => {
|
|||||||
const clientInstanceStore: any = {
|
const clientInstanceStore: any = {
|
||||||
bulkUpsert: bulkSpy,
|
bulkUpsert: bulkSpy,
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
new ClientMetricsService(
|
||||||
const clientMetrics = new ClientMetricsService(
|
|
||||||
{
|
{
|
||||||
clientMetricsStore,
|
clientMetricsStore,
|
||||||
strategyStore: null,
|
strategyStore: null,
|
||||||
@ -564,7 +612,7 @@ test('No registrations during a time period will not call stores', async () => {
|
|||||||
},
|
},
|
||||||
{ getLogger },
|
{ getLogger },
|
||||||
);
|
);
|
||||||
await jest.advanceTimersByTime(6 * 1000);
|
jest.advanceTimersByTime(6000);
|
||||||
expect(appStoreSpy).toHaveBeenCalledTimes(0);
|
expect(appStoreSpy).toHaveBeenCalledTimes(0);
|
||||||
expect(bulkSpy).toHaveBeenCalledTimes(0);
|
expect(bulkSpy).toHaveBeenCalledTimes(0);
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
@ -21,12 +21,9 @@ import {
|
|||||||
IMetricCounts,
|
IMetricCounts,
|
||||||
IMetricsBucket,
|
IMetricsBucket,
|
||||||
} from '../../types/model';
|
} from '../../types/model';
|
||||||
|
|
||||||
import TTLList = require('./ttl-list');
|
|
||||||
import { clientRegisterSchema } from './register-schema';
|
import { clientRegisterSchema } from './register-schema';
|
||||||
|
import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
|
||||||
const FIVE_SECONDS = 5 * 1000;
|
import TTLList = require('./ttl-list');
|
||||||
const FIVE_MINUTES = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
export default class ClientMetricsService {
|
export default class ClientMetricsService {
|
||||||
globalCount = 0;
|
globalCount = 0;
|
||||||
@ -38,13 +35,13 @@ export default class ClientMetricsService {
|
|||||||
lastMinuteProjection = new Projection();
|
lastMinuteProjection = new Projection();
|
||||||
|
|
||||||
lastHourList = new TTLList({
|
lastHourList = new TTLList({
|
||||||
interval: 10000,
|
interval: secondsToMilliseconds(10),
|
||||||
});
|
});
|
||||||
|
|
||||||
logger = null;
|
logger = null;
|
||||||
|
|
||||||
lastMinuteList = new TTLList({
|
lastMinuteList = new TTLList({
|
||||||
interval: 10000,
|
interval: secondsToMilliseconds(10),
|
||||||
expireType: 'minutes',
|
expireType: 'minutes',
|
||||||
expireAmount: 1,
|
expireAmount: 1,
|
||||||
});
|
});
|
||||||
@ -87,8 +84,8 @@ export default class ClientMetricsService {
|
|||||||
| 'eventStore'
|
| 'eventStore'
|
||||||
>,
|
>,
|
||||||
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>,
|
||||||
bulkInterval = FIVE_SECONDS,
|
bulkInterval = secondsToMilliseconds(5),
|
||||||
announcementInterval = FIVE_MINUTES,
|
announcementInterval = minutesToMilliseconds(5),
|
||||||
) {
|
) {
|
||||||
this.clientMetricsStore = clientMetricsStore;
|
this.clientMetricsStore = clientMetricsStore;
|
||||||
this.strategyStore = strategyStore;
|
this.strategyStore = strategyStore;
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { EventEmitter } = require('events');
|
const { EventEmitter } = require('events');
|
||||||
const moment = require('moment');
|
|
||||||
const List = require('./list');
|
const List = require('./list');
|
||||||
|
const {
|
||||||
|
add,
|
||||||
|
isFuture,
|
||||||
|
addMilliseconds,
|
||||||
|
secondsToMilliseconds,
|
||||||
|
} = require('date-fns');
|
||||||
|
|
||||||
// this list must have entries with sorted ttl range
|
// this list must have entries with sorted ttl range
|
||||||
module.exports = class TTLList extends EventEmitter {
|
module.exports = class TTLList extends EventEmitter {
|
||||||
constructor({
|
constructor({
|
||||||
interval = 1000,
|
interval = secondsToMilliseconds(1),
|
||||||
expireAmount = 1,
|
expireAmount = 1,
|
||||||
expireType = 'hours',
|
expireType = 'hours',
|
||||||
} = {}) {
|
} = {}) {
|
||||||
@ -16,6 +21,14 @@ module.exports = class TTLList extends EventEmitter {
|
|||||||
this.expireAmount = expireAmount;
|
this.expireAmount = expireAmount;
|
||||||
this.expireType = expireType;
|
this.expireType = expireType;
|
||||||
|
|
||||||
|
this.getExpiryFrom = (timestamp) => {
|
||||||
|
if (this.expireType === 'milliseconds') {
|
||||||
|
return addMilliseconds(timestamp, expireAmount);
|
||||||
|
} else {
|
||||||
|
return add(timestamp, { [expireType]: expireAmount });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.list = new List();
|
this.list = new List();
|
||||||
|
|
||||||
this.list.on('evicted', ({ value, ttl }) => {
|
this.list.on('evicted', ({ value, ttl }) => {
|
||||||
@ -36,8 +49,8 @@ module.exports = class TTLList extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
add(value, timestamp = new Date()) {
|
add(value, timestamp = new Date()) {
|
||||||
const ttl = moment(timestamp).add(this.expireAmount, this.expireType);
|
const ttl = this.getExpiryFrom(timestamp);
|
||||||
if (moment().isBefore(ttl)) {
|
if (isFuture(ttl)) {
|
||||||
this.list.add({ ttl, value });
|
this.list.add({ ttl, value });
|
||||||
} else {
|
} else {
|
||||||
this.emit('expire', value, ttl);
|
this.emit('expire', value, ttl);
|
||||||
@ -45,17 +58,13 @@ module.exports = class TTLList extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
timedCheck() {
|
timedCheck() {
|
||||||
const now = moment();
|
this.list.reverseRemoveUntilTrue(({ value }) => isFuture(value.ttl));
|
||||||
this.list.reverseRemoveUntilTrue(({ value }) =>
|
|
||||||
now.isBefore(value.ttl),
|
|
||||||
);
|
|
||||||
this.startTimer();
|
this.startTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
// https://github.com/nodejs/node/issues/9561
|
clearTimeout(this.timer);
|
||||||
// clearTimeout(this.timer);
|
this.timer = null;
|
||||||
// this.timer = null;
|
|
||||||
this.list = null;
|
this.list = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const moment = require('moment');
|
|
||||||
const TTLList = require('./ttl-list');
|
const TTLList = require('./ttl-list');
|
||||||
|
const { addMilliseconds } = require('date-fns');
|
||||||
|
|
||||||
test('should emit expire', (done) => {
|
test('should emit expire', (done) => {
|
||||||
jest.useFakeTimers('modern');
|
jest.useFakeTimers('modern');
|
||||||
@ -31,10 +31,10 @@ test('should slice off list', () => {
|
|||||||
expireType: 'milliseconds',
|
expireType: 'milliseconds',
|
||||||
});
|
});
|
||||||
|
|
||||||
list.add({ n: '1' }, moment().add(1, 'milliseconds'));
|
list.add({ n: '1' }, addMilliseconds(Date.now(), 1));
|
||||||
list.add({ n: '2' }, moment().add(50, 'milliseconds'));
|
list.add({ n: '2' }, addMilliseconds(Date.now(), 50));
|
||||||
list.add({ n: '3' }, moment().add(200, 'milliseconds'));
|
list.add({ n: '3' }, addMilliseconds(Date.now(), 200));
|
||||||
list.add({ n: '4' }, moment().add(300, 'milliseconds'));
|
list.add({ n: '4' }, addMilliseconds(Date.now(), 300));
|
||||||
|
|
||||||
const expired = [];
|
const expired = [];
|
||||||
|
|
||||||
@ -43,18 +43,45 @@ test('should slice off list', () => {
|
|||||||
expired.push(entry);
|
expired.push(entry);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
expect(expired).toHaveLength(0);
|
||||||
|
expect(list.list.toArray()).toHaveLength(4);
|
||||||
|
|
||||||
jest.advanceTimersByTime(21);
|
jest.advanceTimersByTime(21);
|
||||||
expect(expired).toHaveLength(1);
|
expect(expired).toHaveLength(1);
|
||||||
|
expect(list.list.toArray()).toHaveLength(3);
|
||||||
|
|
||||||
jest.advanceTimersByTime(51);
|
jest.advanceTimersByTime(51);
|
||||||
expect(expired).toHaveLength(2);
|
expect(expired).toHaveLength(2);
|
||||||
|
expect(list.list.toArray()).toHaveLength(2);
|
||||||
|
|
||||||
jest.advanceTimersByTime(201);
|
jest.advanceTimersByTime(201);
|
||||||
expect(expired).toHaveLength(3);
|
expect(expired).toHaveLength(3);
|
||||||
|
expect(list.list.toArray()).toHaveLength(1);
|
||||||
|
|
||||||
jest.advanceTimersByTime(301);
|
jest.advanceTimersByTime(301);
|
||||||
expect(expired).toHaveLength(4);
|
expect(expired).toHaveLength(4);
|
||||||
|
expect(list.list.toArray()).toHaveLength(0);
|
||||||
|
|
||||||
list.destroy();
|
list.destroy();
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should add item created in the past but expiring in the future', () => {
|
||||||
|
jest.useFakeTimers('modern');
|
||||||
|
|
||||||
|
const list = new TTLList({
|
||||||
|
interval: 10,
|
||||||
|
expireAmount: 10,
|
||||||
|
expireType: 'milliseconds',
|
||||||
|
});
|
||||||
|
|
||||||
|
const expireCallback = jest.fn();
|
||||||
|
list.on('expire', expireCallback);
|
||||||
|
|
||||||
|
list.add({ n: '1' }, new Date());
|
||||||
|
|
||||||
|
expect(expireCallback).not.toHaveBeenCalled();
|
||||||
|
expect(list.list.toArray()).toHaveLength(1);
|
||||||
|
|
||||||
|
jest.useRealTimers();
|
||||||
|
});
|
||||||
|
@ -8,15 +8,12 @@ import {
|
|||||||
IProjectHealthReport,
|
IProjectHealthReport,
|
||||||
IProjectOverview,
|
IProjectOverview,
|
||||||
} from '../types/model';
|
} from '../types/model';
|
||||||
import {
|
|
||||||
MILLISECONDS_IN_DAY,
|
|
||||||
MILLISECONDS_IN_ONE_HOUR,
|
|
||||||
} from '../util/constants';
|
|
||||||
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
|
||||||
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
|
import { IFeatureTypeStore } from '../types/stores/feature-type-store';
|
||||||
import { IProjectStore } from '../types/stores/project-store';
|
import { IProjectStore } from '../types/stores/project-store';
|
||||||
import Timer = NodeJS.Timer;
|
|
||||||
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
|
import FeatureToggleServiceV2 from './feature-toggle-service-v2';
|
||||||
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
|
import Timer = NodeJS.Timer;
|
||||||
|
|
||||||
export default class ProjectHealthService {
|
export default class ProjectHealthService {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -52,7 +49,7 @@ export default class ProjectHealthService {
|
|||||||
this.featureTypes = new Map();
|
this.featureTypes = new Map();
|
||||||
this.healthRatingTimer = setInterval(
|
this.healthRatingTimer = setInterval(
|
||||||
() => this.setHealthRating(),
|
() => this.setHealthRating(),
|
||||||
MILLISECONDS_IN_ONE_HOUR,
|
hoursToMilliseconds(1),
|
||||||
).unref();
|
).unref();
|
||||||
this.featureToggleService = featureToggleService;
|
this.featureToggleService = featureToggleService;
|
||||||
}
|
}
|
||||||
@ -116,7 +113,7 @@ export default class ProjectHealthService {
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
!feature.stale &&
|
!feature.stale &&
|
||||||
diff >= featureTypeExpectedLifetime * MILLISECONDS_IN_DAY
|
diff >= featureTypeExpectedLifetime * hoursToMilliseconds(24)
|
||||||
);
|
);
|
||||||
}).length;
|
}).length;
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,7 @@ import {
|
|||||||
IResetToken,
|
IResetToken,
|
||||||
IResetTokenStore,
|
IResetTokenStore,
|
||||||
} from '../types/stores/reset-token-store';
|
} from '../types/stores/reset-token-store';
|
||||||
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
const ONE_DAY = 86_400_000;
|
|
||||||
|
|
||||||
interface IInviteLinks {
|
interface IInviteLinks {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
@ -106,7 +105,7 @@ export default class ResetTokenService {
|
|||||||
async createToken(
|
async createToken(
|
||||||
tokenUser: number,
|
tokenUser: number,
|
||||||
creator: string,
|
creator: string,
|
||||||
expiryDelta: number = ONE_DAY,
|
expiryDelta: number = hoursToMilliseconds(24),
|
||||||
): Promise<IResetToken> {
|
): Promise<IResetToken> {
|
||||||
const token = await this.generateToken();
|
const token = await this.generateToken();
|
||||||
const expiry = new Date(Date.now() + expiryDelta);
|
const expiry = new Date(Date.now() + expiryDelta);
|
||||||
|
@ -4,8 +4,7 @@ import { IUnleashConfig } from '../types/option';
|
|||||||
import version from '../util/version';
|
import version from '../util/version';
|
||||||
import { Logger } from '../logger';
|
import { Logger } from '../logger';
|
||||||
import { ISettingStore } from '../types/stores/settings-store';
|
import { ISettingStore } from '../types/stores/settings-store';
|
||||||
|
import { hoursToMilliseconds } from 'date-fns';
|
||||||
const TWO_DAYS = 48 * 60 * 60 * 1000;
|
|
||||||
|
|
||||||
export interface IVersionInfo {
|
export interface IVersionInfo {
|
||||||
oss: string;
|
oss: string;
|
||||||
@ -66,7 +65,7 @@ export default class VersionService {
|
|||||||
await this.checkLatestVersion();
|
await this.checkLatestVersion();
|
||||||
this.timer = setInterval(
|
this.timer = setInterval(
|
||||||
async () => this.checkLatestVersion(),
|
async () => this.checkLatestVersion(),
|
||||||
TWO_DAYS,
|
hoursToMilliseconds(48),
|
||||||
);
|
);
|
||||||
this.timer.unref();
|
this.timer.unref();
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1 @@
|
|||||||
export const MILLISECONDS_IN_DAY = 86400000;
|
|
||||||
export const MILLISECONDS_IN_ONE_HOUR = 3600000;
|
|
||||||
export const DEFAULT_ENV = 'default';
|
export const DEFAULT_ENV = 'default';
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { log } from 'db-migrate-shared';
|
import { log } from 'db-migrate-shared';
|
||||||
import { getInstance } from 'db-migrate';
|
import { getInstance } from 'db-migrate';
|
||||||
import { IUnleashConfig } from './lib/types/option';
|
import { IUnleashConfig } from './lib/types/option';
|
||||||
|
import { secondsToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
log.setLogLevel('error');
|
log.setLogLevel('error');
|
||||||
|
|
||||||
export async function migrateDb({ db }: IUnleashConfig): Promise<void> {
|
export async function migrateDb({ db }: IUnleashConfig): Promise<void> {
|
||||||
const custom = { ...db, connectionTimeoutMillis: 10000 };
|
const custom = {
|
||||||
|
...db,
|
||||||
|
connectionTimeoutMillis: secondsToMilliseconds(10),
|
||||||
|
};
|
||||||
|
|
||||||
const dbm = getInstance(true, {
|
const dbm = getInstance(true, {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
@ -18,7 +22,10 @@ export async function migrateDb({ db }: IUnleashConfig): Promise<void> {
|
|||||||
|
|
||||||
// This exists to ease testing
|
// This exists to ease testing
|
||||||
export async function resetDb({ db }: IUnleashConfig): Promise<void> {
|
export async function resetDb({ db }: IUnleashConfig): Promise<void> {
|
||||||
const custom = { ...db, connectionTimeoutMillis: 10000 };
|
const custom = {
|
||||||
|
...db,
|
||||||
|
connectionTimeoutMillis: secondsToMilliseconds(10),
|
||||||
|
};
|
||||||
|
|
||||||
const dbm = getInstance(true, {
|
const dbm = getInstance(true, {
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import dbInit from '../../helpers/database-init';
|
import dbInit from '../../helpers/database-init';
|
||||||
import { setupApp } from '../../helpers/test-helper';
|
import { setupApp } from '../../helpers/test-helper';
|
||||||
import getLogger from '../../../fixtures/no-logger';
|
import getLogger from '../../../fixtures/no-logger';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
|
|
||||||
let app;
|
let app;
|
||||||
let db;
|
let db;
|
||||||
@ -28,25 +29,27 @@ beforeEach(async () => {
|
|||||||
description: 'Some desc',
|
description: 'Some desc',
|
||||||
announced: true,
|
announced: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const clientStartedDate = parseISO('2018-01-15T14:35:38.494Z');
|
||||||
await db.stores.clientInstanceStore.insert({
|
await db.stores.clientInstanceStore.insert({
|
||||||
appName: 'demo-app-1',
|
appName: 'demo-app-1',
|
||||||
instanceId: 'test-1',
|
instanceId: 'test-1',
|
||||||
strategies: ['default'],
|
strategies: ['default'],
|
||||||
started: 1516026938494,
|
started: clientStartedDate,
|
||||||
interval: 10,
|
interval: 10,
|
||||||
});
|
});
|
||||||
await db.stores.clientInstanceStore.insert({
|
await db.stores.clientInstanceStore.insert({
|
||||||
appName: 'demo-seed-2',
|
appName: 'demo-seed-2',
|
||||||
instanceId: 'test-2',
|
instanceId: 'test-2',
|
||||||
strategies: ['default'],
|
strategies: ['default'],
|
||||||
started: 1516026938494,
|
started: clientStartedDate,
|
||||||
interval: 10,
|
interval: 10,
|
||||||
});
|
});
|
||||||
await db.stores.clientInstanceStore.insert({
|
await db.stores.clientInstanceStore.insert({
|
||||||
appName: 'deletable-app',
|
appName: 'deletable-app',
|
||||||
instanceId: 'inst-1',
|
instanceId: 'inst-1',
|
||||||
strategies: ['default'],
|
strategies: ['default'],
|
||||||
started: 1516026938494,
|
started: clientStartedDate,
|
||||||
interval: 10,
|
interval: 10,
|
||||||
});
|
});
|
||||||
await app.services.clientMetricsService.addPayload({
|
await app.services.clientMetricsService.addPayload({
|
||||||
|
@ -60,7 +60,7 @@ test('should allow client to register multiple times', async () => {
|
|||||||
.send(clientRegistration)
|
.send(clientRegistration)
|
||||||
.expect(202);
|
.expect(202);
|
||||||
|
|
||||||
jest.advanceTimersByTime(6 * 1000);
|
jest.advanceTimersByTime(6000);
|
||||||
expect(clientApplicationsStore.exists(clientRegistration)).toBeTruthy();
|
expect(clientApplicationsStore.exists(clientRegistration)).toBeTruthy();
|
||||||
expect(clientInstanceStore.exists(clientRegistration)).toBeTruthy();
|
expect(clientInstanceStore.exists(clientRegistration)).toBeTruthy();
|
||||||
jest.useRealTimers();
|
jest.useRealTimers();
|
||||||
|
@ -78,7 +78,7 @@ test('should only return active addons', async () => {
|
|||||||
await addonService.createAddon(config2, 'me@mail.com');
|
await addonService.createAddon(config2, 'me@mail.com');
|
||||||
await addonService.createAddon(config3, 'me@mail.com');
|
await addonService.createAddon(config3, 'me@mail.com');
|
||||||
|
|
||||||
jest.advanceTimersByTime(61 * 1000);
|
jest.advanceTimersByTime(61_000);
|
||||||
|
|
||||||
const activeAddons = await addonService.fetchAddonConfigs();
|
const activeAddons = await addonService.fetchAddonConfigs();
|
||||||
const allAddons = await addonService.getAddons();
|
const allAddons = await addonService.getAddons();
|
||||||
|
@ -4,6 +4,7 @@ import { ApiTokenService } from '../../../lib/services/api-token-service';
|
|||||||
import { createTestConfig } from '../../config/test-config';
|
import { createTestConfig } from '../../config/test-config';
|
||||||
import { ApiTokenType, IApiToken } from '../../../lib/types/models/api-token';
|
import { ApiTokenType, IApiToken } from '../../../lib/types/models/api-token';
|
||||||
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
import { DEFAULT_ENV } from '../../../lib/util/constants';
|
||||||
|
import { addDays, subDays } from 'date-fns';
|
||||||
|
|
||||||
let db;
|
let db;
|
||||||
let stores;
|
let stores;
|
||||||
@ -102,13 +103,14 @@ test('should update expiry of token', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('should only return valid tokens', async () => {
|
test('should only return valid tokens', async () => {
|
||||||
const today = new Date();
|
const now = Date.now();
|
||||||
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000);
|
const yesterday = subDays(now, 1);
|
||||||
|
const tomorrow = addDays(now, 1);
|
||||||
|
|
||||||
await apiTokenService.createApiToken({
|
await apiTokenService.createApiToken({
|
||||||
username: 'default-expired',
|
username: 'default-expired',
|
||||||
type: ApiTokenType.CLIENT,
|
type: ApiTokenType.CLIENT,
|
||||||
expiresAt: new Date('2021-01-01'),
|
expiresAt: yesterday,
|
||||||
project: '*',
|
project: '*',
|
||||||
environment: DEFAULT_ENV,
|
environment: DEFAULT_ENV,
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import ClientMetricsService from '../../../lib/services/client-metrics';
|
import ClientMetricsService from '../../../lib/services/client-metrics';
|
||||||
import { IClientApp } from '../../../lib/types/model';
|
import { IClientApp } from '../../../lib/types/model';
|
||||||
|
import { secondsToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
const faker = require('faker');
|
const faker = require('faker');
|
||||||
const dbInit = require('../helpers/database-init');
|
const dbInit = require('../helpers/database-init');
|
||||||
@ -13,11 +14,15 @@ let clientMetricsService;
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
db = await dbInit('client_metrics_service_serial', getLogger);
|
db = await dbInit('client_metrics_service_serial', getLogger);
|
||||||
stores = db.stores;
|
stores = db.stores;
|
||||||
|
|
||||||
|
const bulkInterval = secondsToMilliseconds(0.5);
|
||||||
|
const announcementInterval = secondsToMilliseconds(2);
|
||||||
|
|
||||||
clientMetricsService = new ClientMetricsService(
|
clientMetricsService = new ClientMetricsService(
|
||||||
stores,
|
stores,
|
||||||
{ getLogger },
|
{ getLogger },
|
||||||
500,
|
bulkInterval,
|
||||||
2000,
|
announcementInterval,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -53,7 +58,7 @@ test('Apps registered should be announced', async () => {
|
|||||||
const first = await stores.clientApplicationsStore.getUnannounced();
|
const first = await stores.clientApplicationsStore.getUnannounced();
|
||||||
expect(first.length).toBe(2);
|
expect(first.length).toBe(2);
|
||||||
await clientMetricsService.registerClient(clientRegistration, '127.0.0.1');
|
await clientMetricsService.registerClient(clientRegistration, '127.0.0.1');
|
||||||
await new Promise((res) => setTimeout(res, 2000));
|
await new Promise((res) => setTimeout(res, secondsToMilliseconds(2)));
|
||||||
const second = await stores.clientApplicationsStore.getUnannounced();
|
const second = await stores.clientApplicationsStore.getUnannounced();
|
||||||
expect(second.length).toBe(0);
|
expect(second.length).toBe(0);
|
||||||
const events = await stores.eventStore.getEvents();
|
const events = await stores.eventStore.getEvents();
|
||||||
|
@ -2,6 +2,7 @@ import noLoggerProvider from '../../fixtures/no-logger';
|
|||||||
import dbInit from '../helpers/database-init';
|
import dbInit from '../helpers/database-init';
|
||||||
import SessionService from '../../../lib/services/session-service';
|
import SessionService from '../../../lib/services/session-service';
|
||||||
import NotFoundError from '../../../lib/error/notfound-error';
|
import NotFoundError from '../../../lib/error/notfound-error';
|
||||||
|
import { addDays, minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
let stores;
|
let stores;
|
||||||
let db;
|
let db;
|
||||||
@ -10,8 +11,8 @@ const newSession = {
|
|||||||
sid: 'abc123',
|
sid: 'abc123',
|
||||||
sess: {
|
sess: {
|
||||||
cookie: {
|
cookie: {
|
||||||
originalMaxAge: 2880000,
|
originalMaxAge: minutesToMilliseconds(48),
|
||||||
expires: new Date(Date.now() + 86_400_000).toDateString(),
|
expires: addDays(Date.now(), 1).toDateString(),
|
||||||
secure: false,
|
secure: false,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
@ -31,8 +32,8 @@ const otherSession = {
|
|||||||
sid: 'xyz321',
|
sid: 'xyz321',
|
||||||
sess: {
|
sess: {
|
||||||
cookie: {
|
cookie: {
|
||||||
originalMaxAge: 2880000,
|
originalMaxAge: minutesToMilliseconds(48),
|
||||||
expires: new Date(Date.now() + 86400000).toDateString(),
|
expires: addDays(Date.now(), 1).toDateString(),
|
||||||
secure: false,
|
secure: false,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
|
@ -12,6 +12,7 @@ import { IRole } from '../../../lib/types/stores/access-store';
|
|||||||
import { RoleName } from '../../../lib/types/model';
|
import { RoleName } from '../../../lib/types/model';
|
||||||
import SettingService from '../../../lib/services/setting-service';
|
import SettingService from '../../../lib/services/setting-service';
|
||||||
import { simpleAuthKey } from '../../../lib/types/settings/simple-auth-settings';
|
import { simpleAuthKey } from '../../../lib/types/settings/simple-auth-settings';
|
||||||
|
import { addDays, minutesToMilliseconds } from 'date-fns';
|
||||||
|
|
||||||
let db;
|
let db;
|
||||||
let stores;
|
let stores;
|
||||||
@ -161,8 +162,8 @@ test("deleting a user should delete the user's sessions", async () => {
|
|||||||
sid: 'xyz321',
|
sid: 'xyz321',
|
||||||
sess: {
|
sess: {
|
||||||
cookie: {
|
cookie: {
|
||||||
originalMaxAge: 2880000,
|
originalMaxAge: minutesToMilliseconds(48),
|
||||||
expires: new Date(Date.now() + 86400000).toDateString(),
|
expires: addDays(Date.now(), 1).toDateString(),
|
||||||
secure: false,
|
secure: false,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
path: '/',
|
path: '/',
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { subDays } from 'date-fns';
|
import { addHours, set, subDays } from 'date-fns';
|
||||||
import dbInit from '../helpers/database-init';
|
import dbInit from '../helpers/database-init';
|
||||||
import getLogger from '../../fixtures/no-logger';
|
import getLogger from '../../fixtures/no-logger';
|
||||||
import { IUnleashStores } from '../../../lib/types';
|
import { IUnleashStores } from '../../../lib/types';
|
||||||
@ -66,10 +66,9 @@ test('Should "increment" metrics within same hour', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should get individual metrics outside same hour', async () => {
|
test('Should get individual metrics outside same hour', async () => {
|
||||||
const d1 = new Date();
|
const d1 = set(Date.now(), { hours: 10, minutes: 10, seconds: 11 });
|
||||||
const d2 = new Date();
|
const d2 = addHours(d1, 1);
|
||||||
d1.setHours(10, 10, 11);
|
|
||||||
d2.setHours(11, 10, 11);
|
|
||||||
const metrics: IClientMetricsEnv[] = [
|
const metrics: IClientMetricsEnv[] = [
|
||||||
{
|
{
|
||||||
featureName: 'demo',
|
featureName: 'demo',
|
||||||
@ -265,7 +264,7 @@ test('Should not fail on undefined list of metrics', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should return delete old metric', async () => {
|
test('Should return delete old metric', async () => {
|
||||||
const twoDaysAgo = subDays(new Date(), 2);
|
const twoDaysAgo = subDays(Date.now(), 2);
|
||||||
|
|
||||||
const metrics: IClientMetricsEnv[] = [
|
const metrics: IClientMetricsEnv[] = [
|
||||||
{
|
{
|
||||||
@ -311,7 +310,7 @@ test('Should return delete old metric', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Should get metric', async () => {
|
test('Should get metric', async () => {
|
||||||
const twoDaysAgo = subDays(new Date(), 2);
|
const twoDaysAgo = subDays(Date.now(), 2);
|
||||||
|
|
||||||
const metrics: IClientMetricsEnv[] = [
|
const metrics: IClientMetricsEnv[] = [
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,7 @@ test('Should be able to store multiple events at once', async () => {
|
|||||||
const seen = [];
|
const seen = [];
|
||||||
eventStore.on(APPLICATION_CREATED, (e) => seen.push(e));
|
eventStore.on(APPLICATION_CREATED, (e) => seen.push(e));
|
||||||
await eventStore.batchStore([event1, event2, event3]);
|
await eventStore.batchStore([event1, event2, event3]);
|
||||||
await jest.advanceTimersByTime(100);
|
jest.advanceTimersByTime(100);
|
||||||
expect(seen.length).toBe(3);
|
expect(seen.length).toBe(3);
|
||||||
seen.forEach((e) => {
|
seen.forEach((e) => {
|
||||||
expect(e.id).toBeTruthy();
|
expect(e.id).toBeTruthy();
|
||||||
|
@ -4996,11 +4996,6 @@ module-not-found-error@^1.0.1:
|
|||||||
resolved "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz"
|
||||||
integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
|
integrity sha1-z4tP9PKWQGdNbN0CsOO8UjwrvcA=
|
||||||
|
|
||||||
moment@^2.24.0:
|
|
||||||
version "2.29.1"
|
|
||||||
resolved "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz"
|
|
||||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
|
||||||
|
|
||||||
"mongodb-uri@>= 0.9.7":
|
"mongodb-uri@>= 0.9.7":
|
||||||
version "0.9.7"
|
version "0.9.7"
|
||||||
resolved "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz"
|
resolved "https://registry.npmjs.org/mongodb-uri/-/mongodb-uri-0.9.7.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user