import { toNumber, toString, memoize, zip } from 'lodash'; import React, { useState } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators, Dispatch } from 'redux'; import { requestQuestionWithParameters } from 'wdk-client/Actions/QuestionWithParametersActions'; import { requestUpdateStepSearchConfig } from 'wdk-client/Actions/StrategyActions'; import { CollapsibleSection, IconAlt } from 'wdk-client/Components'; import { getFilterValueDisplay } from 'wdk-client/Components/AttributeFilter/AttributeFilterUtils'; import { FilterWithFieldDisplayName } from 'wdk-client/Components/AttributeFilter/Types'; import { makeClassNameHelper } from 'wdk-client/Utils/ComponentUtils'; import { Plugin } from 'wdk-client/Utils/ClientPlugin'; import { preorderSeq } from 'wdk-client/Utils/TreeUtils'; import { Parameter, EnumParam, DatasetParam, QuestionWithParameters } from 'wdk-client/Utils/WdkModel'; import { Step } from 'wdk-client/Utils/WdkUser'; import { isEnumParam, isMultiPick, toMultiValueArray } from 'wdk-client/Views/Question/Params/EnumParamUtils'; import { datasetItemToString, DatasetItem } from 'wdk-client/Views/Question/Params/DatasetParamUtils'; import { StepBoxProps, StepDetailProps, UiStepTree } from 'wdk-client/Views/Strategy/Types'; import { useWdkService } from 'wdk-client/Hooks/WdkServiceHook'; import './StepDetails.scss'; const cx = makeClassNameHelper('StepDetails'); interface DispatchProps { assignWeight: (weight: number) => void; } export type LeafStepDetailsProps = StepDetailProps & DispatchProps; function StepDetails(props: LeafStepDetailsProps) { return ( ); } export function DefaultStepDetails(props: LeafStepDetailsProps) { const { stepTree: { step } } = props; const { question, datasetParamItems } = useStepDetailsData(step); const { weight, weightCollapsed, setWeightCollapsed } = useStepDetailsWeightControls(step); return ( ); } export function useStepDetailsWeightControls(step: Step) { const [ weightCollapsed, setWeightCollapsed ] = useState(true); const weight = toString(step.searchConfig.wdkWeight); return { setWeightCollapsed, weightCollapsed, weight }; } export function useStepDetailsData(step: Step) { const rawData = useWdkService(async wdkService => { const question = await wdkService.getQuestionGivenParameters(step.searchName, step.searchConfig.parameters); const nonemptyDatasetParams = question.parameters .filter( ({ name, type }) => ( type === 'input-dataset' && step.searchConfig.parameters[name] ) ); const datasetParamItemArrays = await Promise.all( nonemptyDatasetParams.map( ({ name }) => wdkService.getDataset(+step.searchConfig.parameters[name]) ) ); const paramsWithItemValues = zip(nonemptyDatasetParams, datasetParamItemArrays); const datasetParamItems = paramsWithItemValues.reduce( (memo, [ param, itemArray ]) => ({ ...memo, [(param as Parameter).name]: itemArray as DatasetItem[] }), {} as Record ); return { datasetParamItems, question }; }, [ step ]); return rawData ?? { datasetParamItems: undefined, question: undefined }; } export interface LeafStepDetailsContentProps extends LeafStepDetailsProps { question?: QuestionWithParameters; datasetParamItems?: Record; weight: string; weightCollapsed: boolean; setWeightCollapsed: (isCollapsed: boolean) => void; } export function DefaultStepDetailsContent({ question, datasetParamItems, stepTree: { step }, assignWeight, weight, weightCollapsed, setWeightCollapsed } : LeafStepDetailsContentProps) { return ( {question?.parameters .filter(parameter => parameter.isVisible) .map(parameter =>( ))}
{parameter.displayName} {formatParameterValue(parameter, step.searchConfig.parameters[parameter.name], datasetParamItems) || No value}
{ e.preventDefault(); const weightInput = e.currentTarget.elements.namedItem('weight'); if (weightInput == null || !(weightInput instanceof HTMLInputElement)) { throw new Error("Could not find the weight input."); } const wdkWeight = toNumber(weightInput.value); assignWeight(wdkWeight); }}>
Optionally give this search a 'weight' (for example 10, 200, -50). In a search strategy, unions and intersects will sum the weights, giving higher scores to items found in multiple searches.
); } function formatParameterValue( parameter: Parameter, value: string | undefined, datasetParamItems: Record | undefined ) { if ( !value || parameter.type === 'string' || parameter.type === 'number' || parameter.type === 'date' || parameter.type === 'timestamp' || parameter.type === 'input-step' ) { return value; } else if (parameter.type === 'date-range' || parameter.type === 'number-range') { return formatRangeParameterValue(value); } else if (isEnumParam(parameter)) { return formatEnumParameterValue(parameter, value); } else if (parameter.type === 'filter') { return formatFilterValue(value); } else { return formatDatasetValue(parameter, datasetParamItems); } } function formatRangeParameterValue(value: string) { try { const { min, max } = JSON.parse(value); return min !== undefined && max !== undefined ? `min:${min},max:${max}` : value; } catch { return value; } } function formatEnumParameterValue(parameter: EnumParam, value: string) { const valueSet = new Set(isMultiPick(parameter) ? toMultiValueArray(value) : [ value ]); const termDisplayPairs = makeTermDisplayPairs(parameter.vocabulary); return termDisplayPairs .filter(([term]) => valueSet.has(term)) .map(([, display]) => display) .join(', '); } const makeTermDisplayPairs = memoize((vocabulary: EnumParam['vocabulary']): [string, string, null][] => Array.isArray(vocabulary) ? vocabulary : preorderSeq(vocabulary) .filter(node => node.children.length === 0) .map((node): [ string, string, null ] => [ node.data.term, node.data.display, null ]) .toArray() ); function formatFilterValue(value: string) { try { const { filters } = JSON.parse(value) as { filters: FilterWithFieldDisplayName[] }; return filters.flatMap((filter, i, coll) => {filter.fieldDisplayName || filter.field}: {getFilterValueDisplay(filter)} {i < coll.length - 1 ? <>

: null}
); } catch { return value; } } function formatDatasetValue( parameter: DatasetParam, datasetParamItems: Record | undefined ) { return !datasetParamItems ? : datasetParamItems[parameter.name] .map(datasetItemToString) .join(', '); } function mapDispatchToProps(dispatch: Dispatch, props: StepBoxProps): DispatchProps { const { step } = props.stepTree; return bindActionCreators({ requestQuestionWithParameters, assignWeight: (wdkWeight: number) => requestUpdateStepSearchConfig( step.strategyId, step.id, { ...step.searchConfig, wdkWeight } ) }, dispatch) } export default connect(null, mapDispatchToProps)(StepDetails);