/* * 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 React, { useState } from 'react'; import { EuiBasicTable, EuiLink, EuiButton, EuiSpacer, EuiCallOut, EuiLoadingSpinner, EuiFlexGroup, EuiText, } from '@elastic/eui'; import { Detector, FEATURE_TYPE, FeatureAttributes, ValidationModelResponse, } from '../../../../models/interfaces'; import { get, sortBy, isEqual } from 'lodash'; import ContentPanel from '../../../../components/ContentPanel/ContentPanel'; import { CodeModal } from '../../../../components/CodeModal/CodeModal'; import { AdditionalSettings } from '../AdditionalSettings/AdditionalSettings'; import { getTitleWithCount } from '../../../../utils/utils'; import { getShingleSizeFromObject } from '../../../ConfigureModel/utils/helpers'; import { SORT_DIRECTION } from '../../../../../server/utils/constants'; interface ModelConfigurationFieldsProps { detector: Detector; onEditModelConfiguration(): void; validationFeatureResponse: ValidationModelResponse; validModel: boolean; validationError: boolean; isLoading: boolean; isCreatingDetector: boolean; } interface ModelConfigurationFieldsState { showCodeModel: boolean[]; sortField: string; sortDirection: SORT_DIRECTION; } export const ModelConfigurationFields = ( props: ModelConfigurationFieldsProps ) => { const [featuresState, setFeaturesState] = useState({ showCodeModel: get(props.detector, 'featureAttributes', []).map( () => false ), sortField: 'name', sortDirection: SORT_DIRECTION.ASC, }); const closeModal = (index: number) => { const cloneShowCodeModal = [...featuresState.showCodeModel]; cloneShowCodeModal[index] = false; setFeaturesState({ ...featuresState, showCodeModel: cloneShowCodeModal, }); }; const showModal = (index: number) => { const cloneShowCodeModal = [...featuresState.showCodeModel]; cloneShowCodeModal[index] = true; setFeaturesState({ ...featuresState, showCodeModel: cloneShowCodeModal, }); }; const getModalVisibilityChange = (index: number) => { return featuresState.showCodeModel[index]; }; const handleTableChange = (props: any) => { setFeaturesState({ ...featuresState, sortField: props.sort.field, sortDirection: props.sort.direction, }); }; const getSortedItems = (items: Array) => { let sorted = sortBy(items, featuresState.sortField); if (featuresState.sortDirection == SORT_DIRECTION.DESC) { sorted = sorted.reverse(); } return sorted; }; const featureAttributes = get(props.detector, 'featureAttributes', []); const shingleSize = getShingleSizeFromObject(props.detector); const sorting = { sort: { field: featuresState.sortField, direction: featuresState.sortDirection, }, }; const items = featureAttributes.map( (feature: FeatureAttributes, index: number) => ({ name: feature.featureName, definition: index, state: feature.featureEnabled ? 'Enabled' : 'Disabled', }) ); const sortedItems = getSortedItems(items); const columns = [ { field: 'name', name: 'Feature name', sortable: true, }, { field: 'definition', name: 'Feature definition', render: (featureIndex: number) => { const feature = featureAttributes[featureIndex]; const metaData = get( props.detector, `uiMetadata.features.${feature.featureName}`, {} ); if ( Object.keys(metaData).length === 0 || metaData.featureType == FEATURE_TYPE.CUSTOM ) { return (

Custom expression:{' '} showModal(featureIndex)} > View code

{!getModalVisibilityChange(featureIndex) ? null : ( closeModal(featureIndex)} getModalVisibilityChange={() => getModalVisibilityChange(featureIndex) } /> )}
); } else { return (

Field: {metaData.aggregationOf || ''}

Aggregation method: {metaData.aggregationBy || ''}

); } }, }, { field: 'state', name: 'Feature state', }, ]; const getCellProps = () => { return { textOnly: true, }; }; const getFeatureValidationCallout = ( issueResponse: ValidationModelResponse ) => { if (issueResponse != undefined) { if (issueResponse.sub_issues != undefined) { const renderList = Object.keys(issueResponse.sub_issues).map((key) => { return ( //@ts-ignore
  • {'The "' + key + '" ' + issueResponse.sub_issues[key]}
  • ); }); return ; } else { return ( ); } } else { return null; } }; const getValidationCallout = () => { //When validation response is loading then displaying loading spinner, don't display // after clicking on "create detector" button as isLoading will be true from that request if (props.isLoading && !props.isCreatingDetector) { return (

    Validating model configurations

    } style={{ marginBottom: '10px' }} size="s" color="primary" /> ); } if (props.validationError) { return null; } if (props.validModel) { return ( ); // makes sure there is a response to display and model configs aren't valid per // validation API } if ( !props.validModel && props.validationFeatureResponse.hasOwnProperty('message') ) { if ( isEqual( get(props, 'validationFeatureResponse.validationType', ''), 'model' ) ) { return ( {JSON.stringify(props.validationFeatureResponse.message).replace( /\"/g, '' )} ); } else { return ( {/* Callout can either display feature subissue which related to a specific feature issue or display a feature_attribute issue that is general like more then x anomaly feature or dulicate feature names */} {props.validationFeatureResponse.hasOwnProperty('sub_issues') ? ( getFeatureValidationCallout(props.validationFeatureResponse) ) : (
    • {JSON.stringify(props.validationFeatureResponse.message)}
    )}
    ); } } else { return null; } }; const featureNum = Object.keys(featureAttributes).length; return ( Edit, ]} > {getValidationCallout()}
    ); };