Skip to content

Commit

Permalink
move shared code to common
Browse files Browse the repository at this point in the history
  • Loading branch information
tsullivan committed Jul 13, 2020
1 parent 4e5eebd commit ed749eb
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/

import moment from 'moment-timezone';
import { DateNanosFormat, analysePatternForFract, formatWithNanos } from './date_nanos';
import { DateNanosFormat, analysePatternForFract, formatWithNanos } from './date_nanos_shared';

describe('Date Nanos Format', () => {
let convert: Function;
Expand Down
124 changes: 124 additions & 0 deletions src/plugins/data/common/field_formats/converters/date_nanos_shared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { i18n } from '@kbn/i18n';
import { memoize, noop } from 'lodash';
import moment, { Moment } from 'moment';
import { FieldFormat, FIELD_FORMAT_IDS, KBN_FIELD_TYPES, TextContextTypeConvert } from '../../';

/**
* Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...)
* returning length, match, pattern and an escaped pattern, that excludes the fractional
* part when formatting with moment.js -> e.g. [SSS]
*/
export function analysePatternForFract(pattern: string) {
const fracSecMatch = pattern.match('S+') as any; // extract fractional seconds sub-pattern
const fracSecMatchStr = fracSecMatch ? fracSecMatch[0] : '';

return {
length: fracSecMatchStr.length,
patternNanos: fracSecMatchStr,
pattern,
patternEscaped: fracSecMatchStr ? pattern.replace(fracSecMatch, `[${fracSecMatch}]`) : '',
};
}

/**
* Format a given moment.js date object
* Since momentjs would loose the exact value for fractional seconds with a higher resolution than
* milliseconds, the fractional pattern is replaced by the fractional value of the raw timestamp
*/
export function formatWithNanos(
dateMomentObj: Moment,
valRaw: string,
fracPatternObj: Record<string, any>
) {
if (fracPatternObj.length <= 3) {
// S,SS,SSS is formatted correctly by moment.js
return dateMomentObj.format(fracPatternObj.pattern);
} else {
// Beyond SSS the precise value of the raw datetime string is used
const valFormatted = dateMomentObj.format(fracPatternObj.patternEscaped);
// Extract fractional value of ES formatted timestamp, zero pad if necessary:
// 2020-05-18T20:45:05.957Z -> 957000000
// 2020-05-18T20:45:05.957000123Z -> 957000123
// we do not need to take care of the year 10000 bug since max year of date_nanos is 2262
const valNanos = valRaw
.substr(20, valRaw.length - 21) // remove timezone(Z)
.padEnd(9, '0') // pad shorter fractionals
.substr(0, fracPatternObj.patternNanos.length);
return valFormatted.replace(fracPatternObj.patternNanos, valNanos);
}
}

export class DateNanosFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.DATE_NANOS;
static title = i18n.translate('data.fieldFormats.date_nanos.title', {
defaultMessage: 'Date nanos',
});
static fieldType = KBN_FIELD_TYPES.DATE;

protected memoizedConverter: Function = noop;
protected memoizedPattern: string = '';
protected timeZone: string = '';

getParamDefaults() {
return {
pattern: this.getConfig!('dateNanosFormat'),
fallbackPattern: this.getConfig!('dateFormat'),
timezone: this.getConfig!('dateFormat:tz'),
};
}

textConvert: TextContextTypeConvert = (val) => {
// don't give away our ref to converter so
// we can hot-swap when config changes
const pattern = this.param('pattern');
const timezone = this.param('timezone');
const fractPattern = analysePatternForFract(pattern);
const fallbackPattern = this.param('patternFallback');

const timezoneChanged = this.timeZone !== timezone;
const datePatternChanged = this.memoizedPattern !== pattern;
if (timezoneChanged || datePatternChanged) {
this.timeZone = timezone;
this.memoizedPattern = pattern;

this.memoizedConverter = memoize(function converter(value: any) {
if (value === null || value === undefined) {
return '-';
}

const date = moment(value);

if (typeof value !== 'string' && date.isValid()) {
// fallback for max/min aggregation, where unixtime in ms is returned as a number
// aggregations in Elasticsearch generally just return ms
return date.format(fallbackPattern);
} else if (date.isValid()) {
return formatWithNanos(date, value, fractPattern);
} else {
return value;
}
});
}

return this.memoizedConverter(val);
};
}
111 changes: 1 addition & 110 deletions src/plugins/data/public/field_formats/converters/date_nanos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,113 +17,4 @@
* under the License.
*/

import { i18n } from '@kbn/i18n';
import { memoize, noop } from 'lodash';
import moment, { Moment } from 'moment';
import {
FieldFormat,
FIELD_FORMAT_IDS,
KBN_FIELD_TYPES,
TextContextTypeConvert,
} from '../../../common';

/**
* Analyse the given moment.js format pattern for the fractional sec part (S,SS,SSS...)
* returning length, match, pattern and an escaped pattern, that excludes the fractional
* part when formatting with moment.js -> e.g. [SSS]
*/
export function analysePatternForFract(pattern: string) {
const fracSecMatch = pattern.match('S+') as any; // extract fractional seconds sub-pattern
const fracSecMatchStr = fracSecMatch ? fracSecMatch[0] : '';

return {
length: fracSecMatchStr.length,
patternNanos: fracSecMatchStr,
pattern,
patternEscaped: fracSecMatchStr ? pattern.replace(fracSecMatch, `[${fracSecMatch}]`) : '',
};
}

/**
* Format a given moment.js date object
* Since momentjs would loose the exact value for fractional seconds with a higher resolution than
* milliseconds, the fractional pattern is replaced by the fractional value of the raw timestamp
*/
export function formatWithNanos(
dateMomentObj: Moment,
valRaw: string,
fracPatternObj: Record<string, any>
) {
if (fracPatternObj.length <= 3) {
// S,SS,SSS is formatted correctly by moment.js
return dateMomentObj.format(fracPatternObj.pattern);
} else {
// Beyond SSS the precise value of the raw datetime string is used
const valFormatted = dateMomentObj.format(fracPatternObj.patternEscaped);
// Extract fractional value of ES formatted timestamp, zero pad if necessary:
// 2020-05-18T20:45:05.957Z -> 957000000
// 2020-05-18T20:45:05.957000123Z -> 957000123
// we do not need to take care of the year 10000 bug since max year of date_nanos is 2262
const valNanos = valRaw
.substr(20, valRaw.length - 21) // remove timezone(Z)
.padEnd(9, '0') // pad shorter fractionals
.substr(0, fracPatternObj.patternNanos.length);
return valFormatted.replace(fracPatternObj.patternNanos, valNanos);
}
}

export class DateNanosFormat extends FieldFormat {
static id = FIELD_FORMAT_IDS.DATE_NANOS;
static title = i18n.translate('data.fieldFormats.date_nanos.title', {
defaultMessage: 'Date nanos',
});
static fieldType = KBN_FIELD_TYPES.DATE;

protected memoizedConverter: Function = noop;
protected memoizedPattern: string = '';
protected timeZone: string = '';

getParamDefaults() {
return {
pattern: this.getConfig!('dateNanosFormat'),
fallbackPattern: this.getConfig!('dateFormat'),
timezone: this.getConfig!('dateFormat:tz'),
};
}

textConvert: TextContextTypeConvert = (val) => {
// don't give away our ref to converter so
// we can hot-swap when config changes
const pattern = this.param('pattern');
const timezone = this.param('timezone');
const fractPattern = analysePatternForFract(pattern);
const fallbackPattern = this.param('patternFallback');

const timezoneChanged = this.timeZone !== timezone;
const datePatternChanged = this.memoizedPattern !== pattern;
if (timezoneChanged || datePatternChanged) {
this.timeZone = timezone;
this.memoizedPattern = pattern;

this.memoizedConverter = memoize(function converter(value: any) {
if (value === null || value === undefined) {
return '-';
}

const date = moment(value);

if (typeof value !== 'string' && date.isValid()) {
// fallback for max/min aggregation, where unixtime in ms is returned as a number
// aggregations in Elasticsearch generally just return ms
return date.format(fallbackPattern);
} else if (date.isValid()) {
return formatWithNanos(date, value, fractPattern);
} else {
return value;
}
});
}

return this.memoizedConverter(val);
};
}
export { DateNanosFormat } from '../../../common/field_formats/converters/date_nanos_shared';
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ import {
analysePatternForFract,
DateNanosFormat,
formatWithNanos,
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
} from '../../../public/field_formats/converters/date_nanos';
} from '../../../common/field_formats/converters/date_nanos_shared';
import { TextContextTypeConvert } from '../../../common';

class DateNanosFormatServer extends DateNanosFormat {
Expand Down

0 comments on commit ed749eb

Please sign in to comment.