diff --git a/app/components/LeftPaneEntries/AssistItem/AssistPopup/index.tsx b/app/components/LeftPaneEntries/AssistItem/AssistPopup/index.tsx index b4581b14b8..57c685c8cd 100644 --- a/app/components/LeftPaneEntries/AssistItem/AssistPopup/index.tsx +++ b/app/components/LeftPaneEntries/AssistItem/AssistPopup/index.tsx @@ -12,11 +12,10 @@ import { import EntryInput from '#components/entry/EntryInput'; import { GeoArea } from '#components/GeoMultiSelectInput'; -import { WidgetHint } from '#types/newAnalyticalFramework'; import { Framework } from '#components/entry/types'; import { - PartialEntryType, PartialAttributeType, + PartialEntryType, } from '#components/entry/schema'; import styles from './styles.css'; @@ -35,8 +34,7 @@ interface Props { geoAreaOptions: GeoArea[] | undefined | null; onGeoAreaOptionsChange: React.Dispatch>; predictionsLoading?: boolean; - hints: WidgetHint[] | undefined; - recommendations: PartialAttributeType[] | undefined; + recommendations?: PartialAttributeType[] | undefined; predictionsErrored: boolean; name: NAME; messageText: string | undefined; @@ -62,7 +60,6 @@ function AssistPopup(props: Props(props: Props { const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( (item) => (item.widgets ?? []), @@ -188,20 +161,12 @@ function AssistItem(props: Props) { ]; return { allWidgets: widgets, - filteredWidgets: widgets.filter((w) => mappingsSupportedWidgets.includes(w.widgetId)), }; }, [ frameworkDetails, ]); - const mappings = frameworkDetails?.predictionTagsMapping; const alert = useAlert(); - const [ - allRecommendations, - setAllRecommendations, - ] = useState(undefined); - - const [allHints, setAllHints] = useState(undefined); const assistPopupRef = useRef< { setShowPopup: React.Dispatch> } @@ -260,165 +225,47 @@ function AssistItem(props: Props) { error, } = useForm(schema, emptyEntry); - const [messageText, setMessageText] = useState(); + const [messageText] = useState(); - const handleMappingsFetch = useCallback(( - predictions: { tags: string[]; locations: GeoArea[]; }, - ) => { - if (predictions.tags.length <= 0 && predictions.locations.length <= 0) { - setMessageText('DEEP could not provide any recommendations for the selected text.'); - return; - } - - setGeoAreaOptions(predictions.locations); - - const matchedMappings = mappings - ?.filter(isCategoricalMappings) - .filter((m) => m.tag && predictions.tags.includes(m.tag)); - - const supportedGeoWidgets = mappings - ?.filter((mappingItem) => mappingItem.widgetType === 'GEO') - ?.map((mappingItem) => mappingItem.widget); - - const { - tempAttrs: recommendedAttributes, - tempHints: widgetsHints, - } = filteredWidgets.reduce( - ( - acc: { tempAttrs: PartialAttributeType[]; tempHints: WidgetHint[]; }, - widget, - ) => { - const { - tempAttrs: oldTempAttrs, - tempHints: oldTempHints, - } = acc; - - if (widget.widgetId === 'MATRIX1D') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMatrix1dMappings); - - const attr = createMatrix1dAttr(supportedTags, widget); - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if (widget.widgetId === 'MATRIX2D') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMatrix2dMappings); - - const attr = createMatrix2dAttr(supportedTags, widget); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if (widget.widgetId === 'SCALE') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterScaleMappings); - - const { - attr, - hints, - } = createScaleAttr(supportedTags, widget); - - const hintsWithInfo: WidgetHint | undefined = hints ? { - widgetPk: widget.id, - widgetType: 'SCALE', - hints, - } : undefined; - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: hintsWithInfo ? [...oldTempHints, hintsWithInfo] : oldTempHints, - }; - } - if (widget.widgetId === 'SELECT') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterSelectMappings); - - const { - attr, - hints, - } = createSelectAttr(supportedTags, widget); - - const hintsWithInfo: WidgetHint | undefined = hints ? { - widgetPk: widget.id, - widgetType: 'SELECT', - hints, - } : undefined; - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: hintsWithInfo ? [...oldTempHints, hintsWithInfo] : oldTempHints, - }; - } - if (widget.widgetId === 'MULTISELECT') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMultiSelectMappings); - - const attr = createMultiSelectAttr( - supportedTags, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if (widget.widgetId === 'ORGANIGRAM') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterOrganigramMappings); - - const attr = createOrganigramAttr( - supportedTags, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if ( - widget.widgetId === 'GEO' - && predictions.locations.length > 0 - && supportedGeoWidgets?.includes(widget.id) - ) { - const attr = createGeoAttr( - predictions.locations, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - return acc; - }, - { - tempAttrs: [], - tempHints: [], - }, - ); + const [draftEntryId, setDraftEntryId] = useState(undefined); + // FIXME: randomId is used to create different query variables after each poll + // so that apollo doesn't create unnecessary cache + const [randomId, setRandomId] = useState(randomString()); + const [predictionsLoading, setPredictionsLoading] = useState(false); - if (recommendedAttributes.length <= 0 && widgetsHints.length <= 0) { - setMessageText('The provided recommendations for this text did not fit any tags in this project.'); - return; - } + const queryVariables = useMemo(() => ( + draftEntryId && projectId ? ({ + projectId, + draftEntryId, + randomId, + }) : undefined + ), [ + randomId, + projectId, + draftEntryId, + ]); - setAllHints(widgetsHints); - setAllRecommendations(recommendedAttributes); + const [isErrored, setIsErrored] = useState(false); + const [recommendations, setRecommendations] = useState(); + + const handleTagsFetch = useCallback((recommendedTags: ModelTagsType) => { + const newAttributes = allWidgets?.map((widget) => { + if (widget.widgetId === 'MATRIX1D') { + return createMatrix1dAttrFromTags( + recommendedTags[widget.key] as Matrix1dValue, + widget, + ); + } + if (widget.widgetId === 'MATRIX2D') { + return createMatrix2dAttrFromTags( + recommendedTags[widget.key] as Matrix2dValue, + widget, + ); + } + return undefined; + }).filter(isDefined); + setRecommendations(newAttributes); setValue( (oldEntry) => { if (!oldEntry) { @@ -428,67 +275,24 @@ function AssistItem(props: Props) { lead: leadId, excerpt: text, droppedExcerpt: text, - attributes: recommendedAttributes.map((attr) => { - if (attr.widgetType !== 'GEO') { - return attr; - } - // NOTE: Selecting only the 1st recommendation - return ({ - ...attr, - data: { - value: attr?.data?.value.slice(0, 1) ?? [], - }, - }); - }), + attributes: newAttributes, }; } return { ...oldEntry, - attributes: recommendedAttributes.map((attr) => { - if (attr.widgetType !== 'GEO') { - return attr; - } - // NOTE: Selecting only the 1st recommendation - return ({ - ...attr, - data: { - value: attr?.data?.value.slice(0, 1) ?? [], - }, - }); - }), + attributes: newAttributes, }; }, undefined, ); }, [ - text, + allWidgets, leadId, - mappings, setValue, - filteredWidgets, - ]); - - const [draftEntryId, setDraftEntryId] = useState(undefined); - // FIXME: randomId is used to create different query variables after each poll - // so that apollo doesn't create unnecessary cache - const [randomId, setRandomId] = useState(randomString()); - const [predictionsLoading, setPredictionsLoading] = useState(false); - - const queryVariables = useMemo(() => ( - draftEntryId && projectId ? ({ - projectId, - draftEntryId, - randomId, - }) : undefined - ), [ - randomId, - projectId, - draftEntryId, + text, ]); - const [isErrored, setIsErrored] = useState(false); - const { loading: draftEntryFetchPending, data, @@ -519,24 +323,12 @@ function AssistItem(props: Props) { return; } - const validPredictions = result?.predictionTags?.filter(isDefined); - - /* - const geoPredictions = validPredictions?.map( - (prediction) => prediction.value, - ) ?? []; - */ - - const categoricalTags = validPredictions?.filter( - (prediction) => prediction.isSelected, - ).map( - (prediction) => prediction.tag, - ).filter(isDefined) ?? []; + const modelTags = result?.tags?.modelTags; + if (!isValidObject(modelTags)) { + return; + } - handleMappingsFetch({ - tags: categoricalTags, - locations: result.geoAreas?.filter(isDefined) ?? [], - }); + handleTagsFetch(modelTags as ModelTagsType); }, onError: () => { alert.show( @@ -604,10 +396,6 @@ function AssistItem(props: Props) { ]); const handleEntryCreateButtonClick = useCallback(() => { - if (!allRecommendations) { - return; - } - const submit = createSubmitHandler( validate, setError, @@ -646,7 +434,6 @@ function AssistItem(props: Props) { data, allWidgets, geoAreaOptions, - allRecommendations, validate, setError, onAssistedEntryAdd, @@ -735,12 +522,11 @@ function AssistItem(props: Props) { ; type Matrix2dWidgetAttribute = getType; +/* type ScaleWidgetAttribute = getType; type SingleSelectWidgetAttribute = getType; type MultiSelectWidgetAttribute = getType; type OrganigramWidgetAttribute = getType; type GeoLocationWidgetAttribute = getType; +*/ + +export function isValidObject(value: unknown | undefined): value is Record { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +export function isValidStringArray(value: unknown | undefined): value is string[] { + return Array.isArray(value) && value.every((item) => typeof item === 'string'); +} -export function createMatrix1dAttr( - mappings: Matrix1dMappingsItem[] | undefined, +// TODO: Write tests +export function createMatrix1dAttrFromTags( + unsafeMappings: Matrix1dValue | undefined, widget: Matrix1dWidget, ): Matrix1dWidgetAttribute | undefined { - if (!mappings || mappings.length <= 0) { + // NOTE: We are supposed to get Matrix1dValue, but adding one more validation layer + const mappings = unsafeMappings as unknown; + if (!mappings || !isValidObject(mappings)) { return undefined; } - const mappingsGroupedByRows = listToGroupList( - mappings, - (m) => m.association.rowKey, - (m) => m.association.subRowKey, + + const cellKeysByRowKey = listToMap( + widget.properties?.rows, + (row) => row.key, + (row) => row.cells.map((cell) => cell.key), ); - const value = mapToMap( - mappingsGroupedByRows, - (m) => m, - (d) => listToMap(d, (k) => k, () => true), + const rowKeys = widget.properties?.rows?.map((item) => item.key); + + const validRowKeys = Object.keys(mappings).filter((item) => rowKeys?.includes(item)); + + const value: Matrix1dValue = listToMap( + validRowKeys, + (validRowKey) => validRowKey, + (validRowKey) => { + const rowDetails = mappings[validRowKey]; + if (!isValidObject(rowDetails)) { + return undefined; + } + const cellKeys = Object.keys(rowDetails); + const validCellKeys = cellKeys.filter((item) => ( + cellKeysByRowKey?.[validRowKey].includes(item) + )); + + return listToMap(validCellKeys, (cellKey) => cellKey, () => true); + }, ); return { @@ -70,74 +83,76 @@ export function createMatrix1dAttr( }; } -interface SubRowMap { - type: 'SUB_ROW'; - rowKey: string; - subRowKey: string; -} - -type ColumnMap = { - type: 'COLUMN'; - columnKey: string; -} | { - type: 'SUB_COLUMN'; - columnKey: string; - subColumnKey: string; -}; - -export function filterColumn( - mappingsItem: Matrix2dMappingsItem, -): mappingsItem is DeepReplace { - return mappingsItem.association.type === 'COLUMN' || mappingsItem.association.type === 'SUB_COLUMN'; -} - -export function filterSubRows( - mappingsItem: Matrix2dMappingsItem, -): mappingsItem is DeepReplace { - return mappingsItem.association.type === 'SUB_ROW'; -} - -export function createMatrix2dAttr( - mappings: Matrix2dMappingsItem[] | undefined, +// TODO: Write tests +export function createMatrix2dAttrFromTags( + unsafeMappings: Matrix2dValue | undefined, widget: Matrix2dWidget, ): Matrix2dWidgetAttribute | undefined { - if (!mappings || mappings.length <= 0) { + // NOTE: We are supposed to get Matrix1dValue, but adding one more validation layer + const mappings = unsafeMappings as unknown; + if (!mappings || !isValidObject(mappings)) { return undefined; } - const columns = mappings.filter(filterColumn); - const rows = mappings.filter(filterSubRows); - if (columns.length === 0 || rows.length === 0) { - return undefined; - } - - const groupedCols = listToGroupList( - columns, - (col) => col.association.columnKey, - // NOTE: We need empty array for Columns while we need non-empty array for sub columns - (col) => (col.association.type === 'SUB_COLUMN' ? col.association.subColumnKey : undefined), - ); - - const transformedCols = mapToMap( - groupedCols, - (colKey) => colKey, - (subColumns) => subColumns.filter(isDefined), + const subRowKeysByRowKey = listToMap( + widget.properties?.rows, + (row) => row.key, + (row) => row.subRows.map((subRow) => subRow.key), ); - const groupedSubRows = listToGroupList( - rows, - (col) => col.association.rowKey, - (col) => col.association.subRowKey, + const subColumnKeysByColumnKey = listToMap( + widget.properties?.columns, + (row) => row.key, + (row) => row.subColumns.map((subColumn) => subColumn.key), ); - const value = mapToMap( - groupedSubRows, - (rowKey) => rowKey, - (subRows) => listToMap( - subRows, - (k) => k, - () => transformedCols, - ), + const rowKeys = widget.properties?.rows?.map((item) => item.key); + const columnKeys = widget.properties?.columns?.map((item) => item.key); + + const validRowKeys = Object.keys(mappings).filter((item) => rowKeys?.includes(item)); + + const value: Matrix2dValue = listToMap( + validRowKeys, + (validRowKey) => validRowKey, + (validRowKey) => { + const rowDetails = mappings[validRowKey]; + if (!isValidObject(rowDetails)) { + return undefined; + } + const subRowKeys = Object.keys(rowDetails); + const validsubRowKeys = subRowKeys.filter((item) => ( + subRowKeysByRowKey?.[validRowKey].includes(item) + )); + + return listToMap( + validsubRowKeys, + (subRowKey) => subRowKey, + (subRowKey) => { + const subRowData = rowDetails[subRowKey]; + if (!isValidObject(subRowData)) { + return undefined; + } + const unSafeColumnKeys = Object.keys(subRowData); + const validColumnKeys = unSafeColumnKeys.filter( + (item) => columnKeys?.includes(item), + ); + + return listToMap( + validColumnKeys, + (columnKey) => columnKey, + (columnKey) => { + const columnData = subRowData[columnKey]; + if (!isValidStringArray(columnData)) { + return undefined; + } + return columnData.filter( + (item) => subColumnKeysByColumnKey?.[columnKey].includes(item), + ); + }, + ); + }, + ); + }, ); return { @@ -148,131 +163,3 @@ export function createMatrix2dAttr( data: { value }, }; } - -export function createScaleAttr( - mappings: ScaleMappingsItem[] | undefined, - widget: ScaleWidget, -): { - attr: ScaleWidgetAttribute | undefined; - hints: string[] | undefined; -} { - if (!mappings || mappings.length <= 0) { - return { - attr: undefined, - hints: undefined, - }; - } - - if (mappings.length === 1) { - return { - attr: { - clientId: randomString(), - widget: widget.id, - widgetVersion: widget.version, - widgetType: 'SCALE' as const, - data: { - value: mappings[0].association.optionKey, - }, - }, - hints: mappings.map((m) => m.association.optionKey), - }; - } - - return ({ - attr: undefined, - hints: mappings.map((m) => m.association.optionKey), - }); -} - -export function createSelectAttr( - mappings: SelectMappingsItem[] | undefined, - widget: SingleSelectWidget, -): { - attr: SingleSelectWidgetAttribute | undefined; - hints: string[] | undefined; -} { - if (!mappings || mappings.length <= 0) { - return { - attr: undefined, - hints: undefined, - }; - } - - if (mappings.length === 1) { - return { - attr: { - clientId: randomString(), - widget: widget.id, - widgetVersion: widget.version, - widgetType: 'SELECT' as const, - data: { - value: mappings[0].association.optionKey, - }, - }, - hints: mappings.map((m) => m.association.optionKey), - }; - } - - return ({ - attr: undefined, - hints: mappings.map((m) => m.association.optionKey), - }); -} - -export function createMultiSelectAttr( - mappings: MultiSelectMappingsItem[] | undefined, - widget: MultiSelectWidget, -): MultiSelectWidgetAttribute | undefined { - if (!mappings || mappings.length <= 0) { - return undefined; - } - - return ({ - clientId: randomString(), - widget: widget.id, - widgetVersion: widget.version, - widgetType: 'MULTISELECT' as const, - data: { - value: mappings.map((m) => m.association.optionKey), - }, - }); -} - -export function createOrganigramAttr( - mappings: OrganigramMappingsItem[] | undefined, - widget: OrganigramWidget, -): OrganigramWidgetAttribute | undefined { - if (!mappings || mappings.length <= 0) { - return undefined; - } - - return ({ - clientId: randomString(), - widget: widget.id, - widgetVersion: widget.version, - widgetType: 'ORGANIGRAM' as const, - data: { - value: mappings.map((m) => m.association.optionKey), - }, - }); -} - -export function createGeoAttr( - locations: GeoArea[] | undefined, - widget: GeoLocationWidget, -): GeoLocationWidgetAttribute | undefined { - if (!locations || locations.length <= 0) { - return undefined; - } - - return ({ - clientId: randomString(), - widget: widget.id, - widgetVersion: widget.version, - widgetType: 'GEO' as const, - data: { - // NOTE: We are reducing the amount of suggestions from geo areas - value: locations.map((location) => location.id).slice(0, 3), - }, - }); -} diff --git a/app/components/LeftPaneEntries/AutoEntriesModal/index.tsx b/app/components/LeftPaneEntries/AutoEntriesModal/index.tsx index c2f48a3ec6..8e07b13125 100644 --- a/app/components/LeftPaneEntries/AutoEntriesModal/index.tsx +++ b/app/components/LeftPaneEntries/AutoEntriesModal/index.tsx @@ -34,18 +34,6 @@ import { import { mergeLists } from '#utils/common'; import { type Framework } from '#components/entry/types'; import { type GeoArea } from '#components/GeoMultiSelectInput'; -import { - mappingsSupportedWidgets, - isCategoricalMappings, - WidgetHint, - filterMatrix1dMappings, - filterMatrix2dMappings, - filterScaleMappings, - filterSelectMappings, - filterMultiSelectMappings, - filterOrganigramMappings, - type MappingsItem, -} from '#types/newAnalyticalFramework'; import getSchema, { defaultFormValues, PartialEntryType, @@ -61,16 +49,17 @@ import { UpdateDraftEntryMutation, UpdateDraftEntryMutationVariables, } from '#generated/types'; +import { + ModelTagsType, + Matrix1dValue, + Matrix2dValue, +} from '#types/newAnalyticalFramework'; import AssistPopup from '../AssistItem/AssistPopup'; import { createDefaultAttributes } from '../utils'; import { - createOrganigramAttr, - createMatrix1dAttr, - createMatrix2dAttr, - createScaleAttr, - createSelectAttr, - createMultiSelectAttr, - createGeoAttr, + isValidObject, + createMatrix1dAttrFromTags, + createMatrix2dAttrFromTags, } from '../AssistItem/utils'; import styles from './styles.css'; @@ -103,17 +92,9 @@ const AUTO_ENTRIES_FOR_LEAD = gql` excerpt predictionReceivedAt predictionStatus - predictionTags { + tags { id - draftEntry - tag - dataTypeDisplay - dataType - category - isSelected - prediction - threshold - value + modelTags } geoAreas { adminLevelLevel @@ -184,190 +165,6 @@ const UPDATE_DRAFT_ENTRY = gql` } `; -interface EntryAttributes { - predictions: { - tags: string[]; - locations: GeoArea[]; - }; - mappings: MappingsItem[] | null | undefined; - filteredWidgets: NonNullable[number]['widgets'] - | NonNullable; -} - -function handleMappingsFetch(entryAttributes: EntryAttributes) { - const { - predictions, - mappings, - filteredWidgets, - } = entryAttributes; - - if (predictions.tags.length <= 0 && predictions.locations.length <= 0) { - // setMessageText('DEEP could not provide any recommendations for the selected text.'); - return {}; - } - - if (isNotDefined(filteredWidgets)) { - return {}; - } - - const matchedMappings = mappings - ?.filter(isCategoricalMappings) - .filter((m) => m.tag && predictions.tags.includes(m.tag)); - - const supportedGeoWidgets = mappings - ?.filter((mappingItem) => mappingItem.widgetType === 'GEO') - ?.map((mappingItem) => mappingItem.widget); - - const { - tempAttrs: recommendedAttributes, - tempHints: widgetsHints, - } = filteredWidgets.reduce( - ( - acc: { tempAttrs: PartialAttributeType[]; tempHints: WidgetHint[]; }, - widget, - ) => { - const { - tempAttrs: oldTempAttrs, - tempHints: oldTempHints, - } = acc; - - if (widget.widgetId === 'MATRIX1D') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMatrix1dMappings); - - const attr = createMatrix1dAttr(supportedTags, widget); - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - - if (widget.widgetId === 'MATRIX2D') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMatrix2dMappings); - - const attr = createMatrix2dAttr(supportedTags, widget); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if (widget.widgetId === 'SCALE') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterScaleMappings); - - const { - attr, - hints, - } = createScaleAttr(supportedTags, widget); - - const hintsWithInfo: WidgetHint | undefined = hints ? { - widgetPk: widget.id, - widgetType: 'SCALE', - hints, - } : undefined; - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: hintsWithInfo - ? [...oldTempHints, hintsWithInfo] - : oldTempHints, - }; - } - if (widget.widgetId === 'SELECT') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterSelectMappings); - - const { - attr, - hints, - } = createSelectAttr(supportedTags, widget); - - const hintsWithInfo: WidgetHint | undefined = hints ? { - widgetPk: widget.id, - widgetType: 'SELECT', - hints, - } : undefined; - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: hintsWithInfo - ? [...oldTempHints, hintsWithInfo] - : oldTempHints, - }; - } - if (widget.widgetId === 'MULTISELECT') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterMultiSelectMappings); - - const attr = createMultiSelectAttr( - supportedTags, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if (widget.widgetId === 'ORGANIGRAM') { - const supportedTags = matchedMappings - ?.filter((m) => m.widget === widget.id) - .filter(filterOrganigramMappings); - - const attr = createOrganigramAttr( - supportedTags, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - if ( - widget.widgetId === 'GEO' - && predictions.locations.length > 0 - && supportedGeoWidgets?.includes(widget.id) - ) { - const attr = createGeoAttr( - predictions.locations, - widget, - ); - - return { - tempAttrs: attr ? [...oldTempAttrs, attr] : oldTempAttrs, - tempHints: oldTempHints, - }; - } - return acc; - }, - { - tempAttrs: [], - tempHints: [], - }, - ); - - if (recommendedAttributes.length <= 0 && widgetsHints.length <= 0) { - // setMessageText( - // 'The provided recommendations for this text did not fit any tags in this project.', - // ); - return {}; - } - - return { - hints: widgetsHints, - recommendations: recommendedAttributes, - geoAreas: predictions.locations, - }; -} - const MAX_ITEMS_PER_PAGE = 20; const entryKeySelector = (entry: PartialEntryType) => entry.clientId; @@ -403,10 +200,12 @@ function AutoEntriesModal(props: Props) { ] = useState('extracted'); const [activePage, setActivePage] = useState(1); + const [recommendations, setRecommendations] = useState< + Record + >(); const { allWidgets, - filteredWidgets, } = useMemo(() => { const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( (item) => (item.widgets ?? []), @@ -418,7 +217,6 @@ function AutoEntriesModal(props: Props) { ]; return { allWidgets: widgets, - filteredWidgets: widgets.filter((w) => mappingsSupportedWidgets.includes(w.widgetId)), }; }, [ frameworkDetails, @@ -566,17 +364,6 @@ function AutoEntriesModal(props: Props) { setRelevantEntries, ] = useState | undefined>(undefined); - const [ - allRecommendations, - setAllRecommendations, - ] = useState | undefined>(undefined); - - const [allHints, setAllHints] = useState< - Record | undefined - >(undefined); - - const mappings = frameworkDetails?.predictionTagsMapping; - const autoEntriesVariables = useMemo(() => ({ projectId, leadId, @@ -592,6 +379,26 @@ function AutoEntriesModal(props: Props) { activePage, ]); + const generateAttributesForEntry = useCallback((recommendedTags: Record) => { + const newAttributes = allWidgets?.map((widget) => { + if (widget.widgetId === 'MATRIX1D') { + return createMatrix1dAttrFromTags( + recommendedTags[widget.key] as Matrix1dValue, + widget, + ); + } + if (widget.widgetId === 'MATRIX2D') { + return createMatrix2dAttrFromTags( + recommendedTags[widget.key] as Matrix2dValue, + widget, + ); + } + return undefined; + }).filter(isDefined); + + return newAttributes; + }, [allWidgets]); + const { data: autoEntries, loading: autoEntriesLoading, @@ -608,27 +415,13 @@ function AutoEntriesModal(props: Props) { onCompleted: (response) => { const entries = response.project?.assistedTagging?.draftEntries?.results; const transformedEntries = (entries ?? [])?.map((entry) => { - const validPredictions = entry.predictionTags?.filter(isDefined); - const categoricalTags = validPredictions?.filter( - (prediction) => prediction.isSelected, - ).map( - (prediction) => prediction.tag, - ).filter(isDefined) ?? []; - - const entryAttributeData: EntryAttributes = { - predictions: { - tags: categoricalTags, - locations: entry.geoAreas?.filter(isDefined) ?? [], - }, - mappings, - filteredWidgets, - }; - - const { - hints: entryHints, - recommendations: entryRecommendations, - geoAreas: entryGeoAreas, - } = handleMappingsFetch(entryAttributeData); + const modelTags = entry?.tags?.modelTags; + if (!isValidObject(modelTags)) { + return undefined; + } + const entryRecommendations = generateAttributesForEntry( + modelTags as ModelTagsType, + ); const entryId = randomString(); const requiredEntry = { @@ -638,41 +431,23 @@ function AutoEntriesModal(props: Props) { excerpt: entry.excerpt, draftEntry: entry.id, droppedExcerpt: entry.excerpt, - attributes: entryRecommendations?.map((attr) => { - if (attr.widgetType !== 'GEO') { - return attr; - } - // NOTE: Selecting only the 1st recommendation - return ({ - ...attr, - data: { - value: attr?.data?.value.slice(0, 1) ?? [], - }, - }); - }), + attributes: entryRecommendations, }; return { entryId, - geoLocations: entryGeoAreas, - recommendations: entryRecommendations, - hints: entryHints, entry: requiredEntry, - relevant: !!entryHints || !!entryRecommendations || !!entryGeoAreas, + geoLocations: entry.geoAreas, + relevant: !!entryRecommendations, }; - }); + }).filter(isDefined); const requiredDraftEntries = transformedEntries?.map( (draftEntry) => draftEntry.entry, ); const entryRecommendations = listToMap( transformedEntries, (item) => item.entryId, - (item) => item.recommendations, - ); - const entryHints = listToMap( - transformedEntries, - (item) => item.entryId, - (item) => item.hints, + (item) => item.entry.attributes, ); const entryGeoAreas = listToMap( transformedEntries, @@ -687,9 +462,8 @@ function AutoEntriesModal(props: Props) { setValue({ entries: requiredDraftEntries, }); - setAllRecommendations(entryRecommendations); + setRecommendations(entryRecommendations); setRelevantEntries(tempRelevantEntries); - setAllHints(entryHints); setGeoAreaOptionsByEntryId(entryGeoAreas); setGeoAreaOptions(Object.values(entryGeoAreas).flat().filter(isDefined)); }, @@ -697,10 +471,6 @@ function AutoEntriesModal(props: Props) { ); const handleEntryCreateButtonClick = useCallback((entryId: string) => { - if (!allRecommendations?.[entryId]) { - return; - } - const selectedEntry = value?.entries?.find((item) => item.clientId === entryId); if (onAssistedEntryAdd && selectedEntry) { const duplicateEntryCheck = createdEntries?.find( @@ -759,7 +529,6 @@ function AutoEntriesModal(props: Props) { value?.entries, allWidgets, geoAreaOptionsByEntryId, - allRecommendations, onAssistedEntryAdd, createdEntries, ]); @@ -878,12 +647,11 @@ function AutoEntriesModal(props: Props) { frameworkDetails, value: datum, className: styles.listItem, + recommendations: recommendations?.[entryId], entryInputClassName: styles.entryInput, name: index, onChange: onEntryChange, leadId, - hints: allHints?.[entryId], - recommendations: allRecommendations?.[entryId], geoAreaOptions, onGeoAreaOptionsChange: setGeoAreaOptions, predictionsLoading: false, @@ -897,13 +665,12 @@ function AutoEntriesModal(props: Props) { relevant: relevantEntries?.[entryId], }); }, [ + recommendations, geoAreaOptions, relevantEntries, value?.entries, handleEntryCreateButtonClick, onEntryChange, - allHints, - allRecommendations, frameworkDetails, leadId, handleUpdateDraftEntryClick, diff --git a/app/components/LeftPaneEntries/SimplifiedTextView/index.tsx b/app/components/LeftPaneEntries/SimplifiedTextView/index.tsx index 545420a35e..0100d0c418 100644 --- a/app/components/LeftPaneEntries/SimplifiedTextView/index.tsx +++ b/app/components/LeftPaneEntries/SimplifiedTextView/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState, useCallback, useMemo } from 'react'; +import React, { useState, useCallback, useMemo } from 'react'; import { IoAdd } from 'react-icons/io5'; import { _cs, @@ -15,6 +15,7 @@ import { GeoArea } from '#components/GeoMultiSelectInput'; import { PartialEntryType as EntryInput } from '#components/entry/schema'; import { Framework } from '#components/entry/types'; +import ProjectContext from '#base/context/ProjectContext'; import useTextSelection from './useTextSelection'; import EntryItem from '../EntryItem'; @@ -60,7 +61,6 @@ interface Props { disableApproveButton?: boolean; disableDiscardButton?: boolean; disableAddButton?: boolean; - assistedTaggingEnabled: boolean; projectId: string | undefined; frameworkDetails?: Framework; textZoomValue?: number | undefined; @@ -75,7 +75,6 @@ function SimplifiedTextView(props: Props) { onAssistedEntryAdd, onExcerptChange, activeEntryClientId, - assistedTaggingEnabled, onExcerptClick, onApproveButtonClick, leadId, @@ -96,15 +95,6 @@ function SimplifiedTextView(props: Props) { const [charactersLoaded, setCharactersLoaded] = useState(CHARACTER_PER_PAGE); const [textToAssist, setTextToAssist] = useState(); - useEffect(() => { - if (textToAssist && !assistedTaggingEnabled) { - setTextToAssist(undefined); - } - }, [ - textToAssist, - assistedTaggingEnabled, - ]); - const text = useMemo(() => { if (textFromProps) { const textLength = Math.min(textFromProps.length, charactersLoaded); @@ -218,6 +208,8 @@ function SimplifiedTextView(props: Props) { setTextToAssist(undefined); }, []); + const { project } = React.useContext(ProjectContext); + let children: React.ReactNode = null; if (!text || splits.length === 0) { children = text; @@ -360,46 +352,30 @@ function SimplifiedTextView(props: Props) { className={styles.actionsPopup} style={position ? ({ ...position }) : undefined} > - {assistedTaggingEnabled ? ( - <> - {isDefined(onAddButtonClick) && ( - - - - )} - {isDefined(onAssistedEntryAdd) && ( - - - - )} - - ) : ( - isDefined(onAddButtonClick) && ( - - - - ) + {isDefined(onAddButtonClick) && ( + + + + )} + {isDefined(onAssistedEntryAdd) && !project?.isPrivate && ( + + + )} )} diff --git a/app/components/LeftPaneEntries/index.tsx b/app/components/LeftPaneEntries/index.tsx index 8377d7290b..9a78186751 100644 --- a/app/components/LeftPaneEntries/index.tsx +++ b/app/components/LeftPaneEntries/index.tsx @@ -1,9 +1,9 @@ import React, { - useContext, useState, useMemo, useEffect, useCallback, + useContext, useRef, } from 'react'; import { @@ -56,8 +56,8 @@ import { import { GeoArea } from '#components/GeoMultiSelectInput'; import LeadPreview from '#components/lead/LeadPreview'; +import { ProjectContext } from '#base/context/ProjectContext'; import Screenshot from '#components/Screenshot'; -import { UserContext } from '#base/context/UserContext'; import { LeadPreviewForTextQuery, LeadPreviewForTextQueryVariables, @@ -209,9 +209,8 @@ function LeftPaneEntries(props: Props) { onAttachmentClick, } = props; + const { project } = useContext(ProjectContext); const alert = useAlert(); - const { user } = useContext(UserContext); - const entriesMappingByAttachment = useMemo(() => ( listToMap( entries?.map((entry) => { @@ -229,9 +228,6 @@ function LeftPaneEntries(props: Props) { ) ), [entries]); - const isAssistedTaggingAccessible = !!user - ?.accessibleFeatures?.some((feature) => feature.key === 'ASSISTED'); - const [activeTab, setActiveTab] = useState( (hideSimplifiedPreview && defaultTab === 'simplified') || (hideOriginalPreview && defaultTab === 'original') ? 'entries' @@ -277,11 +273,22 @@ function LeftPaneEntries(props: Props) { setAttachmentsWithEntriesHidden, ] = useState(false); - const leadAttachmentIdsWithEntries = useMemo(() => ( - Object.values(entryAttachmentsMap ?? {}) - .map((entry) => entry?.leadAttachmentId) - .filter(isDefined) - ), [ + const currentEntryClientIds = useMemo(() => ( + entries?.map((item) => item.clientId) + ), [entries]); + + const leadAttachmentIdsWithEntries = useMemo(() => { + if (!entryAttachmentsMap) { + return []; + } + return ( + Object.keys(entryAttachmentsMap) + .filter((item) => currentEntryClientIds?.includes(item)) + .map((entry) => entryAttachmentsMap[entry]?.leadAttachmentId) + .filter(isDefined) + ); + }, [ + currentEntryClientIds, entryAttachmentsMap, ]); @@ -721,17 +728,16 @@ function LeftPaneEntries(props: Props) { }); }, []); - const assistedTaggingShown = isAssistedTaggingAccessible - && frameworkDetails?.assistedTaggingEnabled - && (frameworkDetails?.predictionTagsMapping?.length ?? 0) > 0; - const isAutoExtractionCompatible = isDefined(leadPreview?.textExtractionId) - && assistedTaggingShown; + && !project?.isPrivate; const errorMessageForAutoExtraction = useMemo(() => { if (isAutoExtractionCompatible) { return undefined; } + if (project?.isPrivate) { + return 'The feature to extract entries through Natural Language Processing (NLP) is currently unavailable for the selected source because the project is private.'; + } if (isDefined(leadPreviewData?.project?.lead?.connectorLead)) { return 'The feature to extract entries through Natural Language Processing (NLP) is currently unavailable for the selected source. The connector associated with the chosen source may be outdated or incompatible with the NLP extraction functionality.'; } @@ -741,6 +747,7 @@ function LeftPaneEntries(props: Props) { return 'The feature to extract entries through Natural Language Processing (NLP) is currently unavailable for the selected source. The selected source appears to be outdated, and the NLP extraction feature is not compatible with older content formats.'; }, [ isAutoExtractionCompatible, + project?.isPrivate, leadPreviewData?.project?.lead, ]); @@ -789,7 +796,7 @@ function LeftPaneEntries(props: Props) { > <>
- {!isEntrySelectionActive && assistedTaggingShown && ( + {!isEntrySelectionActive && (
- ); -} - -export default CheckButton; diff --git a/app/views/AnalyticalFramework/AssistedTagging/CheckButton/styles.css b/app/views/AnalyticalFramework/AssistedTagging/CheckButton/styles.css deleted file mode 100644 index 120c2be339..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/CheckButton/styles.css +++ /dev/null @@ -1,23 +0,0 @@ -.check-button { - position: relative; - overflow: visible; - text-transform: initial; - - .actions { - display: flex; - position: absolute; - top: -0.5em; - right: -0.5em; - align-items: center; - justify-content: center; - border: var(--dui-width-separator-thin) solid var(--color-nlp-tag); - /* NOTE: Want to move this a bit up */ - border-radius: 1em; - background-color: var(--dui-color-foreground); - padding: 2px; - min-width: 1.4em; - min-height: 1.4em; - color: var(--dui-color-nlp); - font-size: var(--dui-font-size-extra-small); - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/AddButton/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/AddButton/index.tsx deleted file mode 100644 index 8565b2f759..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/AddButton/index.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { listToMap } from '@togglecorp/fujs'; -import { - QuickActionButton, - useBooleanState, - SelectInput, -} from '@the-deep/deep-ui'; -import { - IoCloseOutline, - IoAdd, -} from 'react-icons/io5'; - -import { - PredictionTag, - CategoricalMappingsItem, -} from '#types/newAnalyticalFramework'; - -const predictionKeySelector = (prediction: PredictionTag) => prediction.id; -const predictionLabelSelector = (prediction: PredictionTag) => prediction.name; -const predictionGroupKeySelector = (prediction: PredictionTag) => prediction.group ?? 'Misc'; - -interface Props { - disabled?: boolean; - existingMappings: CategoricalMappingsItem[] | undefined; - predictionTags: PredictionTag[] | undefined; - onTagSelect: (tag: string | undefined) => void; -} - -function AddFrameworkTagButton(props: Props) { - const { - disabled, - existingMappings, - predictionTags, - onTagSelect, - } = props; - - const [addInputShown, showAddInput, hideAddInput] = useBooleanState(false); - - const handleTagSelect = useCallback((newTag: string | undefined) => { - onTagSelect(newTag); - hideAddInput(); - }, [ - hideAddInput, - onTagSelect, - ]); - - const existingMappingsMap = useMemo(() => ( - listToMap( - existingMappings, - (d) => d.tag, - () => true, - ) - ), [existingMappings]); - - const filteredOptions = useMemo(() => ( - predictionTags?.filter( - (tag) => !existingMappingsMap?.[tag.id], - ) - ), [ - predictionTags, - existingMappingsMap, - ]); - - if (disabled) { - return null; - } - - if (addInputShown) { - return ( - <> - - - - - - ); - } - - return ( - - - - ); -} - -export default AddFrameworkTagButton; diff --git a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/index.tsx deleted file mode 100644 index 1814ca7d5d..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { - _cs, - listToMap, -} from '@togglecorp/fujs'; -import { - Tag, - List, - QuickActionButton, - DropContainer, -} from '@the-deep/deep-ui'; -import { - IoCloseOutline, -} from 'react-icons/io5'; - -import { - PredictionTag, - CategoricalMappingsItem, -} from '#types/newAnalyticalFramework'; - -import AddButton from './AddButton'; - -import styles from './styles.css'; - -const mappingKeySelector = (mapping: CategoricalMappingsItem) => mapping.tag; - -interface Props { - className?: string; - title: string; - itemKey: string; - onMappingRemoveClick: (cellKey: string, tagKey: string) => void; - onMappingAddClick: (cellKey: string, tagKey: string) => void; - associationKeySelector: (item: N) => string; - mappings: N[] | undefined; - predictionTags: PredictionTag[] | undefined; - disabled?: boolean; -} - -function FrameworkTagRow(props: Props) { - const { - className, - title, - itemKey, - mappings, - predictionTags, - onMappingRemoveClick, - onMappingAddClick, - disabled, - associationKeySelector, - } = props; - - const predictionTagsById = useMemo(() => ( - listToMap( - predictionTags, - (tag) => tag.id, - (tag) => tag, - ) - ), [predictionTags]); - - const handleTagRemove = useCallback((tagKey: string) => { - onMappingRemoveClick(itemKey, tagKey); - }, [ - itemKey, - onMappingRemoveClick, - ]); - - const tagRendererParams = useCallback((tagKey: string) => ({ - className: styles.tag, - children: predictionTagsById?.[tagKey]?.name, - actions: ( - - - - ), - variant: 'accent' as const, - }), [ - predictionTagsById, - handleTagRemove, - ]); - - const filteredMappings = useMemo(() => ( - mappings?.filter((mapping) => associationKeySelector(mapping) === itemKey) - ), [ - mappings, - itemKey, - associationKeySelector, - ]); - - const handleDrop = useCallback((valFromArgument: Record | undefined) => { - const val = valFromArgument as { badgeKey: string | undefined }; - if (!val || !val.badgeKey || typeof val.badgeKey !== 'string') { - return; - } - onMappingAddClick(itemKey, val.badgeKey); - }, [ - itemKey, - onMappingAddClick, - ]); - - const handleTagSelect = useCallback((newTag: string | undefined) => { - if (newTag) { - onMappingAddClick(itemKey, newTag); - } - }, [ - onMappingAddClick, - itemKey, - ]); - - return ( -
-
- - {title} - -
- - - - -
- ); -} - -export default FrameworkTagRow; diff --git a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/styles.css b/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/styles.css deleted file mode 100644 index a704285c8f..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/FrameworkTagRow/styles.css +++ /dev/null @@ -1,38 +0,0 @@ -.framework-tag-row { - display: flex; - border-bottom: var(--dui-width-separator-thin) solid var(--dui-color-separator); - - .left-container { - display: flex; - align-items: center; - flex-shrink: 0; - border-right: var(--dui-width-separator-thin) solid var(--dui-color-separator); - padding: var(--dui-spacing-medium) var(--dui-spacing-small); - width: 40%; - } - - .drop-container { - display: flex; - flex-shrink: 0; - flex-wrap: wrap; - background: unset; - width: 60%; - gap: var(--dui-spacing-extra-small); - - .right-container { - display: flex; - align-items: center; - flex-wrap: wrap; - padding: var(--dui-spacing-medium) var(--dui-spacing-small); - gap: var(--dui-spacing-extra-small); - - .tag { - background-color: var(--dui-color-nlp); - - .remove-button { - padding: 0; - } - } - } - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/index.tsx deleted file mode 100644 index 11adab74ab..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useMemo } from 'react'; -import { _cs } from '@togglecorp/fujs'; - -import { - Tag, - DraggableContent, -} from '@the-deep/deep-ui'; - -import styles from './styles.css'; - -interface Props { - className?: string; - children: React.ReactNode; - badgeCount?: number; - badgeKey: string; -} - -function TagWithBadge(props: Props) { - const { - className, - children, - badgeCount = 0, - badgeKey, - } = props; - - const dragValue = useMemo(() => ({ - badgeKey, - }), [badgeKey]); - - return ( - 0 && styles.nlp, - )} - actionsContainerClassName={styles.actions} - actions={badgeCount > 0 && badgeCount} - spacing="none" - > - - {children} - - - ); -} - -export default TagWithBadge; diff --git a/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/styles.css b/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/styles.css deleted file mode 100644 index a94cee58a8..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/TagWithBadge/styles.css +++ /dev/null @@ -1,53 +0,0 @@ -.check-button { - position: relative; - background-color: var(--dui-color-background-negative); - overflow: visible; - text-transform: initial; - - .drag-container { - display: flex; - align-items: center; - flex-direction: row; - border: unset; - background: transparent; - padding: var(--dui-spacing-small) var(--dui-spacing-large); - text-align: left; - gap: 0; - - .header { - display: flex; - padding: 0; - - .heading { - padding: 0; - min-width: 0; - } - >* { - gap: 0; - } - } - } - - .actions { - display: flex; - position: absolute; - top: -0.5em; - right: -0.5em; - align-items: center; - justify-content: center; - border: var(--dui-width-separator-thin) solid var(--color-nlp-tag); - /* NOTE: Want to move this a bit up */ - border-radius: 1em; - background-color: var(--dui-color-foreground); - padding: 2px; - min-width: 1.4em; - min-height: 1.4em; - color: var(--dui-color-nlp); - font-size: var(--dui-font-size-extra-small); - } - - &.nlp { - background-color: var(--dui-color-nlp); - color: var(--dui-color-text-on-accent); - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/index.tsx deleted file mode 100644 index 1f09767cd8..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/index.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { - _cs, - unique, - listToMap, - isDefined, - randomString, -} from '@togglecorp/fujs'; -import { - ListView, -} from '@the-deep/deep-ui'; - -import { - Matrix1dWidget, - Matrix1dMappingsItem, - PredictionTag, -} from '#types/newAnalyticalFramework'; -import { sortByOrder } from '#utils/common'; - -import CellGroup from '../../CellGroup'; -import FrameworkTagRow from '../../FrameworkTagRow'; - -import styles from './styles.css'; - -const matrix1dAssociationKeySelector = ( - mapping: Matrix1dMappingsItem, -) => mapping.association.subRowKey; - -interface CellItem { - subRowKey: string; - subRowLabel: string; - rowOrder: number; - subRowOrder: number; - rowKey: string; - rowLabel: string; -} - -const cellKeySelector = (cell: CellItem) => cell.subRowKey; -const groupKeySelector = (n: CellItem) => n.rowKey; - -interface Props { - className?: string; - widget: Matrix1dWidget; - mappings: Matrix1dMappingsItem[] | undefined; - onMappingsChange: (newMappings: Matrix1dMappingsItem[], widgetPk: string) => void; - disabled?: boolean; - predictionTags: PredictionTag[] | undefined; -} - -function Matrix1dTagInput(props: Props) { - const { - className, - widget, - mappings, - onMappingsChange, - disabled, - predictionTags, - } = props; - - const sortedCells = useMemo(() => ( - sortByOrder(widget?.properties?.rows) - ?.map((row) => ( - row.cells.map((cell) => ({ - rowKey: row.key, - rowOrder: row.order, - rowLabel: row.label, - subRowKey: cell.key, - subRowOrder: cell.order, - subRowLabel: cell.label, - })) - )) - .flat() - ), [widget.properties?.rows]); - - const groupLabelMap = useMemo(() => ( - listToMap( - unique(sortedCells ?? [], (cell) => cell.rowKey), - (cell) => cell.rowKey, - (cell) => cell.rowLabel, - ) - ), [sortedCells]); - - const handleCellRemove = useCallback((cellKey: string, tagKey: string) => { - if (!mappings) { - return; - } - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag && mapping.association.subRowKey === cellKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - - const handleCellAdd = useCallback((cellKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag && mapping.association.subRowKey === cellKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - - const rowKey = sortedCells - ?.find((cell) => cell.subRowKey === cellKey)?.rowKey; - - if (!rowKey) { - // eslint-disable-next-line no-console - console.error('Sub-row without row is found'); - return; - } - - onMappingsChange([ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: 'MATRIX1D', - association: { - subRowKey: cellKey, - rowKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as Matrix1dMappingsItem, - ], widget.id); - }, [ - sortedCells, - onMappingsChange, - mappings, - widget, - ]); - - const tagRowRendererParams = useCallback((_: string, cell: CellItem) => ({ - title: cell.subRowLabel, - itemKey: cell.subRowKey, - onMappingRemoveClick: handleCellRemove, - onMappingAddClick: handleCellAdd, - associationKeySelector: matrix1dAssociationKeySelector, - mappings, - predictionTags, - disabled, - }), [ - disabled, - handleCellRemove, - handleCellAdd, - mappings, - predictionTags, - ]); - - const subRowGroupRendererParams = useCallback((groupKey: string) => ({ - title: groupLabelMap[groupKey] ?? groupKey, - direction: 'vertical' as const, - }), [groupLabelMap]); - - return ( - - ); -} - -export default Matrix1dTagInput; diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/styles.css b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/styles.css deleted file mode 100644 index fb68193c19..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix1dTagInput/styles.css +++ /dev/null @@ -1,4 +0,0 @@ -.matrix-tag-input { - display: flex; - flex-direction: column; -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx deleted file mode 100644 index e6a6a372b4..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx +++ /dev/null @@ -1,465 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { - _cs, - listToMap, - unique, - isDefined, - randomString, -} from '@togglecorp/fujs'; -import { - List, - ListView, -} from '@the-deep/deep-ui'; - -import { - Matrix2dWidget, - Matrix2dMappingsItem, - PredictionTag, -} from '#types/newAnalyticalFramework'; -import { sortByOrder } from '#utils/common'; -import { getType } from '#utils/types'; - -import FrameworkTagRow from '../../FrameworkTagRow'; -import CellGroup from '../../CellGroup'; - -import styles from './styles.css'; - -type ColMappingItem = Omit & { - association: getType; -}; - -type SubRowMappingItem = Omit & { - association: getType; -}; - -type SubColumnMappingItem = Omit & { - association: getType; -}; - -const columnAssociationKeySelector = (item: ColMappingItem) => item.association.columnKey; -const subRowAssociationKeySelector = (item: SubRowMappingItem) => item.association.subRowKey; -const subColumnAssociationKeySelector = ( - item: SubColumnMappingItem, -) => item.association.subColumnKey; - -interface ColItem { - columnKey: string; - label: string; -} - -const colKeySelector = (c: ColItem) => c.columnKey; - -interface SubRowItem { - subRowKey: string; - subRowLabel: string; - rowOrder: number; - subRowOrder: number; - rowKey: string; - rowLabel: string; -} - -interface SubColumnItem { - columnKey: string; - columnLabel: string; - columnOrder: number; - subColumnKey: string; - subColumnLabel: string; - subColumnOrder: number; -} - -const subRowKeySelector = (subRow: SubRowItem) => subRow.subRowKey; -const subRowGroupKeySelector = (item: SubRowItem) => item.rowKey; - -const subColumnKeySelector = (subRow: SubColumnItem) => subRow.subColumnKey; -const subColumnGroupKeySelector = (item: SubColumnItem) => item.columnKey; - -interface Props { - className?: string; - widget: Matrix2dWidget; - mappings: Matrix2dMappingsItem[] | undefined; - onMappingsChange: (newMappings: Matrix2dMappingsItem[], widgetPk: string) => void; - disabled?: boolean; - predictionTags: PredictionTag[] | undefined; -} - -function Matrix2dTagInput(props: Props) { - const { - className, - widget, - mappings, - onMappingsChange, - disabled, - predictionTags, - } = props; - - const sortedColumns = useMemo(() => ( - sortByOrder(widget?.properties?.columns)?.map((column) => ({ - columnKey: column.key, - label: column.label, - })) - ), [widget.properties?.columns]); - - const sortedSubRows = useMemo(() => ( - sortByOrder(widget?.properties?.rows) - ?.map((row) => ( - row.subRows.map((cell) => ({ - rowKey: row.key, - rowOrder: row.order, - rowLabel: row.label, - subRowKey: cell.key, - subRowOrder: cell.order, - subRowLabel: cell.label, - })) - )) - .flat() - ), [widget.properties?.rows]); - - const sortedSubColumns = useMemo(() => ( - sortByOrder(widget?.properties?.columns) - ?.map((column) => ( - column.subColumns.map((cell) => ({ - columnKey: column.key, - columnOrder: column.order, - columnLabel: column.label, - subColumnKey: cell.key, - subColumnOrder: cell.order, - subColumnLabel: cell.label, - })) - )) - .flat() - ), [widget.properties?.columns]); - - const subRowMappings = useMemo(() => ( - mappings?.filter((mappingItem): mappingItem is SubRowMappingItem => ( - mappingItem.association.type === 'SUB_ROW' - )) - ), [ - mappings, - ]); - - const subColumnMappings = useMemo(() => ( - mappings?.filter((mappingItem): mappingItem is SubColumnMappingItem => ( - mappingItem.association.type === 'SUB_COLUMN' - )) - ), [ - mappings, - ]); - - const columnMappings = useMemo(() => ( - mappings?.filter((mappingItem): mappingItem is ColMappingItem => ( - mappingItem.association.type === 'COLUMN' - )) - ), [ - mappings, - ]); - - const subColumnGroupLabelMap = useMemo(() => ( - listToMap( - unique(sortedSubColumns ?? [], (cell) => cell.columnKey), - (cell) => cell.columnKey, - (cell) => cell.columnLabel, - ) - ), [sortedSubColumns]); - - const subRowGroupLabelMap = useMemo(() => ( - listToMap( - unique(sortedSubRows ?? [], (cell) => cell.rowKey), - (cell) => cell.rowKey, - (cell) => cell.rowLabel, - ) - ), [sortedSubRows]); - - const handleColumnRemove = useCallback((columnKey: string, tagKey: string) => { - if (!mappings) { - return; - } - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'COLUMN' - && mapping.association.columnKey === columnKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - - const handleColumnAdd = useCallback((columnKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'COLUMN' - && mapping.association.columnKey === columnKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - onMappingsChange([ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: widget.widgetId, - association: { - type: 'COLUMN', - columnKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as Matrix2dMappingsItem, - ], widget.id); - }, [ - onMappingsChange, - widget, - mappings, - ]); - - const handleSubRowRemove = useCallback((subRowKey: string, tagKey: string) => { - if (!mappings) { - return; - } - - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'SUB_ROW' - && mapping.association.subRowKey === subRowKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - - const handleSubRowAdd = useCallback((subRowKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'SUB_ROW' - && mapping.association.subRowKey === subRowKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - const rowKey = sortedSubRows - ?.find((subRow) => subRow.subRowKey === subRowKey)?.rowKey; - - if (!rowKey) { - // eslint-disable-next-line no-console - console.error('Sub-row without row is found'); - return; - } - - const newMappings = [ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - subRowKey, - rowKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as Matrix2dMappingsItem, - ]; - onMappingsChange(newMappings, widget.id); - }, [ - sortedSubRows, - onMappingsChange, - mappings, - widget, - ]); - - const handleSubColumnRemove = useCallback((subColumnKey: string, tagKey: string) => { - if (!mappings) { - return; - } - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'SUB_COLUMN' - && mapping.association.subColumnKey === subColumnKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - - const handleSubColumnAdd = useCallback((subColumnKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.type === 'SUB_COLUMN' - && mapping.association.subColumnKey === subColumnKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - - const columnKey = sortedSubColumns?.find( - (subColumn) => subColumn.subColumnKey === subColumnKey, - )?.columnKey; - - if (!columnKey) { - // eslint-disable-next-line no-console - console.error('Sub-column without column is found'); - return; - } - - const newMappings = [ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: 'MATRIX2D', - association: { - type: 'SUB_COLUMN', - subColumnKey, - columnKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as Matrix2dMappingsItem, - ]; - - onMappingsChange(newMappings, widget.id); - }, [ - sortedSubColumns, - onMappingsChange, - mappings, - widget, - ]); - - const colRendererParams = useCallback((_: string, column: ColItem) => ({ - title: column.label, - itemKey: column.columnKey, - onMappingRemoveClick: handleColumnRemove, - onMappingAddClick: handleColumnAdd, - mappings: columnMappings, - associationKeySelector: columnAssociationKeySelector, - predictionTags, - disabled, - }), [ - disabled, - handleColumnAdd, - handleColumnRemove, - columnMappings, - predictionTags, - ]); - - const subRowRendererParams = useCallback((_: string, subRow: SubRowItem) => ({ - title: subRow.subRowLabel, - itemKey: subRow.subRowKey, - onMappingRemoveClick: handleSubRowRemove, - onMappingAddClick: handleSubRowAdd, - mappings: subRowMappings, - associationKeySelector: subRowAssociationKeySelector, - predictionTags, - disabled, - }), [ - disabled, - handleSubRowRemove, - handleSubRowAdd, - subRowMappings, - predictionTags, - ]); - - const subColumnRendererParams = useCallback((_: string, subColumn: SubColumnItem) => ({ - title: subColumn.subColumnLabel, - itemKey: subColumn.subColumnKey, - onMappingRemoveClick: handleSubColumnRemove, - onMappingAddClick: handleSubColumnAdd, - mappings: subColumnMappings, - associationKeySelector: subColumnAssociationKeySelector, - predictionTags, - disabled, - }), [ - disabled, - handleSubColumnRemove, - handleSubColumnAdd, - subColumnMappings, - predictionTags, - ]); - - const subColumnGroupRendererParams = useCallback((groupKey: string) => ({ - title: subColumnGroupLabelMap[groupKey] ?? groupKey, - direction: 'vertical' as const, - }), [subColumnGroupLabelMap]); - - const subRowGroupRendererParams = useCallback((groupKey: string) => ({ - title: subRowGroupLabelMap[groupKey] ?? groupKey, - direction: 'vertical' as const, - }), [subRowGroupLabelMap]); - - return ( -
- - - - - -
- ); -} - -export default Matrix2dTagInput; diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/styles.css b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/styles.css deleted file mode 100644 index 56e436bbe4..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/styles.css +++ /dev/null @@ -1,16 +0,0 @@ -.matrix-tag-input { - display: flex; - flex-direction: column; - - .sub-columns { - display: flex; - flex-direction: column; - gap: var(--dui-spacing-small); - } - - .sub-rows { - display: flex; - flex-direction: column; - gap: var(--dui-spacing-small); - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OptionTypeTagInput/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OptionTypeTagInput/index.tsx deleted file mode 100644 index eda817a36e..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OptionTypeTagInput/index.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { - isDefined, - randomString, -} from '@togglecorp/fujs'; -import { - ListView, -} from '@the-deep/deep-ui'; - -import { - ScaleWidget, - SingleSelectWidget, - MultiSelectWidget, - ScaleMappingsItem, - SelectMappingsItem, - MultiSelectMappingsItem, - KeyLabelEntity, - PredictionTag, -} from '#types/newAnalyticalFramework'; -import { sortByOrder } from '#utils/common'; - -import FrameworkTagRow from '../../FrameworkTagRow'; - -const cellKeySelector = (cell: KeyLabelEntity) => cell.key; -const optionTypeKeySelector = ( - mapping: ScaleMappingsItem | SelectMappingsItem | MultiSelectMappingsItem, -) => mapping.association.optionKey; - -interface Props { - className?: string; - widget: ScaleWidget | SingleSelectWidget | MultiSelectWidget; - mappings: (ScaleMappingsItem | SelectMappingsItem | MultiSelectMappingsItem)[] | undefined; - onMappingsChange: ( - newMappings: (ScaleMappingsItem | SelectMappingsItem | MultiSelectMappingsItem)[], - widgetPk: string, - ) => void; - predictionTags: PredictionTag[] | undefined; - disabled?: boolean; -} - -function OptionTypeTagInput(props: Props) { - const { - className, - widget, - mappings, - onMappingsChange, - predictionTags, - disabled, - } = props; - - const sortedCells = useMemo(() => ( - sortByOrder(widget?.properties?.options) ?? [] - ), [widget.properties?.options]); - - const handleCellRemove = useCallback((cellKey: string, tagKey: string) => { - if (!mappings) { - return; - } - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag && mapping.association.optionKey === cellKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - const handleCellAdd = useCallback((cellKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.optionKey === cellKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - - onMappingsChange([ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: widget.widgetId, - association: { - optionKey: cellKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as ScaleMappingsItem | SelectMappingsItem | MultiSelectMappingsItem, - ], widget.id); - }, [ - onMappingsChange, - mappings, - widget, - ]); - - const cellRendererParams = useCallback((_: string, cell: KeyLabelEntity) => ({ - title: cell.label, - itemKey: cell.key, - onMappingRemoveClick: handleCellRemove, - onMappingAddClick: handleCellAdd, - associationKeySelector: optionTypeKeySelector, - mappings, - predictionTags, - disabled, - }), [ - disabled, - mappings, - handleCellRemove, - handleCellAdd, - predictionTags, - ]); - - return ( - - ); -} - -export default OptionTypeTagInput; diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/index.tsx deleted file mode 100644 index 335af328e4..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/index.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { useMemo, useCallback } from 'react'; -import { - isDefined, - randomString, -} from '@togglecorp/fujs'; -import { - ListView, -} from '@the-deep/deep-ui'; - -import { - OrganigramWidget, - OrganigramMappingsItem, - KeyLabelEntity, - PredictionTag, -} from '#types/newAnalyticalFramework'; -import { sortByOrder } from '#utils/common'; - -import FrameworkTagRow from '../../FrameworkTagRow'; - -import { getOrganigramFlatOptions } from './utils'; - -const cellKeySelector = (cell: KeyLabelEntity) => cell.key; -const optionTypeKeySelector = (mapping: OrganigramMappingsItem) => mapping.association.optionKey; - -interface Props { - className?: string; - widget: OrganigramWidget; - mappings: OrganigramMappingsItem[] | undefined; - onMappingsChange: ( - newMappings: OrganigramMappingsItem[], - widgetPk: string, - ) => void; - predictionTags: PredictionTag[] | undefined; - disabled?: boolean; -} - -function OrganigramTagInput(props: Props) { - const { - className, - widget, - mappings, - onMappingsChange, - disabled, - predictionTags, - } = props; - - const sortedCells = useMemo(() => ( - sortByOrder(getOrganigramFlatOptions(widget?.properties?.options)) ?? [] - ), [widget.properties?.options]); - - const handleCellRemove = useCallback((cellKey: string, tagKey: string) => { - if (!mappings) { - return; - } - const selectedMappingsIndex = mappings.findIndex((mapping) => ( - tagKey === mapping.tag && mapping.association.optionKey === cellKey - )); - - if (selectedMappingsIndex !== -1) { - const newMappings = [...mappings]; - newMappings.splice(selectedMappingsIndex, 1); - - onMappingsChange(newMappings, widget.id); - } - }, [ - onMappingsChange, - widget.id, - mappings, - ]); - - const handleCellAdd = useCallback((cellKey: string, tagKey: string) => { - const selectedMappingsIndex = mappings?.findIndex((mapping) => ( - tagKey === mapping.tag - && mapping.association.optionKey === cellKey - )); - - if (isDefined(selectedMappingsIndex) && selectedMappingsIndex !== -1) { - return; - } - - onMappingsChange([ - ...(mappings ?? []), - { - tag: tagKey, - widget: widget.id, - widgetType: widget.widgetId, - association: { - optionKey: cellKey, - }, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as OrganigramMappingsItem, - ], widget.id); - }, [ - onMappingsChange, - mappings, - widget, - ]); - - const cellRendererParams = useCallback((_: string, cell: KeyLabelEntity) => ({ - title: cell.label, - itemKey: cell.key, - onMappingRemoveClick: handleCellRemove, - onMappingAddClick: handleCellAdd, - associationKeySelector: optionTypeKeySelector, - mappings, - predictionTags, - disabled, - }), [ - predictionTags, - mappings, - disabled, - handleCellAdd, - handleCellRemove, - ]); - - return ( - - ); -} - -export default OrganigramTagInput; diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/utils.ts b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/utils.ts deleted file mode 100644 index 96d382b533..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/OrganigramTagInput/utils.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { OrganigramDatum } from '#types/newAnalyticalFramework'; - -// FIXME: move this to utils -// eslint-disable-next-line import/prefer-default-export -export function getOrganigramFlatOptions( - data?: OrganigramDatum, - prefix?: string, -): Omit[] { - if (!data) { - return []; - } - const { children, ...values } = data; - const label = `${prefix ? `${prefix}/` : ''}${values.label}`; - - const childrenValues = children?.flatMap((v) => getOrganigramFlatOptions(v, label)) ?? []; - return [ - { ...values, label }, - ...childrenValues, - ]; -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/index.tsx deleted file mode 100644 index 64bba3ad75..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/index.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React, { useMemo } from 'react'; -import { _cs } from '@togglecorp/fujs'; -import { Container } from '@the-deep/deep-ui'; - -import { - Widget, - PredictionTag, - CategoricalMappingsItem, - Matrix1dMappingsItem, - Matrix2dMappingsItem, - ScaleMappingsItem, - SelectMappingsItem, - OrganigramMappingsItem, - MultiSelectMappingsItem, -} from '#types/newAnalyticalFramework'; - -import Matrix1dTagInput from './Matrix1dTagInput'; -import Matrix2dTagInput from './Matrix2dTagInput'; -import OptionTypeTagInput from './OptionTypeTagInput'; -import OrganigramTagInput from './OrganigramTagInput'; - -import styles from './styles.css'; - -interface Props { - className?: string; - widget: Widget; - predictionTags: PredictionTag[] | undefined; - mappings: CategoricalMappingsItem[] | undefined; - onMappingsChange: (newMappings: CategoricalMappingsItem[], widgetPk: string) => void; - disabled?: boolean; -} - -function WidgetTagList(props: Props) { - const { - className, - widget, - mappings, - onMappingsChange, - disabled, - predictionTags, - } = props; - - const filteredMappings = useMemo( - () => mappings?.filter((mappingItem) => ( - mappingItem.widgetType === widget.widgetId - && mappingItem.widget === widget.id - )), - [ - mappings, - widget, - ], - ); - - if (widget.widgetId === 'MATRIX1D') { - return ( - - - - ); - } - - if (widget.widgetId === 'MATRIX2D') { - return ( - - - - ); - } - - if ( - widget.widgetId === 'SCALE' - || widget.widgetId === 'SELECT' - || widget.widgetId === 'MULTISELECT' - ) { - return ( - - - - ); - } - - if (widget.widgetId === 'ORGANIGRAM') { - return ( - - - - ); - } - - return ( - - This widget is not supported at the moment. - - ); -} - -export default WidgetTagList; diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/styles.css b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/styles.css deleted file mode 100644 index c797f4a8d8..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/styles.css +++ /dev/null @@ -1,9 +0,0 @@ -.widget-tag-list { - display: flex; - flex-direction: column; - background-color: transparent; - - .content { - overflow: visible; - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/index.tsx deleted file mode 100644 index deaa6ac128..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/index.tsx +++ /dev/null @@ -1,445 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { - _cs, - listToMap, - listToGroupList, - isDefined, - randomString, - unique, -} from '@togglecorp/fujs'; -import { - Error, - SetValueArg, - analyzeErrors, -} from '@togglecorp/toggle-form'; -import { compareTwoStrings } from 'string-similarity'; -import { - ConfirmButton, - ListView, - useAlert, - Card, - Container, - ContainerCard, - Header, - Switch, - Link, -} from '@the-deep/deep-ui'; - -import { - Widget, - MappingsItem, - isCategoricalMappings, - categoricalWidgets, - GeoMappingsItem, -} from '#types/newAnalyticalFramework'; -import { - AssistedPredictionTagsQuery, -} from '#generated/types'; -import ProgressLine from '#components/ProgressLine'; -import generateString from '#utils/string'; - -import { - getMatrix2dPossibleMappings, - getMatrix1dPossibleMappings, - getOptionTypePossibleMappings, - getOrganigramPossibleMappings, -} from './utils'; -import { WidgetsType } from '../schema'; -import TagWithBadge from './TagWithBadge'; -import CheckButton from './CheckButton'; -import WidgetTagList from './WidgetTagList'; -import CellGroup from './CellGroup'; - -import styles from './styles.css'; - -function isCaseInsensitiveMatch(foo: string | undefined = '', bar: string | undefined = '') { - return (compareTwoStrings(foo?.toLowerCase(), bar?.toLowerCase()) > 0.7); -} - -type AssistedTag = NonNullable['assistedTagging']>['predictionTags']>[number]; - -const nlpLabelGroupKeySelector = (tag: AssistedTag) => tag.group ?? 'Miscellaneous'; -const nlpLabelKeySelector = (tag: AssistedTag) => tag.id; -const widgetKeySelector = (widget: Widget) => widget.clientId; - -interface Props { - className?: string; - allWidgets: WidgetsType | undefined; - assistedTaggingEnabled: boolean | undefined; - onAssistedTaggingStatusChange: (newVal: boolean) => void; - pending?: boolean; - assistedPredictionTags: AssistedTag[] | undefined | null; - - name: K; - value: MappingsItem[] | undefined; - error: Error | undefined; - onChange: (value: SetValueArg, name: K) => void; - disabled?: boolean; -} - -function AssistedTagging(props: Props) { - const { - className, - allWidgets, - assistedTaggingEnabled, - pending, - onAssistedTaggingStatusChange, - assistedPredictionTags, - name, - value: mappings, - error, - onChange, - disabled, - } = props; - const alert = useAlert(); - - const errored = analyzeErrors(error); - - const predictionTags = useMemo(() => ( - assistedPredictionTags?.filter((tag) => ( - !tag.isCategory && !tag.hideInAnalysisFrameworkMapping - )) - ), [assistedPredictionTags]); - - type SetMappingsFn = React.Dispatch>; - const setMappings = useCallback((newMappings) => { - onChange(newMappings, name); - }, [ - onChange, - name, - ]); - - const widgets = useMemo(() => ( - allWidgets - ?.filter((widget) => ( - isDefined(widget.id) && categoricalWidgets.includes(widget.widgetId) - )) - ), [allWidgets]); - - const geoWidgets = useMemo(() => ( - allWidgets?.filter((widget) => isDefined(widget.id) && widget.widgetId === 'GEO') - ), [allWidgets]); - - const categoricalMappings = useMemo( - () => mappings?.filter(isCategoricalMappings), - [mappings], - ); - - const mappingsByTagId = useMemo(() => ( - listToGroupList( - categoricalMappings, - (mappingItem) => mappingItem.tag, - ) - ), [categoricalMappings]); - - const geoWidgetsMappingValue = useMemo(() => ( - listToMap( - mappings?.filter((mapping) => mapping.widgetType === 'GEO'), - (mapping) => mapping.widget, - () => true, - ) - ), [mappings]); - - const possibleTagsInFramework = useMemo(() => ( - widgets?.map((widget) => { - if (widget.widgetId === 'MATRIX2D') { - return getMatrix2dPossibleMappings(widget); - } - if (widget.widgetId === 'MATRIX1D') { - return getMatrix1dPossibleMappings(widget); - } - if ( - widget.widgetId === 'SCALE' - || widget.widgetId === 'SELECT' - || widget.widgetId === 'MULTISELECT' - ) { - return getOptionTypePossibleMappings(widget); - } - if (widget.widgetId === 'ORGANIGRAM') { - return getOrganigramPossibleMappings(widget); - } - return []; - }).flat() - ), [widgets]); - - const handleGeoWidgetClick = useCallback((widgetPk: string) => { - setMappings((oldMappings = []) => { - const selectedWidgetIndex = oldMappings.findIndex( - (mapping) => mapping.widget === widgetPk, - ); - if (selectedWidgetIndex !== -1) { - return oldMappings.filter((mapping) => mapping.widget !== widgetPk); - } - return [ - ...oldMappings, - { - widgetType: 'GEO', - widget: widgetPk, - clientId: randomString(), - // FIXME: need to cast here because we cannot set id - // and a proper fix would require more time - } as GeoMappingsItem, - ]; - }); - }, [setMappings]); - - const handleWidgetMappingsChange = useCallback(( - newWidgetMappings: MappingsItem[], - widgetPk: string, - ) => { - setMappings((oldMappings = []) => { - const filteredMappings = oldMappings.filter( - (mapping) => mapping.widget !== widgetPk, - ); - const finalMappings = [ - ...filteredMappings, - ...newWidgetMappings, - ]; - return finalMappings; - }); - }, [setMappings]); - - const nlpLabelGroupRendererParams = useCallback((groupKey: string) => ({ - title: groupKey, - }), []); - - const geoWidgetsRendererParams = useCallback((itemKey: string, widget: Widget) => ({ - children: widget.title, - name: itemKey, - value: !!geoWidgetsMappingValue?.[widget.id], - onClick: handleGeoWidgetClick, - disabled: disabled || !assistedTaggingEnabled, - }), [ - assistedTaggingEnabled, - disabled, - geoWidgetsMappingValue, - handleGeoWidgetClick, - ]); - - const widgetRendererParams = useCallback((_: string, widget: Widget) => ({ - widget, - mappings: categoricalMappings, - onMappingsChange: handleWidgetMappingsChange, - predictionTags, - disabled: disabled || !assistedTaggingEnabled, - }), [ - predictionTags, - disabled, - assistedTaggingEnabled, - categoricalMappings, - handleWidgetMappingsChange, - ]); - - const uniqueMappedPercent = useMemo(() => { - if (!categoricalMappings || !possibleTagsInFramework) { - return 0; - } - const uniqueMappings = unique( - categoricalMappings, - (map) => JSON.stringify(map?.association), - ).length; - return Math.round((uniqueMappings / possibleTagsInFramework.length) * 10000) / 100; - }, [ - categoricalMappings, - possibleTagsInFramework, - ]); - - const handleAutoMatchClick = useCallback(() => { - if (!possibleTagsInFramework) { - return; - } - const mapping = possibleTagsInFramework.map((item) => ( - predictionTags?.reduce((acc, tag) => { - if (!isCaseInsensitiveMatch(item?.label, tag.name)) { - return acc; - } - return ([ - { - ...item, - tag: tag.id, - clientId: randomString(), - }, - ...acc, - ]); - }, [] as Omit[]) - )).flat().filter(isDefined); - - // FIXME: MappingsItem requires id, but its not required at first - setMappings(mapping as MappingsItem[]); - const newMappings = (mapping ?? []) as MappingsItem[]; - const uniqueMappings = unique( - newMappings.filter(isCategoricalMappings), - (map) => JSON.stringify(map?.association), - ).length; - const newMappedPercent = Math.round( - (uniqueMappings / possibleTagsInFramework.length) * 10000, - ) / 100; - - alert.show( - `DEEP was able to match ${newMappedPercent}% tags in your framework. Please, verify the matches and add the ones that are missing.`, - { - variant: 'success', - duration: 6000, - }, - ); - }, [ - alert, - possibleTagsInFramework, - setMappings, - predictionTags, - ]); - - const viewOnlyNlpRendererParams = useCallback((itemKey: string, tag: AssistedTag) => ({ - children: tag.name, - badgeCount: mappingsByTagId?.[itemKey]?.length ?? 0, - badgeKey: tag.id, - }), [ - mappingsByTagId, - ]); - - return ( -
-
- this video - - ), - }, - )} - actionsContainerClassName={styles.actions} - actions={( - <> - - - - )} - /> -
- - -
- My Framework -
-
- Auto Match - - )} - /> -
- )} - > - - - - - - - {(geoWidgets?.length ?? 0) > 0 && ( - - - - - - )} -
-
- ); -} - -export default AssistedTagging; diff --git a/app/views/AnalyticalFramework/AssistedTagging/styles.css b/app/views/AnalyticalFramework/AssistedTagging/styles.css deleted file mode 100644 index 090b05b59a..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/styles.css +++ /dev/null @@ -1,106 +0,0 @@ -.assisted-tagging { - display: flex; - flex-direction: column; - flex-grow: 1; - background-color: var(--dui-color-background); - overflow-y: auto; - gap: var(--dui-spacing-medium); - - .header { - flex-shrink: 0; - background-color: var(--dui-color-foreground); - padding: var(--dui-spacing-large) var(--dui-spacing-super-large); - gap: var(--dui-spacing-large); - - .actions { - display: flex; - align-items: flex-end; - flex-direction: column; - flex-shrink: 0; - justify-content: flex-end; - - .progress-line { - width: 16rem; - - .title { - font-weight: var(--dui-font-weight-bold); - } - } - } - } - - .link { - display: inline-flex; - - .link-actions { - display: none; - } - } - - .content { - display: flex; - flex-direction: column; - flex-grow: 1; - margin: var(--dui-spacing-medium) var(--dui-spacing-super-large); - gap: var(--dui-spacing-medium); - overflow-y: auto; - - .card { - display: flex; - overflow-y: auto; - gap: var(--dui-spacing-medium); - - .heading-container { - width: 100%; - - .heading { - display: flex; - - .left-container { - padding: var(--dui-spacing-small); - width: 40%; - } - - .right-container { - padding: var(--dui-spacing-small); - width: 60%; - } - } - } - - .nlp-framework-list, - .selected-framework-list { - overflow-y: auto; - } - } - - .geo-widgets { - flex-shrink: 0; - } - - .current-framework { - display: flex; - flex-basis: calc(70% - var(--dui-spacing-medium) / 2); - flex-shrink: 0; - background-color: var(--dui-color-foreground); - } - - .nlp-framework { - display: flex; - flex-basis: calc(30% - var(--dui-spacing-medium) / 2); - flex-shrink: 0; - background-color: var(--dui-color-background-information); - } - - &.fade-out { - filter: grayscale(50%); - opacity: 0.4; - } - } - - .geo-widget-list { - display: flex; - flex-wrap: wrap; - gap: var(--dui-spacing-small); - } -} diff --git a/app/views/AnalyticalFramework/AssistedTagging/utils.test.ts b/app/views/AnalyticalFramework/AssistedTagging/utils.test.ts deleted file mode 100644 index 69896ae938..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/utils.test.ts +++ /dev/null @@ -1,419 +0,0 @@ -import { - Matrix2dWidget, - Matrix1dWidget, - ScaleWidget, - OrganigramWidget, -} from '#types/newAnalyticalFramework'; - -import { - getMatrix2dPossibleMappings, - getMatrix1dPossibleMappings, - getOptionTypePossibleMappings, - Matrix1dPossibleMapping, - Matrix2dPossibleMapping, - ScalePossibleMapping, - OrganigramPossibleMapping, - getOrganigramPossibleMappings, -} from './utils'; - -const matrix2dSample: Matrix2dWidget = { - width: 'FULL', - version: 11, - title: 'Matrix 2D', - order: 11, - key: 'sample-matrix-2d', - id: '12', - clientId: 'sample-matrix-2d', - widgetId: 'MATRIX2D', - properties: { - columns: [ - { - order: 1, - key: 'column-1', - label: 'Column 1', - subColumns: [ - { - order: 1, - key: 'sub-col-1', - label: 'Sub Col 1', - }, - { - order: 2, - key: 'sub-col-2', - label: 'Sub Col 2', - }, - ], - }, - { - order: 2, - key: 'column-2', - label: 'Column 2', - subColumns: [], - }, - ], - rows: [ - { - order: 1, - color: 'red', - key: 'row-1', - label: 'Row 1', - subRows: [ - { - key: 'sub-row-1-1', - label: 'Sub Row 1 1', - order: 1, - }, - { - key: 'sub-row-1-2', - label: 'Sub Row 1 2', - order: 2, - }, - ], - }, - { - order: 2, - color: 'red', - key: 'row-2', - label: 'Row 2', - subRows: [ - { - key: 'sub-row-2-1', - label: 'Sub Row 2 1', - order: 1, - }, - { - key: 'sub-row-2-2', - label: 'Sub Row 2 2', - order: 2, - }, - { - key: 'sub-row-2-3', - label: 'Sub Row 2 3', - order: 3, - }, - ], - }, - ], - }, -}; - -const matrix2dPossibleMapping: Matrix2dPossibleMapping[] = [ - { - label: 'Column 1', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'COLUMN', - columnKey: 'column-1', - }, - }, - { - label: 'Column 2', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'COLUMN', - columnKey: 'column-2', - }, - }, - { - label: 'Sub Col 1', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_COLUMN', - columnKey: 'column-1', - subColumnKey: 'sub-col-1', - }, - }, - { - label: 'Sub Col 2', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_COLUMN', - columnKey: 'column-1', - subColumnKey: 'sub-col-2', - }, - }, - { - label: 'Sub Row 1 1', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - rowKey: 'row-1', - subRowKey: 'sub-row-1-1', - }, - }, - { - label: 'Sub Row 1 2', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - rowKey: 'row-1', - subRowKey: 'sub-row-1-2', - }, - }, - { - label: 'Sub Row 2 1', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - rowKey: 'row-2', - subRowKey: 'sub-row-2-1', - }, - }, - { - label: 'Sub Row 2 2', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - rowKey: 'row-2', - subRowKey: 'sub-row-2-2', - }, - }, - { - label: 'Sub Row 2 3', - widget: '12', - widgetType: 'MATRIX2D', - association: { - type: 'SUB_ROW', - rowKey: 'row-2', - subRowKey: 'sub-row-2-3', - }, - }, -]; - -const matrix1dSample: Matrix1dWidget = { - width: 'FULL', - version: 11, - title: 'Matrix 1D', - order: 11, - key: 'sample-matrix-1d', - id: '13', - clientId: 'sample-matrix-1d', - widgetId: 'MATRIX1D', - properties: { - rows: [ - { - key: 'row-1', - label: 'Row 1', - order: 1, - color: '#b0b0b0', - cells: [ - { - key: 'sub-row-1-1', - label: 'Sub Row 1 1', - order: 1, - }, - ], - }, - { - key: 'row-2', - label: 'row-2', - order: 2, - color: '#ff0000', - cells: [ - { - key: 'sub-row-2-1', - label: 'Sub Row 2 1', - order: 1, - }, - { - key: 'sub-row-2-2', - label: 'Sub Row 2 2', - order: 2, - }, - ], - }, - ], - }, -}; - -const matrix1dPossibleMapping: Matrix1dPossibleMapping[] = [ - { - label: 'Sub Row 1 1', - widget: '13', - widgetType: 'MATRIX1D', - association: { - rowKey: 'row-1', - subRowKey: 'sub-row-1-1', - }, - }, - { - label: 'Sub Row 2 1', - widget: '13', - widgetType: 'MATRIX1D', - association: { - rowKey: 'row-2', - subRowKey: 'sub-row-2-1', - }, - }, - { - label: 'Sub Row 2 2', - widget: '13', - widgetType: 'MATRIX1D', - association: { - rowKey: 'row-2', - subRowKey: 'sub-row-2-2', - }, - }, -]; - -const scaleSample: ScaleWidget = { - width: 'HALF', - version: 11, - title: 'Scale', - order: 11, - key: 'sample-scale', - id: '14', - clientId: 'sample-scale', - widgetId: 'SCALE', - properties: { - options: [ - { - key: 'option-1', - label: 'Option 1', - order: 1, - color: '#f0f0f0', - }, - { - key: 'option-2', - label: 'Option 2', - order: 2, - color: '#123123', - }, - ], - }, -}; - -const scalePossibleMapping: ScalePossibleMapping[] = [ - { - label: 'Option 1', - widget: '14', - widgetType: 'SCALE', - association: { - optionKey: 'option-1', - }, - }, - { - label: 'Option 2', - widget: '14', - widgetType: 'SCALE', - association: { - optionKey: 'option-2', - }, - }, -]; - -const organigramSample: OrganigramWidget = { - width: 'HALF', - version: 11, - title: 'Organigram', - order: 11, - key: 'sample-organigram', - id: '15', - clientId: 'sample-organigram', - widgetId: 'ORGANIGRAM', - properties: { - options: { - key: 'parent-0', - label: 'Parent 0', - order: 1, - children: [ - { - key: 'child-0-1', - label: 'Child 0 1', - order: 1, - children: [ - { - key: 'child-1-1', - label: 'Child 1 1', - order: 1, - children: [], - }, - { - key: 'child-1-2', - label: 'Child 1 2', - order: 2, - children: [], - }, - ], - }, - { - key: 'child-0-2', - label: 'Child 0 2', - order: 2, - children: [], - }, - ], - }, - }, -}; - -const organigramPossibleMapping: OrganigramPossibleMapping[] = [ - { - label: 'Parent 0', - widget: '15', - widgetType: 'ORGANIGRAM', - association: { - optionKey: 'parent-0', - }, - }, - { - label: 'Parent 0/Child 0 1', - widget: '15', - widgetType: 'ORGANIGRAM', - association: { - optionKey: 'child-0-1', - }, - }, - { - label: 'Parent 0/Child 0 1/Child 1 1', - widget: '15', - widgetType: 'ORGANIGRAM', - association: { - optionKey: 'child-1-1', - }, - }, - { - label: 'Parent 0/Child 0 1/Child 1 2', - widget: '15', - widgetType: 'ORGANIGRAM', - association: { - optionKey: 'child-1-2', - }, - }, - { - label: 'Parent 0/Child 0 2', - widget: '15', - widgetType: 'ORGANIGRAM', - association: { - optionKey: 'child-0-2', - }, - }, -]; - - -test('get matrix possible mappings', () => { - expect(getMatrix2dPossibleMappings(undefined)).toStrictEqual([]); - expect( - getMatrix2dPossibleMappings(matrix2dSample), - ).toStrictEqual(matrix2dPossibleMapping); - expect(getMatrix1dPossibleMappings(undefined)).toStrictEqual([]); - expect( - getMatrix1dPossibleMappings(matrix1dSample), - ).toStrictEqual(matrix1dPossibleMapping); - expect(getOptionTypePossibleMappings(undefined)).toStrictEqual([]); - expect( - getOptionTypePossibleMappings(scaleSample), - ).toStrictEqual(scalePossibleMapping); - expect(getOrganigramPossibleMappings(undefined)).toStrictEqual([]); - expect( - getOrganigramPossibleMappings(organigramSample), - ).toStrictEqual(organigramPossibleMapping); -}); diff --git a/app/views/AnalyticalFramework/AssistedTagging/utils.ts b/app/views/AnalyticalFramework/AssistedTagging/utils.ts deleted file mode 100644 index 8bd8e8ebf3..0000000000 --- a/app/views/AnalyticalFramework/AssistedTagging/utils.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - Matrix2dWidget, - Matrix2dMappingsItem, - Matrix1dWidget, - Matrix1dMappingsItem, - ScaleWidget, - SingleSelectWidget, - MultiSelectWidget, - ScaleMappingsItem, - SelectMappingsItem, - MultiSelectMappingsItem, - OrganigramWidget, - OrganigramMappingsItem, -} from '#types/newAnalyticalFramework'; - -import { getOrganigramFlatOptions } from './WidgetTagList/OrganigramTagInput/utils'; - -type ConvertToPossibleMapping = Omit & { label: string }; - -export type Matrix2dPossibleMapping = ConvertToPossibleMapping; -export type Matrix1dPossibleMapping = ConvertToPossibleMapping; -export type ScalePossibleMapping = ConvertToPossibleMapping; -export type SelectPossibleMapping = ConvertToPossibleMapping; -export type MultiSelectPossibleMapping = ConvertToPossibleMapping; -export type OrganigramPossibleMapping = ConvertToPossibleMapping; - -export function getMatrix2dPossibleMappings( - widget: Matrix2dWidget | undefined, -): Matrix2dPossibleMapping[] { - const columns = widget?.properties?.columns?.map((column): Matrix2dPossibleMapping => ({ - label: column.label, - widget: widget.id, - widgetType: 'MATRIX2D' as const, - association: { - type: 'COLUMN' as const, - columnKey: column.key, - }, - })) ?? []; - - const subColumns: Matrix2dPossibleMapping[] = widget?.properties?.columns - ?.map((column) => ( - column.subColumns.map((cell) => ({ - label: cell.label, - widget: widget.id, - widgetType: 'MATRIX2D' as const, - association: { - type: 'SUB_COLUMN' as const, - subColumnKey: cell.key, - columnKey: column.key, - }, - })) - )).flat() ?? []; - - const subRows: Matrix2dPossibleMapping[] = widget?.properties?.rows - ?.map((row) => ( - row.subRows.map((cell) => ({ - label: cell.label, - widget: widget.id, - widgetType: 'MATRIX2D' as const, - association: { - type: 'SUB_ROW' as const, - subRowKey: cell.key, - rowKey: row.key, - }, - })) - )).flat() ?? []; - - return [ - ...columns, - ...subColumns, - ...subRows, - ]; -} - -export function getMatrix1dPossibleMappings( - widget: Matrix1dWidget | undefined, -): Matrix1dPossibleMapping[] { - const subRows = widget?.properties?.rows - ?.map((row) => ( - row.cells.map((cell) => ({ - label: cell.label, - widget: widget.id, - widgetType: 'MATRIX1D' as const, - association: { - subRowKey: cell.key, - rowKey: row.key, - }, - })) - )).flat() ?? []; - - return subRows; -} - -export function getOptionTypePossibleMappings( - widget: ScaleWidget | MultiSelectWidget | SingleSelectWidget | undefined, -): (ScalePossibleMapping | SelectPossibleMapping | MultiSelectPossibleMapping)[] { - const subRows = widget?.properties?.options - ?.map((option) => ({ - label: option.label, - widget: widget.id, - widgetType: widget.widgetId, - association: { - optionKey: option.key, - }, - })) ?? []; - - return subRows; -} - -export function getOrganigramPossibleMappings( - widget: OrganigramWidget | undefined, -): OrganigramPossibleMapping[] { - if (!widget) { - return []; - } - const flatOptions = getOrganigramFlatOptions(widget.properties?.options) ?? []; - const options = flatOptions - ?.map((option) => ({ - label: option.label, - widget: widget.id, - widgetType: 'ORGANIGRAM' as const, - association: { - optionKey: option.key, - }, - })) ?? []; - - return options; -} diff --git a/app/views/AnalyticalFramework/index.tsx b/app/views/AnalyticalFramework/index.tsx index 6e26eae071..e4e8a1ead0 100644 --- a/app/views/AnalyticalFramework/index.tsx +++ b/app/views/AnalyticalFramework/index.tsx @@ -2,7 +2,6 @@ import React, { useMemo, useState, useCallback, useEffect, useContext } from 're import { _cs, isDefined, - listToGroupList, } from '@togglecorp/fujs'; import { useParams, @@ -52,13 +51,8 @@ import NewOrganizationSelectInput, { BasicOrganization } from '#components/selec import PreloadMessage from '#base/components/PreloadMessage'; import FullPageErrorMessage from '#views/FullPageErrorMessage'; import SubNavbarContext from '#components/SubNavbar/context'; -import { - MappingsItem, - categoricalWidgets, -} from '#types/newAnalyticalFramework'; import _ts from '#ts'; import { - AssistedPredictionTagsQuery, CurrentFrameworkQuery, CurrentFrameworkQueryVariables, UpdateFrameworkMutation, @@ -82,14 +76,7 @@ import { CURRENT_FRAMEWORK, UPDATE_FRAMEWORK, CREATE_FRAMEWORK, - ASSISTED_PREDICTION_TAGS_QUERY, } from './queries'; -import { - getMatrix2dPossibleMappings, - getMatrix1dPossibleMappings, - getOptionTypePossibleMappings, - getOrganigramPossibleMappings, -} from './AssistedTagging/utils'; import PrivacyInput from './components/PrivacyInput'; import UserTable from './UserTable'; import UploadImage from './UploadImage'; @@ -97,118 +84,8 @@ import PrimaryTagging from './PrimaryTagging'; import Properties from './Properties'; import SecondaryTagging from './SecondaryTagging'; import Review from './Review'; -import AssistedTagging from './AssistedTagging'; import styles from './styles.css'; -const stripGhostAssociations = ( - newWidgets: WidgetsType | undefined, - currentMapping: MappingsItem[] | undefined, -) => { - const newCatWidgets = newWidgets - ?.filter((widget) => ( - isDefined(widget.id) && categoricalWidgets.includes(widget.widgetId) - )); - const newGeoWidgets = newWidgets - ?.filter((widget) => ( - isDefined(widget.id) && widget.widgetId === 'GEO' - )); - const possibleTagsInFramework = listToGroupList( - newCatWidgets?.map((widget) => { - if (widget.widgetId === 'MATRIX2D') { - return getMatrix2dPossibleMappings(widget); - } - if (widget.widgetId === 'MATRIX1D') { - return getMatrix1dPossibleMappings(widget); - } - if ( - widget.widgetId === 'SCALE' - || widget.widgetId === 'SELECT' - || widget.widgetId === 'MULTISELECT' - ) { - return getOptionTypePossibleMappings(widget); - } - if (widget.widgetId === 'ORGANIGRAM') { - return getOrganigramPossibleMappings(widget); - } - return []; - }).flat(), - (item) => item.widget, - (item) => item, - ); - - const filteredMappings = currentMapping?.filter( - (item) => { - const possibleAssociations = possibleTagsInFramework?.[item.widget]; - if (item.widgetType === 'MATRIX1D') { - return possibleAssociations?.some((possibleTag) => { - if (possibleTag.widgetType === 'MATRIX1D') { - return ( - possibleTag.association.rowKey === item.association.rowKey - && possibleTag.association.subRowKey === item.association.subRowKey - ); - } - return false; - }); - } - if (item.widgetType === 'MATRIX2D') { - return possibleAssociations?.some((possibleTag) => { - if (possibleTag.widgetType === 'MATRIX2D') { - return ( - item.association.type === 'SUB_ROW' && possibleTag.association.type === 'SUB_ROW' - && possibleTag.association.rowKey === item.association.rowKey - && possibleTag.association.subRowKey === item.association.subRowKey - ) || ( - item.association.type === 'SUB_COLUMN' && possibleTag.association.type === 'SUB_COLUMN' - && possibleTag.association.columnKey === item.association.columnKey - && possibleTag - .association.subColumnKey === item.association.subColumnKey - ) || ( - item.association.type === 'COLUMN' && possibleTag.association.type === 'COLUMN' - && possibleTag.association.columnKey === item.association.columnKey - ); - } - return false; - }); - } - if ( - item.widgetType === 'SCALE' - || item.widgetType === 'SELECT' - || item.widgetType === 'MULTISELECT' - ) { - return possibleAssociations?.some((possibleTag) => { - if ( - possibleTag.widgetType === 'SCALE' - || possibleTag.widgetType === 'SELECT' - || possibleTag.widgetType === 'MULTISELECT' - ) { - return ( - possibleTag.association.optionKey === item.association.optionKey - ); - } - return false; - }); - } - if (item.widgetType === 'ORGANIGRAM') { - return possibleAssociations?.some((possibleTag) => { - if (possibleTag.widgetType === 'ORGANIGRAM') { - return ( - possibleTag.association.optionKey === item.association.optionKey - ); - } - return false; - }); - } - if (item.widgetType === 'GEO') { - return newGeoWidgets?.some( - (possibleTag) => item.widget === possibleTag.id, - ); - } - return false; - }, - ); - return filteredMappings; -}; - function getTimestamp(dateString: string | undefined) { if (!dateString) { return 0; @@ -226,7 +103,6 @@ function transformFramework(framework: Framework): FrameworkInput { secondaryTagging, properties, modifiedAt, - predictionTagsMapping, assistedTaggingEnabled, } = framework; @@ -237,7 +113,6 @@ function transformFramework(framework: Framework): FrameworkInput { organization: organization?.id, primaryTagging, secondaryTagging, - predictionTagsMapping, assistedTaggingEnabled, properties, isVisualizationEnabled: isDefined(properties), @@ -250,7 +125,6 @@ interface Stored { pristine: boolean, primaryTaggingPristine: boolean, secondaryTaggingPristine: boolean, - predictionTagsMappingPristine: boolean, framework: PartialFormType, frameworkImage: Framework['previewImage'], organizationOptions: BasicOrganization[] | null | undefined, @@ -292,9 +166,6 @@ function AnalyticalFramework(props: Props) { const [actionsNode, setActionsNode] = useState(); const [iconsNode, setIconsNode] = useState(); - const isAssistedTaggingAccessible = !!user - ?.accessibleFeatures?.some((feature) => feature.key === 'ASSISTED'); - const [ isNavigationDisabled, setNavigationDisableState, @@ -308,10 +179,6 @@ function AnalyticalFramework(props: Props) { secondaryTaggingPristine, setSecondaryTaggingPristine, ] = useState(true); - const [ - predictionTagsMappingPristine, - setPredictionTagsMappingPristine, - ] = useState(true); const [ frameworkImage, @@ -351,7 +218,6 @@ function AnalyticalFramework(props: Props) { setOrganizationOptions(storedAfArg.organizationOptions); setPrimaryTaggingPristine(storedAfArg.primaryTaggingPristine); setSecondaryTaggingPristine(storedAfArg.secondaryTaggingPristine); - setPredictionTagsMappingPristine(storedAfArg.predictionTagsMappingPristine); setCacheUsed(true); } @@ -399,7 +265,6 @@ function AnalyticalFramework(props: Props) { setPristine(true); setPrimaryTaggingPristine(true); setSecondaryTaggingPristine(true); - setPredictionTagsMappingPristine(true); if (storedData) { // NOTE: clearing out stored af after new data is fetched @@ -447,7 +312,6 @@ function AnalyticalFramework(props: Props) { setPristine(true); setPrimaryTaggingPristine(true); setSecondaryTaggingPristine(true); - setPredictionTagsMappingPristine(true); const path = generatePath( routes.analyticalFrameworkEdit.path, @@ -502,7 +366,6 @@ function AnalyticalFramework(props: Props) { setValue(transformFramework(result as Framework)); setPrimaryTaggingPristine(true); setSecondaryTaggingPristine(true); - setPredictionTagsMappingPristine(true); } }, onError: (error) => { @@ -613,7 +476,6 @@ function AnalyticalFramework(props: Props) { pristine, primaryTaggingPristine, secondaryTaggingPristine, - predictionTagsMappingPristine, framework: value, frameworkImage, organizationOptions, @@ -627,7 +489,6 @@ function AnalyticalFramework(props: Props) { value, primaryTaggingPristine, secondaryTaggingPristine, - predictionTagsMappingPristine, frameworkImage, organizationOptions, ], @@ -636,66 +497,15 @@ function AnalyticalFramework(props: Props) { const handlePrimaryTaggingChange = useCallback( (val: SetValueArg) => { setPrimaryTaggingPristine(false); - setPredictionTagsMappingPristine(false); - setValue((oldVal) => { - const newData = (typeof val === 'function') ? val(oldVal.primaryTagging) : val; - const primaryTaggingWidgets = newData?.map( - (item) => item.widgets, - ).flat().filter(isDefined) ?? []; - const newMappings = stripGhostAssociations( - [ - ...(oldVal.secondaryTagging ?? []), - ...primaryTaggingWidgets, - ], - oldVal.predictionTagsMapping, - ); - return { - ...oldVal, - primaryTagging: newData, - predictionTagsMapping: newMappings, - }; - }, true); + setFieldValue(val, 'primaryTagging'); }, - [setValue], + [setFieldValue], ); const handleSecondaryTaggingChange = useCallback( (val: SetValueArg) => { setSecondaryTaggingPristine(false); - setPredictionTagsMappingPristine(false); - setValue((oldVal) => { - const newData = (typeof val === 'function') ? val(oldVal.secondaryTagging) : val; - const primaryTaggingWidgets = oldVal.primaryTagging?.map( - (item) => item.widgets, - ).flat().filter(isDefined) ?? []; - const newMappings = stripGhostAssociations( - [ - ...primaryTaggingWidgets, - ...(newData ?? []), - ], - oldVal.predictionTagsMapping, - ); - return { - ...oldVal, - secondaryTagging: newData, - predictionTagsMapping: newMappings, - }; - }, true); - }, - [setValue], - ); - - const handlePredictionTagsMappings = useCallback( - (val: SetValueArg, name: 'predictionTagsMapping') => { - setPredictionTagsMappingPristine(false); - setFieldValue(val, name); - }, - [setFieldValue], - ); - - const handleAssistedTaggingStatusChange = useCallback( - (val: boolean) => { - setFieldValue(val, 'assistedTaggingEnabled'); + setFieldValue(val, 'secondaryTagging'); }, [setFieldValue], ); @@ -744,12 +554,6 @@ function AnalyticalFramework(props: Props) { parentWidgetType: undefined, } : null, })), - predictionTagsMapping: predictionTagsMappingPristine - ? undefined - : val.predictionTagsMapping?.map((item) => ({ - ...item, - widgetType: undefined, - })), }; const data = newData as AnalysisFrameworkInputType; @@ -781,19 +585,11 @@ function AnalyticalFramework(props: Props) { [ setError, validate, frameworkId, primaryTaggingPristine, secondaryTaggingPristine, - predictionTagsMappingPristine, updateAnalysisFramework, createAnalysisFramework, ], ); - const { - data: assistedTagsResponse, - loading: assistedTagsPending, - } = useQuery( - ASSISTED_PREDICTION_TAGS_QUERY, - ); - - const pending = creatingAnalysisFramework || updatingAnalysisFramework || assistedTagsPending; + const pending = creatingAnalysisFramework || updatingAnalysisFramework; const error = getErrorObject(riskyError); @@ -801,7 +597,6 @@ function AnalyticalFramework(props: Props) { detailsErrored, primaryTaggingErrored, secondaryTaggingErrored, - predictionTagsMappingErrored, propertiesErrored, ] = useMemo( () => { @@ -809,13 +604,11 @@ function AnalyticalFramework(props: Props) { delete errorWithoutTaggings.primaryTagging; delete errorWithoutTaggings.secondaryTagging; delete errorWithoutTaggings.properties; - delete errorWithoutTaggings.predictionTagsMapping; return [ analyzeErrors(errorWithoutTaggings), analyzeErrors(error?.primaryTagging), analyzeErrors(error?.secondaryTagging), - analyzeErrors(error?.predictionTagsMapping), analyzeErrors(error?.properties), ]; }, @@ -948,18 +741,6 @@ function AnalyticalFramework(props: Props) { > {_ts('analyticalFramework', 'review')} - {isAssistedTaggingAccessible && ( - - Assisted Tagging - - )} {value.isVisualizationEnabled && ( - {isAssistedTaggingAccessible && ( - - - - )} {value.isVisualizationEnabled && ( ; +export type PartialFormType = PartialForm; export type WidgetsType = NonNullable; export type SectionsType = NonNullable; export type PropertiesType = NonNullable; export type StatsConfigType = NonNullable['statsConfig']>; -export type PredictionTagMappingsType = NonNullable; export type PartialWidgetsType = WidgetsType; export type PartialSectionsType = SectionsType; -export type PartialPredictionTagMappingsType = PredictionTagMappingsType; // NOTE: These are not partials widget types even if it's on the name type PartialWidgetType = WidgetsType[number]; type PartialSectionType = SectionsType[number]; -type PartialPredictionTagMappingType = PredictionTagMappingsType[number]; type FormSchema = ObjectSchema; type FormSchemaFields = ReturnType; -type PredictionTagMappingSchema = ObjectSchema; -type PredictionTagMappingSchemaFields = ReturnType; -const predictionTagMappingSchema: PredictionTagMappingSchema = { - fields: (): PredictionTagMappingSchemaFields => ({ - association: [], - tag: [], - - widget: [], - widgetType: [], - clientId: [], - id: [defaultUndefinedType], - }), -}; - -type PredictionTagMappingsSchema = ArraySchema; -type PredictionTagMappingsSchemaMember = ReturnType; -const predictionTagMappingsSchema: PredictionTagMappingsSchema = { - keySelector: (col) => col.clientId, - member: (): PredictionTagMappingsSchemaMember => predictionTagMappingSchema, -}; - type WidgetSchema = ObjectSchema; type WidgetSchemaFields = ReturnType; const widgetSchema: WidgetSchema = { @@ -132,8 +108,6 @@ const schema: FormSchema = { primaryTagging: sectionsSchema, secondaryTagging: widgetsSchema, - - predictionTagsMapping: predictionTagMappingsSchema, }; if (value?.isVisualizationEnabled) { diff --git a/app/views/AnalyticalFramework/types.tsx b/app/views/AnalyticalFramework/types.tsx index 5eeb03f53b..e5daaafca4 100644 --- a/app/views/AnalyticalFramework/types.tsx +++ b/app/views/AnalyticalFramework/types.tsx @@ -4,15 +4,12 @@ import { import { WidgetType as WidgetRaw, - AnalysisFrameworkPredictionMappingType as MappingsItemRaw, WidgetGqlInputType as WidgetInputRaw, - PredictionTagAnalysisFrameworkMapInputType as MappingsItemInputRaw, CurrentFrameworkQuery, AnalysisFrameworkInputType, } from '#generated/types'; import { Widget as WidgetFromAF, - MappingsItem, } from '#types/newAnalyticalFramework'; import { DeepMandatory, @@ -23,15 +20,11 @@ import { // Remove this DeepMandatory transformation after server sends key as mandatory export type FrameworkRaw = DeepMandatory, 'key'>; type FrameworkWithWidgets = DeepReplace, WidgetFromAF>; -type FrameworkWithTags = DeepReplace; -export type Framework = FrameworkWithTags; +export type Framework = FrameworkWithWidgets; export type FrameworkInputRaw = DeepMandatory, 'clientId' | 'key' | 'widgetId' | 'order' | 'conditional'>; type FrameworkInputWithWidgets = DeepReplace; -type FrameworkInputWithTags = DeepReplace< - FrameworkInputWithWidgets, MappingsItemInputRaw, MappingsItem ->; -export type FrameworkInput = Omit & { +export type FrameworkInput = Omit & { previewImage?: File | null, }; export type Section = NonNullable[number]>; diff --git a/app/views/Home/ProjectItem/index.tsx b/app/views/Home/ProjectItem/index.tsx index 4e3c889674..d418c38558 100644 --- a/app/views/Home/ProjectItem/index.tsx +++ b/app/views/Home/ProjectItem/index.tsx @@ -142,6 +142,7 @@ export interface RecentProjectItemProps { isPinned?: boolean; pinnedId: string | undefined; onProjectPinChange: () => void; + onProjectLeaveSuccess: () => void; disablePinButton: boolean; } @@ -169,6 +170,7 @@ function ProjectItem(props: RecentProjectItemProps) { recentActiveUsers, isPinned, onProjectPinChange, + onProjectLeaveSuccess, disablePinButton, } = props; @@ -248,7 +250,7 @@ function ProjectItem(props: RecentProjectItemProps) { } = leaveProjectResponse; if (ok) { - onProjectPinChange(); + onProjectLeaveSuccess(); alert.show( 'Project successfully left.', { variant: 'success' }, diff --git a/app/views/Home/index.tsx b/app/views/Home/index.tsx index f535cd1c76..4a32276d41 100644 --- a/app/views/Home/index.tsx +++ b/app/views/Home/index.tsx @@ -133,6 +133,7 @@ function Home(props: ViewProps) { const { data: recentProjectsResponse, loading: recentProjectsPending, + refetch: retriggerRecentProjects, } = useQuery( RECENT_PROJECTS, ); @@ -209,6 +210,14 @@ function Home(props: ViewProps) { selectedProjectResponse, ]); + const handleProjectLeaveSuccess = useCallback(() => { + retriggerPinnedProjectsList(); + retriggerRecentProjects(); + }, [ + retriggerPinnedProjectsList, + retriggerRecentProjects, + ]); + const recentProjectsRendererParams = useCallback( (_: string, data: ProjectDetail): RecentProjectItemProps => ({ projectId: data?.id, @@ -232,11 +241,13 @@ function Home(props: ViewProps) { pinnedId: pinnedProjectsList?.find((item) => item.project.id === data?.id)?.id, isPinned: data?.isProjectPinned, onProjectPinChange: retriggerPinnedProjectsList, + onProjectLeaveSuccess: handleProjectLeaveSuccess, disablePinButton: pinButtonDisabled, }), [ pinnedProjectsList, retriggerPinnedProjectsList, + handleProjectLeaveSuccess, pinButtonDisabled, ], ); @@ -264,9 +275,11 @@ function Home(props: ViewProps) { pinnedId: data.id, isPinned: true, onProjectPinChange: retriggerPinnedProjectsList, + onProjectLeaveSuccess: handleProjectLeaveSuccess, disablePinButton: pinButtonDisabled, }), [ + handleProjectLeaveSuccess, retriggerPinnedProjectsList, pinButtonDisabled, ], @@ -352,6 +365,7 @@ function Home(props: ViewProps) { allowedPermissions={selectedProjectDetail?.allowedPermissions} recentActiveUsers={selectedProjectDetail?.recentActiveUsers} isPinned={selectedProjectDetail?.isProjectPinned} + onProjectLeaveSuccess={handleProjectLeaveSuccess} onProjectPinChange={retriggerPinnedProjectsList} disablePinButton={pinButtonDisabled} /> diff --git a/app/views/ProjectEdit/GeoAreas/RegionCard/index.tsx b/app/views/ProjectEdit/GeoAreas/RegionCard/index.tsx index 50efa3feb7..e74ed33d30 100644 --- a/app/views/ProjectEdit/GeoAreas/RegionCard/index.tsx +++ b/app/views/ProjectEdit/GeoAreas/RegionCard/index.tsx @@ -152,6 +152,7 @@ export interface Props { onRegionRetriggerSuccess: () => void; onRegionPublishSuccess: () => void; onRegionDeleteSuccess: () => void; + onAdminRemoveSuccess: () => void; } function RegionCard(props: Props) { @@ -172,6 +173,7 @@ function RegionCard(props: Props) { onRegionRetriggerSuccess, onRegionPublishSuccess, onRegionDeleteSuccess, + onAdminRemoveSuccess, } = props; // setting this so that when user add an admin level, it is updated @@ -312,6 +314,7 @@ function RegionCard(props: Props) { if (ok) { refetchAdminLevels(); + onAdminRemoveSuccess(); if (onAdminLevelUpdate) { onAdminLevelUpdate(); diff --git a/app/views/ProjectEdit/GeoAreas/index.tsx b/app/views/ProjectEdit/GeoAreas/index.tsx index ff77a57273..88d725ce68 100644 --- a/app/views/ProjectEdit/GeoAreas/index.tsx +++ b/app/views/ProjectEdit/GeoAreas/index.tsx @@ -171,6 +171,7 @@ function GeoAreas(props: Props) { onRegionPublishSuccess: regionsRefetch, onRegionDeleteSuccess: regionsRefetch, onAdminLevelAddSuccess: regionsRefetch, + onAdminRemoveSuccess: regionsRefetch, onRegionRetriggerSuccess: regionsRefetch, navigationDisabled, }; diff --git a/app/views/Sources/SourcesTable/LeadDuplicatesModal/LeadView/index.tsx b/app/views/Sources/SourcesTable/LeadDuplicatesModal/LeadView/index.tsx index ac13c721d1..d9db5fe5ef 100644 --- a/app/views/Sources/SourcesTable/LeadDuplicatesModal/LeadView/index.tsx +++ b/app/views/Sources/SourcesTable/LeadDuplicatesModal/LeadView/index.tsx @@ -61,7 +61,6 @@ function LeadView(props: Props) {