/* * SPDX-License-Identifier: Apache-2.0 * * The OpenSearch Contributors require contributions made to * this file be licensed under the Apache-2.0 license or a * compatible open source license. * * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ import { get, isEmpty } from 'lodash'; import React from 'react'; import { EuiTitle } from '@elastic/eui'; import { darkModeEnabled } from './opensearchDashboardsUtils'; import { ALERTING_PLUGIN_NAME, NAME_REGEX, INDEX_NAME_REGEX, MAX_FEATURE_NAME_SIZE, MAX_INDEX_NAME_SIZE, } from './constants'; import { CoreStart } from '../../../../src/core/public'; import { CoreServicesContext } from '../components/CoreServices/CoreServices'; import datemath from '@elastic/datemath'; import moment from 'moment'; import { Detector } from '../models/interfaces'; import { CUSTOM_AD_RESULT_INDEX_PREFIX } from '../../server/utils/constants'; export const validateFeatureName = ( featureName: string ): string | undefined => { return validateName(featureName, 'feature'); }; export const validateDetectorName = ( detectorName: string ): string | undefined => { return validateName(detectorName, 'detector'); }; export const validateName = ( name: string, fieldName: string ): string | undefined => { if (isEmpty(name)) { return `You must enter a ${fieldName} name`; } if (name.length > MAX_FEATURE_NAME_SIZE) { return `Name is too big maximum limit is ${MAX_FEATURE_NAME_SIZE}`; } if (!NAME_REGEX.test(name)) { return 'Valid characters are a-z, A-Z, 0-9, -(hyphen) and _(underscore)'; } }; export const validateCustomResultIndex = (name: string): string | undefined => { if (isEmpty(name)) { return `You must enter a index name`; } let resultIndexName = CUSTOM_AD_RESULT_INDEX_PREFIX + name; if (resultIndexName.length > MAX_INDEX_NAME_SIZE) { return `Index name is too long, maximum limit is ${MAX_INDEX_NAME_SIZE}`; } if (!INDEX_NAME_REGEX.test(resultIndexName)) { return 'Valid characters are a-z, 0-9, -(hyphen) and _(underscore)'; } }; export const isInvalid = (name: string, form: any) => !!get(form.touched, name, false) && !!get(form.errors, name, false); export const getError = (name: string, form: any) => get(form.errors, name); export const requiredSelectField = (val: any): string | undefined => { return required(val, 'You must select a field'); }; export const required = ( val: any, customErrorMessage?: string ): string | undefined => { // if val is number, skip check as number value already exists const message = !isEmpty(customErrorMessage) ? customErrorMessage : 'Required'; return typeof val !== 'number' && !val ? message : undefined; }; export const requiredNonEmptyArray = ( val: any, customErrorMessage?: string ): string | undefined => { const message = !isEmpty(customErrorMessage) ? customErrorMessage : 'Required'; return !val || val.length === 0 ? message : undefined; }; export const requiredNonEmptyFieldSelected = (val: any): string | undefined => { return requiredNonEmptyArray(val, 'You must select a field'); }; export const validateCategoryField = (val: any): string | undefined => { return requiredNonEmptyArray(val, 'You must select a category field'); }; export const validatePositiveInteger = (value: any) => { if (!Number.isInteger(value) || value < 1) return 'Must be a positive integer'; }; export const validateNonNegativeInteger = (value: any) => { if (!Number.isInteger(value) || value < 0) return 'Must be a non-negative integer'; }; export const getErrorMessage = (err: any, defaultMessage: string) => { if (typeof err === 'string') return err; if (err && err.message) return err.message; return defaultMessage; }; const getPluginRootPath = (url: string, pluginName: string) => { return url.slice(0, url.indexOf(pluginName) + pluginName.length); }; export const getAlertingCreateMonitorLink = ( detectorId: string, detectorName: string, detectorInterval: number, unit: string, resultIndex?: string ): string => { try { const core = React.useContext(CoreServicesContext) as CoreStart; const navLinks = get(core, 'chrome.navLinks', undefined); const url = `${navLinks.get(ALERTING_PLUGIN_NAME)?.url}`; const alertingRootUrl = getPluginRootPath(url, ALERTING_PLUGIN_NAME); return !resultIndex ? `${alertingRootUrl}#/create-monitor?searchType=ad&adId=${detectorId}&name=${detectorName}&interval=${ 2 * detectorInterval }&unit=${unit}` : `${alertingRootUrl}#/create-monitor?searchType=ad&adId=${detectorId}&name=${detectorName}&interval=${ 2 * detectorInterval }&unit=${unit}&adResultIndex=${resultIndex}`; } catch (e) { console.error('unable to get the alerting URL', e); return ''; } }; export const getAlertingMonitorListLink = (): string => { try { const core = React.useContext(CoreServicesContext) as CoreStart; const navLinks = get(core, 'chrome.navLinks', undefined); const url = `${navLinks.get(ALERTING_PLUGIN_NAME).url}`; const alertingRootUrl = getPluginRootPath(url, ALERTING_PLUGIN_NAME); return `${alertingRootUrl}#/monitors`; } catch (e) { console.error('unable to get the alerting URL', e); return ''; } }; export interface Listener { onSuccess(): void; onException(): void; } const detectorCountFontColor = () => darkModeEnabled() ? '#98A2B3' : '#535966'; export const getTitleWithCount = (title: string, count: number | string) => { return (

{title} 

{`(${count})`}

); }; export function convertTimestampToString(timestamp: number | string) { if (typeof timestamp === 'string') { return timestamp; } return moment(timestamp).format('MM/DD/YYYY hh:mm A'); } export function convertTimestampToNumber(timestamp: number | string) { if (typeof timestamp === 'string') { return datemath.parse(timestamp)?.valueOf(); } return timestamp; } export function getHistoricalRangeString(detector: Detector) { if (!detector?.detectionDateRange) { return '-'; } else { const startTimeAsNumber = convertTimestampToNumber( get(detector, 'detectionDateRange.startTime', 0) ); const endTimeAsNumber = convertTimestampToNumber( get(detector, 'detectionDateRange.endTime', 0) ); return ( moment(startTimeAsNumber).format('MMM DD, YYYY @ hh:mm A') + ' - ' + moment(endTimeAsNumber).format('MMM DD, YYYY @ hh:mm A') ); } }