From 28faacfcb20307226e4227b518b02589fc2d8fe8 Mon Sep 17 00:00:00 2001 From: Subina Date: Thu, 2 Nov 2023 16:27:53 +0545 Subject: [PATCH 01/11] Remove assisted tagging switch --- .../LeftPane/SimplifiedTextView/index.tsx | 1 - .../LeftPane/SimplifiedTextView/styles.css | 7 -- app/views/EntryEdit/LeftPane/index.tsx | 65 ++++++------------- 3 files changed, 21 insertions(+), 52 deletions(-) diff --git a/app/views/EntryEdit/LeftPane/SimplifiedTextView/index.tsx b/app/views/EntryEdit/LeftPane/SimplifiedTextView/index.tsx index 1acdfb8796..4c644cd819 100644 --- a/app/views/EntryEdit/LeftPane/SimplifiedTextView/index.tsx +++ b/app/views/EntryEdit/LeftPane/SimplifiedTextView/index.tsx @@ -326,7 +326,6 @@ function SimplifiedTextView(props: Props) { styles.simplifiedTextView, className, disableAddButton && styles.disabled, - assistedTaggingEnabled && styles.assistedEnabled, )} > {children} diff --git a/app/views/EntryEdit/LeftPane/SimplifiedTextView/styles.css b/app/views/EntryEdit/LeftPane/SimplifiedTextView/styles.css index 8d293e7518..5b04b7b114 100644 --- a/app/views/EntryEdit/LeftPane/SimplifiedTextView/styles.css +++ b/app/views/EntryEdit/LeftPane/SimplifiedTextView/styles.css @@ -16,13 +16,6 @@ margin: var(--dui-spacing-medium) 0; } - &.assisted-enabled { - >*::selection { - background: var(--dui-color-nlp); - color: var(--dui-color-text-on-accent); - } - } - .actions-popup { display: flex; position: absolute; diff --git a/app/views/EntryEdit/LeftPane/index.tsx b/app/views/EntryEdit/LeftPane/index.tsx index 7df23619e9..e43f9a6978 100644 --- a/app/views/EntryEdit/LeftPane/index.tsx +++ b/app/views/EntryEdit/LeftPane/index.tsx @@ -24,7 +24,6 @@ import { QuickActionLink, PendingMessage, Message, - Switch, } from '@the-deep/deep-ui'; import { IoAdd, @@ -37,7 +36,6 @@ import { IoCheckmark, } from 'react-icons/io5'; -import useLocalStorage from '#hooks/useLocalStorage'; import { GeoArea } from '#components/GeoMultiSelectInput'; import LeadPreview from '#components/lead/LeadPreview'; import Screenshot from '#components/Screenshot'; @@ -149,11 +147,6 @@ function LeftPane(props: Props) { : defaultTab, ); - const [ - assistedTaggingEnabled, - onAssistedTaggingStatusChange, - ] = useLocalStorage('assisted-tagging-enabled', false); - useEffect(() => { if (activeTabRef) { activeTabRef.current = { @@ -501,8 +494,7 @@ function LeftPane(props: Props) { ); - const assistedTaggingShown = assistedTaggingEnabled - && !project?.isPrivate + const assistedTaggingShown = !project?.isPrivate && isAssistedTaggingAccessible && frameworkDetails?.assistedTaggingEnabled && (frameworkDetails?.predictionTagsMapping?.length ?? 0) > 0; @@ -545,41 +537,26 @@ function LeftPane(props: Props) { retainMount="lazy" > {(leadPreview?.textExtract?.length ?? 0) > 0 ? ( - <> - {(frameworkDetails?.predictionTagsMapping?.length ?? 0) > 0 - && !project?.isPrivate - && frameworkDetails?.assistedTaggingEnabled - && isAssistedTaggingAccessible - && ( - - )} - - + ) : ( Date: Fri, 24 Nov 2023 14:54:34 +0545 Subject: [PATCH 02/11] Add tags in framework selection --- .../FrameworkTagSelectInput/index.tsx | 145 ++++++++++++++++++ .../FrameworkTagSelectInput/styles.css | 5 + app/views/ProjectEdit/Framework/index.tsx | 27 ++++ package.json | 2 +- yarn.lock | 8 +- 5 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 app/components/selections/FrameworkTagSelectInput/index.tsx create mode 100644 app/components/selections/FrameworkTagSelectInput/styles.css diff --git a/app/components/selections/FrameworkTagSelectInput/index.tsx b/app/components/selections/FrameworkTagSelectInput/index.tsx new file mode 100644 index 0000000000..5159a095ba --- /dev/null +++ b/app/components/selections/FrameworkTagSelectInput/index.tsx @@ -0,0 +1,145 @@ +import React, { useMemo, useCallback } from 'react'; +import { + Button, + BadgeInput, + BadgeInputProps, +} from '@the-deep/deep-ui'; +import { useQuery, gql } from '@apollo/client'; + +import { + FrameworkTagOptionsQuery, + FrameworkTagOptionsQueryVariables, +} from '#generated/types'; + +import styles from './styles.css'; + +const FRAMEWORK_TAGS = gql` + query FrameworkTagOptions( + $page: Int, + $pageSize: Int, + ) { + analysisFrameworkTags( + page: $page, + pageSize: $pageSize, + ) { + page + pageSize + results { + description + id + title + icon { + name + url + } + } + totalCount + } + } +`; + +const PAGE_SIZE = 10; + +type BasicFrameworkTag = NonNullable['analysisFrameworkTags']>['results']>[number]; +const keySelector = (item: BasicFrameworkTag) => item.id; +const labelSelector = (item: BasicFrameworkTag) => item.title; +const titleSelector = (item: BasicFrameworkTag) => item.description; +function iconSelector(item: BasicFrameworkTag) { + if (!item.icon?.url) { + return undefined; + } + return ( + {item.icon.url} + ); +} + +type Props = Omit< + BadgeInputProps, + 'options' | 'keySelector' | 'labelSelector' +>; + +function FrameworkTagSelectInput( + props: Props, +) { + const variables = useMemo(() => ({ + page: 1, + pageSize: PAGE_SIZE, + }), []); + + const { + data, + fetchMore, + } = useQuery( + FRAMEWORK_TAGS, + { + variables, + }, + ); + + const handleShowMoreClick = useCallback(() => { + fetchMore({ + variables: { + ...variables, + page: (data?.analysisFrameworkTags?.page ?? 1) + 1, + }, + updateQuery: (previousResult, { fetchMoreResult }) => { + if (!previousResult.analysisFrameworkTags) { + return previousResult; + } + + const oldFrameworkTags = previousResult.analysisFrameworkTags; + const newFrameworkTags = fetchMoreResult?.analysisFrameworkTags; + + if (!newFrameworkTags) { + return previousResult; + } + + return ({ + ...previousResult, + analysisFrameworkTags: { + ...newFrameworkTags, + results: [ + ...(oldFrameworkTags.results ?? []), + ...(newFrameworkTags.results ?? []), + ], + }, + }); + }, + }); + }, [ + data?.analysisFrameworkTags?.page, + fetchMore, + variables, + ]); + + return ( + <> + + {(data?.analysisFrameworkTags?.totalCount ?? 0) + > (data?.analysisFrameworkTags?.results ?? []).length && ( + + )} + + ); +} + +export default FrameworkTagSelectInput; diff --git a/app/components/selections/FrameworkTagSelectInput/styles.css b/app/components/selections/FrameworkTagSelectInput/styles.css new file mode 100644 index 0000000000..965ca45ff6 --- /dev/null +++ b/app/components/selections/FrameworkTagSelectInput/styles.css @@ -0,0 +1,5 @@ +.icon { + width: auto; + height: 1rem; + object-fit: contain; +} diff --git a/app/views/ProjectEdit/Framework/index.tsx b/app/views/ProjectEdit/Framework/index.tsx index 46be87e2e2..6b06d35f49 100644 --- a/app/views/ProjectEdit/Framework/index.tsx +++ b/app/views/ProjectEdit/Framework/index.tsx @@ -8,6 +8,7 @@ import { import { Button, Container, + Checkbox, Kraken, ListView, Message, @@ -29,6 +30,7 @@ import { import SmartButtonLikeLink from '#base/components/SmartButtonLikeLink'; import useDebouncedValue from '#hooks/useDebouncedValue'; import ProjectContext from '#base/context/ProjectContext'; +import FrameworkTagSelectInput from '#components/selections/FrameworkTagSelectInput'; import { isFiltered } from '#utils/common'; import routes from '#base/configs/routes'; import _ts from '#ts'; @@ -49,7 +51,9 @@ const frameworkKeySelector = (d: FrameworkType) => d.id; export const PROJECT_FRAMEWORKS = gql` query ProjectAnalysisFrameworks( $isCurrentUserMember: Boolean, + $tags: [ID!], $search: String, + $recentlyUsed: Boolean, $page: Int, $pageSize: Int, $createdBy: [ID!], @@ -57,8 +61,10 @@ export const PROJECT_FRAMEWORKS = gql` analysisFrameworks( search: $search, isCurrentUserMember: $isCurrentUserMember + recentlyUsed: $recentlyUsed, page: $page, pageSize: $pageSize, + tags: $tags, createdBy: $createdBy, ) { results { @@ -133,7 +139,9 @@ const relatedToMeLabelSelector = (d: Option) => d.label; type FormType = { relatedToMe?: 'true' | 'false'; + recentlyUsed: boolean; search: string; + tag?: string; }; type FormSchema = ObjectSchema>; @@ -142,12 +150,16 @@ type FormSchemaFields = ReturnType const schema: FormSchema = { fields: (): FormSchemaFields => ({ search: [], + tag: [], + recentlyUsed: [], relatedToMe: [requiredCondition], }), }; const defaultFormValue: PartialForm = { relatedToMe: 'true', + recentlyUsed: false, + tag: undefined, search: '', }; @@ -183,12 +195,16 @@ function ProjectFramework(props: Props) { const analysisFrameworkVariables = useMemo(() => ( { isCurrentUserMember: delayedValue.relatedToMe === 'true' ? true : undefined, + tags: delayedValue.tag ? [delayedValue.tag] : undefined, + recentlyUsed: delayedValue.recentlyUsed, search: delayedValue.search, page: 1, pageSize: PAGE_SIZE, } ), [ delayedValue.relatedToMe, + delayedValue.recentlyUsed, + delayedValue.tag, delayedValue.search, ]); @@ -287,6 +303,17 @@ function ProjectFramework(props: Props) { value={value.search} placeholder={_ts('projectEdit', 'searchLabel')} /> + +
Date: Fri, 8 Dec 2023 11:32:39 +0545 Subject: [PATCH 03/11] Add citation and date format change options in word and excel export * Fix drop in framework mapping --- .../general/ExportHistory/index.tsx | 2 + .../SourcesAppliedFilters/index.tsx | 2 +- .../leadFilters/SourcesFilter/index.tsx | 2 +- .../WidgetTagList/Matrix2dTagInput/index.tsx | 2 +- .../AdvancedOptionsSelection/index.tsx | 175 ++++++++++++++---- .../AdvancedOptionsSelection/styles.css | 16 ++ app/views/NewExport/EntryPreview/index.tsx | 94 ++++++++-- app/views/NewExport/index.tsx | 19 +- 8 files changed, 255 insertions(+), 57 deletions(-) diff --git a/app/components/general/ExportHistory/index.tsx b/app/components/general/ExportHistory/index.tsx index dc539d9edb..103e29fc72 100644 --- a/app/components/general/ExportHistory/index.tsx +++ b/app/components/general/ExportHistory/index.tsx @@ -112,6 +112,8 @@ const PROJECT_EXPORTS = gql` widgetKey } excelDecoupled + dateFormat + reportCitationStyle reportExportingWidgets reportLevels { id diff --git a/app/components/leadFilters/SourcesAppliedFilters/index.tsx b/app/components/leadFilters/SourcesAppliedFilters/index.tsx index adf98de02d..62b1b946c7 100644 --- a/app/components/leadFilters/SourcesAppliedFilters/index.tsx +++ b/app/components/leadFilters/SourcesAppliedFilters/index.tsx @@ -171,7 +171,7 @@ function SourcesAppliedFilters(props: Props) { keySelector={organizationKeySelector} /> diff --git a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx index 5bc332d7a7..e6a6a372b4 100644 --- a/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx +++ b/app/views/AnalyticalFramework/AssistedTagging/WidgetTagList/Matrix2dTagInput/index.tsx @@ -139,7 +139,7 @@ function Matrix2dTagInput(props: Props) { const subColumnMappings = useMemo(() => ( mappings?.filter((mappingItem): mappingItem is SubColumnMappingItem => ( - mappingItem.association.type === 'SUB_ROW' + mappingItem.association.type === 'SUB_COLUMN' )) ), [ mappings, diff --git a/app/views/NewExport/AdvancedOptionsSelection/index.tsx b/app/views/NewExport/AdvancedOptionsSelection/index.tsx index d0a2150831..6447cfb767 100644 --- a/app/views/NewExport/AdvancedOptionsSelection/index.tsx +++ b/app/views/NewExport/AdvancedOptionsSelection/index.tsx @@ -4,12 +4,18 @@ import { Modal, Checkbox, Tag, + RadioInput, } from '@the-deep/deep-ui'; +import { useQuery, gql } from '@apollo/client'; import TreeSelection from '#components/TreeSelection'; -import { ExportFormatEnum } from '#generated/types'; +import { + ExportFormatEnum, + ExportEnumsQuery, + ExportDateFormatEnum, + ExportReportCitationStyleEnum, +} from '#generated/types'; import _ts from '#ts'; - import EntryPreview from '../EntryPreview'; import { TreeSelectableWidget, @@ -23,6 +29,37 @@ import { import { ExcelColumnNode } from '..'; import styles from './styles.css'; +const EXPORT_ENUMS = gql` + query ExportEnums { + enums { + ExportExtraOptionsSerializerDateFormat { + description + enum + label + } + ExportExtraOptionsSerializerReportCitationStyle { + description + enum + label + } + } + } +`; +interface DateFormatOption { + enum: ExportDateFormatEnum; + label: string; +} +const dateFormatKeySelector = (item: DateFormatOption) => item.enum; +const dateFormatLabelSelector = (item: DateFormatOption) => item.label; + +interface CitationFormatOption { + enum: ExportReportCitationStyleEnum; + label: string; + description?: string | null | undefined; +} +const citationFormatKeySelector = (item: CitationFormatOption) => item.enum; +const citationFormatLabelSelector = (item: CitationFormatOption) => item.description ?? item.label; + function columnsLabelSelector(node: ExcelColumnNode) { const isEntryMetadata = node.key.includes('ENTRY'); @@ -92,6 +129,10 @@ interface Props { onTextWidgetsChange: (value: TreeSelectableWidget[]) => void; widgetColumns: ExcelColumnNode[]; onWidgetColumnChange: (value: ExcelColumnNode[]) => void; + dateFormat: ExportDateFormatEnum | undefined; + citationFormat: ExportReportCitationStyleEnum | undefined; + onDateFormatChange: (newVal: ExportDateFormatEnum | undefined) => void; + onCitationFormatChange: (newVal: ExportReportCitationStyleEnum | undefined) => void; } function AdvancedOptionsSelection(props: Props) { const { @@ -118,8 +159,18 @@ function AdvancedOptionsSelection(props: Props) { onExcelDecoupledChange, widgetColumns, onWidgetColumnChange, + dateFormat, + onDateFormatChange, + citationFormat, + onCitationFormatChange, } = props; + const { + data: exportEnums, + } = useQuery( + EXPORT_ENUMS, + ); + const handleSwapOrderValueChange = useCallback((newValue: boolean) => { if (newValue) { onReportStructureVariantChange(DIMENSION_FIRST); @@ -203,41 +254,72 @@ function AdvancedOptionsSelection(props: Props) { )}
- - - - - )} - headingDescription={( -

- Options shown are based on dimensions - available after filtering from the main export page -

- )} - > - -
+
+ + + + + + + + + )} + headingDescription={( +

+ Options shown are based on dimensions + available after filtering from the main export page +

+ )} + > + +
+
@@ -262,6 +346,23 @@ function AdvancedOptionsSelection(props: Props) {

{_ts('export', 'decoupledEntriesTitle2')}

{_ts('export', 'decoupledEntriesTitle')}

+ + + {widgetColumns.length > 0 && ( { @@ -115,7 +141,7 @@ function WidgetSample(props: WidgetSampleProps) { return firstItem.label; } if (widget.widgetId === 'DATE_RANGE') { - return sampleDateRange; + return `${sampleDate} - ${sampleDate}`; } if (widget.widgetId === 'DATE') { return sampleDate; @@ -130,7 +156,7 @@ function WidgetSample(props: WidgetSampleProps) { return sampleGeo; } return undefined; - }, [widget]); + }, [widget, sampleDate]); if (!content) { return null; @@ -150,6 +176,8 @@ interface Props { showLeadEntryId: boolean; showAssessmentData: boolean; showEntryWidgetData: boolean; + dateFormat: ExportDateFormatEnum | undefined; + citationFormat: ExportReportCitationStyleEnum | undefined; } function EntryPreview(props: Props) { @@ -160,11 +188,33 @@ function EntryPreview(props: Props) { showAssessmentData, showEntryWidgetData, textWidgets, + dateFormat, + citationFormat, } = props; - const selectedExcerpt = useMemo(() => ( - sampleExcerpts[Math.floor(Math.random() * sampleExcerpts.length)] - ), []); + const { + data: exportEnums, + } = useQuery( + EXPORT_ENUMS, + ); + + const selectedDateFormat = useMemo(() => { + const options = exportEnums?.enums?.ExportExtraOptionsSerializerDateFormat ?? []; + const selectedFormat = options.find((item) => item.enum === dateFormat); + return formatDateToString(new Date(), selectedFormat?.label ?? 'dd-MM-yyyy'); + }, [ + exportEnums, + dateFormat, + ]); + + const selectedExcerpt = useMemo(() => { + const excerpt = sampleExcerpts[Math.floor(Math.random() * sampleExcerpts.length)]; + if (citationFormat === 'STYLE_1') { + return excerpt.replace(/\.$/, ''); + } + return excerpt; + }, [citationFormat]); + const filteredContextualWidgets = useMemo(() => ( contextualWidgets?.filter((widget) => widget.selected) ), [contextualWidgets]); @@ -175,7 +225,8 @@ function EntryPreview(props: Props) { const widgetSampleRendererParams = useCallback((_: string, widget: Widget) => ({ widget, - }), []); + sampleDate: selectedDateFormat, + }), [selectedDateFormat]); const textWidgetRendererParams = useCallback((_: string, widget: Widget) => ({ title: widget.title, @@ -201,7 +252,7 @@ function EntryPreview(props: Props) { [ - , 612 Key Informant Interview, Data collection: 08/04/2022 - 20/04/2022] + {`, 612 Key Informant Interview, Data collection: ${selectedDateFormat}`} )} {showEntryWidgetData && filteredContextualWidgets?.length > 0 && ( @@ -231,13 +282,24 @@ function EntryPreview(props: Props) { errored={false} /> )} - - ( - - HarperCollins + {citationFormat === 'DEFAULT' && ( + + ( + + HarperCollins + + {`, Moby-Dick, ${selectedDateFormat})`} - , Moby-Dick, 17/04/2022) - + )} + {citationFormat === 'STYLE_1' && ( + + ( + + Moby-Dick + + {` ${selectedDateFormat}).`} + + )} ); diff --git a/app/views/NewExport/index.tsx b/app/views/NewExport/index.tsx index ddfc131fe9..21d3f52e97 100644 --- a/app/views/NewExport/index.tsx +++ b/app/views/NewExport/index.tsx @@ -48,6 +48,9 @@ import { ProjectSourceStatsForExportQueryVariables, LeadsFilterDataInputType, ExportExcelSelectedStaticColumnEnum, + ExportDateFormatEnum, + ExportReportCitationStyleEnum, + ExportCreateInputType, } from '#generated/types'; import { transformToFormError, ObjectError } from '#base/utils/errorTransform'; import { @@ -322,6 +325,12 @@ function NewExport(props: Props) { excelDecoupled, setExcelDecoupled, ] = useState(locationState?.extraOptions?.excelDecoupled ?? false); + const [dateFormat, setDateFormat] = useState( + locationState?.extraOptions?.dateFormat ?? 'DEFAULT', + ); + const [citationFormat, setCitationFormat] = useState( + locationState?.extraOptions?.reportCitationStyle ?? 'DEFAULT', + ); const [ textWidgets, @@ -620,9 +629,11 @@ function NewExport(props: Props) { setPristine(true); }, [clearSourcesFilterValue, setPristine]); - const getCreateExportData = useCallback((isPreview: boolean) => ({ + const getCreateExportData = useCallback((isPreview: boolean): ExportCreateInputType => ({ extraOptions: { excelDecoupled, + dateFormat, + reportCitationStyle: citationFormat, excelColumns: columns.filter((widget) => widget.selected).map((col) => ( col.isWidget ? { isWidget: col.isWidget, @@ -656,6 +667,8 @@ function NewExport(props: Props) { type: 'ENTRIES' as const, title: queryTitle, }), [ + dateFormat, + citationFormat, columns, exportFileFormat, contextualWidgets, @@ -866,6 +879,10 @@ function NewExport(props: Props) { showMatrix2dOptions={showMatrix2dOptions} contextualWidgets={contextualWidgets} textWidgets={textWidgets} + dateFormat={dateFormat} + citationFormat={citationFormat} + onDateFormatChange={setDateFormat} + onCitationFormatChange={setCitationFormat} onReportStructureChange={setReportStructure} onReportShowLeadEntryIdChange={setReportShowLeadEntryId} onReportShowAssessmentDataChange={setReportShowAssessmentData} From 25266f1e3c87d789cbce1207f9c0869778b2079f Mon Sep 17 00:00:00 2001 From: Aditya Khatri Date: Wed, 13 Dec 2023 09:50:18 +0545 Subject: [PATCH 04/11] Add date range in entry export preview --- app/views/NewExport/EntryPreview/index.tsx | 48 ++++++++++++++-------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/app/views/NewExport/EntryPreview/index.tsx b/app/views/NewExport/EntryPreview/index.tsx index 0a8b3b1a5e..a6cef1c044 100644 --- a/app/views/NewExport/EntryPreview/index.tsx +++ b/app/views/NewExport/EntryPreview/index.tsx @@ -93,17 +93,27 @@ function TextWidgetRenderer(props: TextWidgetRendererProps) { ); } +const todaysDate = new Date(); +const nextYear = todaysDate; +nextYear.setFullYear(todaysDate.getFullYear() + 1); + interface WidgetSampleProps { widget: Widget; - sampleDate: string; + selectedFormat?: { label: string; }; } function WidgetSample(props: WidgetSampleProps) { const { widget, - sampleDate, + selectedFormat, } = props; + const sampleDateOne = formatDateToString(todaysDate, selectedFormat?.label ?? 'dd-MM-yyyy'); + const sampleDateTwo = formatDateToString( + nextYear, + selectedFormat?.label ?? 'dd-MM-yyyy', + ); + const content = useMemo(() => { if (widget.widgetId === 'SCALE') { const firstItem = widget.properties?.options?.[0]; @@ -141,10 +151,12 @@ function WidgetSample(props: WidgetSampleProps) { return firstItem.label; } if (widget.widgetId === 'DATE_RANGE') { - return `${sampleDate} - ${sampleDate}`; + const oneYearLaterDate = new Date(sampleDateOne); + oneYearLaterDate.setFullYear(oneYearLaterDate.getFullYear() + 1); + return `${sampleDateOne} - ${sampleDateTwo}`; } if (widget.widgetId === 'DATE') { - return sampleDate; + return sampleDateOne; } if (widget.widgetId === 'TIME') { return sampleTime; @@ -156,7 +168,7 @@ function WidgetSample(props: WidgetSampleProps) { return sampleGeo; } return undefined; - }, [widget, sampleDate]); + }, [widget, sampleDateOne, sampleDateTwo]); if (!content) { return null; @@ -198,13 +210,15 @@ function EntryPreview(props: Props) { EXPORT_ENUMS, ); - const selectedDateFormat = useMemo(() => { + const selectedFormat = useMemo(() => { const options = exportEnums?.enums?.ExportExtraOptionsSerializerDateFormat ?? []; - const selectedFormat = options.find((item) => item.enum === dateFormat); - return formatDateToString(new Date(), selectedFormat?.label ?? 'dd-MM-yyyy'); - }, [ - exportEnums, - dateFormat, + return options.find((item) => item.enum === dateFormat); + }, [exportEnums, dateFormat]); + + const dateInSelectedFormat = useMemo(() => ( + formatDateToString(new Date(), selectedFormat?.label ?? 'dd-MM-yyyy') + ), [ + selectedFormat, ]); const selectedExcerpt = useMemo(() => { @@ -225,8 +239,10 @@ function EntryPreview(props: Props) { const widgetSampleRendererParams = useCallback((_: string, widget: Widget) => ({ widget, - sampleDate: selectedDateFormat, - }), [selectedDateFormat]); + selectedFormat, + }), [ + selectedFormat, + ]); const textWidgetRendererParams = useCallback((_: string, widget: Widget) => ({ title: widget.title, @@ -252,7 +268,7 @@ function EntryPreview(props: Props) { [ - {`, 612 Key Informant Interview, Data collection: ${selectedDateFormat}`} + {`, 612 Key Informant Interview, Data collection: ${dateInSelectedFormat}`} )} {showEntryWidgetData && filteredContextualWidgets?.length > 0 && ( @@ -288,7 +304,7 @@ function EntryPreview(props: Props) { HarperCollins - {`, Moby-Dick, ${selectedDateFormat})`} + {`, Moby-Dick, ${dateInSelectedFormat})`} )} {citationFormat === 'STYLE_1' && ( @@ -297,7 +313,7 @@ function EntryPreview(props: Props) { Moby-Dick - {` ${selectedDateFormat}).`} + {` ${dateInSelectedFormat}).`} )} From 9883049a6cb1dccf1bbeefad963338b7d95e5000 Mon Sep 17 00:00:00 2001 From: Subina Date: Wed, 22 Nov 2023 13:51:22 +0545 Subject: [PATCH 05/11] Add auto extracted draft entries --- .../LeftPane/AutoEntriesModal/index.tsx | 77 +++++++ app/views/EntryEdit/LeftPane/index.tsx | 208 +++++++++++++----- app/views/EntryEdit/LeftPane/styles.css | 2 +- 3 files changed, 229 insertions(+), 58 deletions(-) create mode 100644 app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx new file mode 100644 index 0000000000..d801b13109 --- /dev/null +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -0,0 +1,77 @@ +import React, { useMemo } from 'react'; +import { isNotDefined } from '@togglecorp/fujs'; +import { gql, useQuery } from '@apollo/client'; +import { + Modal, +} from '@the-deep/deep-ui'; + +import { + AutoEntriesForLeadQuery, + AutoEntriesForLeadQueryVariables, +} from '#generated/types'; + +const AUTO_ENTRIES_FOR_LEAD = gql` + query AutoEntriesForLead( + $projectId: ID!, + $leadIds: [ID!], + ) { + project(id: $projectId) { + assistedTagging { + draftEntryByLeads( + filter: { + draftEntryType: AUTO, + lead: $leadIds, + }) { + id + excerpt + predictionReceivedAt + predictionStatus + } + } + } + } +`; + +interface Props { + onModalClose: () => void; + projectId: string; + leadId: string; +} + +function AutoEntriesModal(props: Props) { + const { + onModalClose, + projectId, + leadId, + } = props; + + const autoEntriesVariables = useMemo(() => ({ + projectId, + leadIds: [leadId], + }), [ + projectId, + leadId, + ]); + + const { + data: autoEntries, + } = useQuery( + AUTO_ENTRIES_FOR_LEAD, + { + skip: isNotDefined(autoEntriesVariables), + variables: autoEntriesVariables, + }, + ); + + console.log('here', autoEntries); + + return ( + + Auto entries here + + ); +} + +export default AutoEntriesModal; diff --git a/app/views/EntryEdit/LeftPane/index.tsx b/app/views/EntryEdit/LeftPane/index.tsx index e43f9a6978..02e15000ab 100644 --- a/app/views/EntryEdit/LeftPane/index.tsx +++ b/app/views/EntryEdit/LeftPane/index.tsx @@ -5,7 +5,7 @@ import { isDefined, randomString, } from '@togglecorp/fujs'; -import { gql, useQuery } from '@apollo/client'; +import { gql, useQuery, useMutation } from '@apollo/client'; import { Tabs, Container, @@ -15,6 +15,7 @@ import { TextInput, QuickActionButton, useBooleanState, + useModalState, Button, QuickActionDropdownMenu, QuickActionDropdownMenuProps, @@ -44,6 +45,8 @@ import { UserContext } from '#base/context/UserContext'; import { LeadPreviewForTextQuery, LeadPreviewForTextQueryVariables, + CreateAutoDraftEntriesMutation, + CreateAutoDraftEntriesMutationVariables, } from '#generated/types'; import { PartialEntryType as EntryInput } from '#components/entry/schema'; @@ -53,6 +56,7 @@ import CanvasDrawModal from './CanvasDrawModal'; import { Lead, EntryImagesMap } from '../index'; import SimplifiedTextView from './SimplifiedTextView'; import EntryItem, { ExcerptModal } from './EntryItem'; +import AutoEntriesModal from './AutoEntriesModal'; import styles from './styles.css'; const LEAD_PREVIEW = gql` @@ -73,6 +77,22 @@ const LEAD_PREVIEW = gql` } `; +const CREATE_AUTO_DRAFT_ENTRIES = gql` + mutation CreateAutoDraftEntries ( + $projectId: ID!, + $leadId: ID!, + ) { + project(id: $projectId) { + assistedTagging { + autoDraftEntryCreate(data: {lead: $leadId}) { + ok + errors + } + } + } + } +`; + const entryKeySelector = (e: EntryInput) => e.clientId; export type TabOptions = 'simplified' | 'original' | 'entries' | undefined; @@ -147,6 +167,12 @@ function LeftPane(props: Props) { : defaultTab, ); + const [ + autoEntriesModalShown, + showAutoEntriesModal, + hideAutoEntriesModal, + ] = useModalState(false); + useEffect(() => { if (activeTabRef) { activeTabRef.current = { @@ -193,6 +219,57 @@ function LeftPane(props: Props) { }, ); + const [ + triggerAutoEntriesCreate, + ] = useMutation( + CREATE_AUTO_DRAFT_ENTRIES, + { + onCompleted: (response) => { + const autoEntriesResponse = response?.project?.assistedTagging + ?.autoDraftEntryCreate; + if (autoEntriesResponse?.ok) { + alert.show( + 'The entries extraction has started.', + { variant: 'success' }, + ); + showAutoEntriesModal(); + } else { + alert.show( + 'Failed to extract entries using NLP.', + { + variant: 'error', + }, + ); + } + }, + onError: () => { + alert.show( + 'Failed to extract entries using NLP.', + { + variant: 'error', + }, + ); + }, + }, + ); + + const handleAutoExtractClick = useCallback(() => { + if (isNotDefined(projectId)) { + return; + } + + triggerAutoEntriesCreate({ + variables: { + projectId, + leadId, + }, + }); + }, [ + projectId, + leadId, + triggerAutoEntriesCreate, + ]); + const leadPreview = leadPreviewData?.project?.lead?.leadPreview; const extractionStatus = leadPreviewData?.project?.lead?.extractionStatus; @@ -536,62 +613,72 @@ function LeftPane(props: Props) { activeClassName={styles.simplifiedTab} retainMount="lazy" > - {(leadPreview?.textExtract?.length ?? 0) > 0 ? ( - - ) : ( - - )} - message={( - (extractionStatus === 'PENDING' - || extractionStatus === 'STARTED') - ? 'Simplified text is currently being extracted from this source. Please retry after few minutes.' - : 'Oops! Either the source was empty or we couldn\'t extract its text.' - )} - errored={extractionStatus === 'FAILED'} - erroredEmptyIcon={( - - )} - erroredEmptyMessage="There was an when issue extracting simplified - text for this source." - actions={(extractionStatus === 'PENDING' || extractionStatus === 'STARTED') && ( - - )} - /> - )} + <> + + + {(leadPreview?.textExtract?.length ?? 0) > 0 ? ( + + ) : ( + + )} + message={( + (extractionStatus === 'PENDING' + || extractionStatus === 'STARTED') + ? 'Simplified text is currently being extracted from this source. Please retry after few minutes.' + : 'Oops! Either the source was empty or we couldn\'t extract its text.' + )} + errored={extractionStatus === 'FAILED'} + erroredEmptyIcon={( + + )} + erroredEmptyMessage="There was an when issue extracting simplified + text for this source." + actions={(extractionStatus === 'PENDING' || extractionStatus === 'STARTED') && ( + + )} + /> + )} + )} {!hideOriginalPreview && ( @@ -637,6 +724,13 @@ function LeftPane(props: Props) { /> + {autoEntriesModalShown && isDefined(projectId) && ( + + )} ); } diff --git a/app/views/EntryEdit/LeftPane/styles.css b/app/views/EntryEdit/LeftPane/styles.css index cefec76d1f..e3b035c498 100644 --- a/app/views/EntryEdit/LeftPane/styles.css +++ b/app/views/EntryEdit/LeftPane/styles.css @@ -31,7 +31,7 @@ background-color: var(--dui-color-background-information); padding: var(--dui-spacing-large); overflow: auto; - gap: var(--dui-spacing-small); + gap: var(--dui-spacing-medium); .switch { flex-shrink: 0; From 291fe72dbdb9db64ea34001e608d98b63103cc8d Mon Sep 17 00:00:00 2001 From: Subina Date: Tue, 28 Nov 2023 09:48:30 +0545 Subject: [PATCH 06/11] Data transformation for draft entries --- app/types/user.tsx | 4 +- .../LeftPane/AssistItem/AssistPopup/index.tsx | 4 +- .../LeftPane/AutoEntriesModal/index.tsx | 589 +++++++++++++++++- app/views/EntryEdit/LeftPane/index.tsx | 83 ++- .../Home/Assignment/AssignmentItem/index.tsx | 2 +- 5 files changed, 671 insertions(+), 11 deletions(-) diff --git a/app/types/user.tsx b/app/types/user.tsx index 7978737a5e..79f3002f52 100644 --- a/app/types/user.tsx +++ b/app/types/user.tsx @@ -36,12 +36,12 @@ export interface Assignment { displayName: string; email: string; }; - contentObjectDetails: { + contentObjectDetails?: { id: number; title: string; lead?: string; entry?: string; - }; + } | undefined; isDone: boolean; contentObjectType: 'lead' | 'entryreviewcomment' | 'entrycomment'; } diff --git a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx index 007552694b..5625b58788 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx @@ -34,6 +34,7 @@ interface Props { onChange: (val: SetValueArg, name: undefined) => void; error: Error | undefined; onEntryCreateButtonClick: () => void; + variant?: 'normal' | 'compact' | 'nlp'; // NOTE: Normal entry creation refers to entry created without use of // recommendations onNormalEntryCreateButtonClick: () => void; @@ -50,6 +51,7 @@ interface Props { function AssistPopup(props: Props) { const { className, + variant = 'nlp', leadId, value, onChange, @@ -153,7 +155,7 @@ function AssistPopup(props: Props) { recommendations={recommendations} emptyValueHidden addButtonHidden - variant="nlp" + variant={variant} /> )} diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index d801b13109..e8202405c8 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -1,14 +1,56 @@ -import React, { useMemo } from 'react'; -import { isNotDefined } from '@togglecorp/fujs'; +import React, { + useMemo, + useCallback, + useState, +} from 'react'; +import { + isNotDefined, + isDefined, + randomString, + noOp, + listToMap, +} from '@togglecorp/fujs'; import { gql, useQuery } from '@apollo/client'; import { Modal, + ListView, } from '@the-deep/deep-ui'; +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 { + PartialEntryType, + PartialAttributeType, +} from '#components/entry/schema'; import { AutoEntriesForLeadQuery, AutoEntriesForLeadQueryVariables, } from '#generated/types'; +import AssistPopup from '../AssistItem/AssistPopup'; + +import { + createOrganigramAttr, + createMatrix1dAttr, + createMatrix2dAttr, + createScaleAttr, + createSelectAttr, + createMultiSelectAttr, + createGeoAttr, +} from '../AssistItem/utils'; + +const GEOLOCATION_DEEPL_MODEL_ID = 'geolocation'; const AUTO_ENTRIES_FOR_LEAD = gql` query AutoEntriesForLead( @@ -26,16 +68,217 @@ const AUTO_ENTRIES_FOR_LEAD = gql` excerpt predictionReceivedAt predictionStatus + predictions { + id + draftEntry + tag + dataTypeDisplay + dataType + category + isSelected + modelVersion + modelVersionDeeplModelId + prediction + threshold + value + } } } } } `; +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 entryKeySelector = (entry: PartialEntryType) => entry.clientId; + interface Props { onModalClose: () => void; projectId: string; leadId: string; + frameworkDetails: Framework; } function AutoEntriesModal(props: Props) { @@ -43,8 +286,231 @@ function AutoEntriesModal(props: Props) { onModalClose, projectId, leadId, + frameworkDetails, } = props; + const [ + geoAreaOptions, + setGeoAreaOptions, + ] = useState<{ + entryId: string; + geoAreas: GeoArea[] | undefined | null; + } | undefined>(); + + // const [messageText, setMessageText] = useState(); + + const [ + allRecommendations, + setAllRecommendations, + ] = useState | undefined>(undefined); + + const [allHints, setAllHints] = useState< + Record | undefined + >(undefined); + + const mappings = frameworkDetails?.predictionTagsMapping; + + const { + allWidgets, + filteredWidgets, + } = useMemo(() => { + const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( + (item) => (item.widgets ?? []), + ) ?? []; + const widgetsFromSecondary = frameworkDetails?.secondaryTagging ?? []; + const widgets = [ + ...widgetsFromPrimary, + ...widgetsFromSecondary, + ]; + return { + allWidgets: widgets, + filteredWidgets: widgets.filter((w) => mappingsSupportedWidgets.includes(w.widgetId)), + }; + }, [ + frameworkDetails, + ]); + + /* + const handleMappingsFetchTest = 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: [], + }, + ); + + if (recommendedAttributes.length <= 0 && widgetsHints.length <= 0) { + setMessageText( + 'The provided recommendations for this text did + not fit any tags in this project.', + ); + return; + } + setAllHints(widgetsHints); + setAllRecommendations(recommendedAttributes); + return { + hints: widgetsHints, + recommendations: recommendedAttributes, + + }; + }, + [ + mappings, + filteredWidgets, + ], + ); + */ + + const [ + draftEntries, + setDraftEntries, + ] = useState([]); + const autoEntriesVariables = useMemo(() => ({ projectId, leadIds: [leadId], @@ -54,22 +520,135 @@ function AutoEntriesModal(props: Props) { ]); const { - data: autoEntries, + loading: autoEntriesLoading, } = useQuery( AUTO_ENTRIES_FOR_LEAD, { skip: isNotDefined(autoEntriesVariables), variables: autoEntriesVariables, + onCompleted: (response) => { + const entries = response.project?.assistedTagging?.draftEntryByLeads; + const transformedEntries = (entries ?? [])?.map((entry) => { + const validPredictions = entry.predictions?.filter(isDefined); + const categoricalTags = validPredictions?.filter( + (prediction) => ( + prediction.modelVersionDeeplModelId !== GEOLOCATION_DEEPL_MODEL_ID + && prediction.isSelected + ), + ).map( + (prediction) => prediction.tag, + ).filter(isDefined) ?? []; + + const entryAttributeData: EntryAttributes = { + predictions: { + tags: categoricalTags, + locations: [], + }, + mappings, + filteredWidgets, + }; + + const { + hints: entryHints, + recommendations: entryRecommendations, + geoAreas: entryGeoAreas, + } = handleMappingsFetch(entryAttributeData); + + const entryId = randomString(); + const requiredEntry = { + clientId: entryId, + entryType: 'EXCERPT' as const, + lead: leadId, + excerpt: entry.excerpt, + 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) ?? [], + }, + }); + }), + }; + + return { + entryId, + geoLocations: entryGeoAreas, + recommendations: entryRecommendations, + hints: entryHints, + entry: requiredEntry, + }; + }); + 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, + ); + const entryGeoAreas = listToMap( + transformedEntries, + (item) => item.entryId, + (item) => item.geoLocations, + ); + setDraftEntries(requiredDraftEntries); + setAllRecommendations(entryRecommendations); + setAllHints(entryHints); + setGeoAreaOptions(entryGeoAreas); + }, }, ); - console.log('here', autoEntries); + const rendererParams = useCallback((entryId: string, datum: PartialEntryType) => ({ + frameworkDetails, + value: datum, + onChange: noOp, + leadId, + hints: allHints?.[entryId], + recommendations: allRecommendations?.[entryId], + geoAreaOptions: undefined, + onEntryDiscardButtonClick: noOp, + onEntryCreateButtonClick: noOp, + onNormalEntryCreateButtonClick: noOp, + onGeoAreaOptionsChange: noOp, + predictionsLoading: false, + predictionsErrored: false, + messageText: undefined, + variant: 'normal' as const, + error: undefined, + }), [ + allHints, + allRecommendations, + frameworkDetails, + leadId, + ]); return ( - Auto entries here + ); } diff --git a/app/views/EntryEdit/LeftPane/index.tsx b/app/views/EntryEdit/LeftPane/index.tsx index 02e15000ab..ff4c6ad1ef 100644 --- a/app/views/EntryEdit/LeftPane/index.tsx +++ b/app/views/EntryEdit/LeftPane/index.tsx @@ -47,6 +47,8 @@ import { LeadPreviewForTextQueryVariables, CreateAutoDraftEntriesMutation, CreateAutoDraftEntriesMutationVariables, + AutoDraftEntriesStatusQuery, + AutoDraftEntriesStatusQueryVariables, } from '#generated/types'; import { PartialEntryType as EntryInput } from '#components/entry/schema'; @@ -93,6 +95,22 @@ const CREATE_AUTO_DRAFT_ENTRIES = gql` } `; +const AUTO_DRAFT_ENTRIES_STATUS = gql` + query AutoDraftEntriesStatus ( + $projectId: ID!, + $leadId: ID!, + ) { + project(id: $projectId) { + id + assistedTagging { + extractionStatusByLead(leadId: $leadId) { + autoEntryExtractionStatus + } + } + } + } +`; + const entryKeySelector = (e: EntryInput) => e.clientId; export type TabOptions = 'simplified' | 'original' | 'entries' | undefined; @@ -219,6 +237,65 @@ function LeftPane(props: Props) { }, ); + // 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 autoEntryStatusVariables = useMemo(() => { + if (isNotDefined(projectId)) { + return undefined; + } + return ({ + leadId, + randomId, + projectId, + }); + }, [ + randomId, + leadId, + projectId, + ]); + + const { + data: autoEntryExtractionStatus, + refetch: retriggerEntryExtractionStatus, + } = useQuery( + AUTO_DRAFT_ENTRIES_STATUS, + { + skip: isNotDefined(autoEntryStatusVariables), + variables: autoEntryStatusVariables, + }, + ); + + const [draftEntriesLoading, setDraftEntriesLoading] = useState(false); + + // TODO: This polling calls two queries at a time. Fix this. + useEffect(() => { + const timeout = setTimeout( + () => { + const shouldPoll = autoEntryExtractionStatus?.project + ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus === 'PENDING'; + if (shouldPoll) { + setDraftEntriesLoading(true); + setRandomId(randomString()); + retriggerEntryExtractionStatus(); + } else { + setDraftEntriesLoading(false); + } + }, + 2000, + ); + + return () => { + clearTimeout(timeout); + }; + }, [ + autoEntryExtractionStatus?.project?.assistedTagging + ?.extractionStatusByLead?.autoEntryExtractionStatus, + leadId, + retriggerEntryExtractionStatus, + ]); + const [ triggerAutoEntriesCreate, ] = useMutation( @@ -242,13 +319,14 @@ function LeftPane(props: Props) { ); } }, - onError: () => { + onError: (error) => { alert.show( 'Failed to extract entries using NLP.', { variant: 'error', }, ); + showAutoEntriesModal(); }, }, ); @@ -724,11 +802,12 @@ function LeftPane(props: Props) { /> - {autoEntriesModalShown && isDefined(projectId) && ( + {autoEntriesModalShown && isDefined(projectId) && frameworkDetails && ( )} diff --git a/app/views/Home/Assignment/AssignmentItem/index.tsx b/app/views/Home/Assignment/AssignmentItem/index.tsx index 6386d1ff64..97ef179d29 100644 --- a/app/views/Home/Assignment/AssignmentItem/index.tsx +++ b/app/views/Home/Assignment/AssignmentItem/index.tsx @@ -55,7 +55,7 @@ function AssignmentItem(props: AssignmentItemProps) { )); } if (contentObjectType === 'entryreviewcomment' || contentObjectType === 'entrycomment') { - if (!projectDetails?.id && contentObjectDetails?.lead) { + if (!contentObjectDetails || !projectDetails?.id || !contentObjectDetails?.lead) { return ( an entry From 68acb0f00613a71f302e1d5047d60646291ae5a0 Mon Sep 17 00:00:00 2001 From: Aditya Khatri Date: Thu, 30 Nov 2023 17:01:53 +0545 Subject: [PATCH 07/11] Rearrange queries and mutation for auto entries modal --- .../LeftPane/AutoEntriesModal/index.tsx | 380 +++++++++--------- app/views/EntryEdit/LeftPane/index.tsx | 154 +------ 2 files changed, 195 insertions(+), 339 deletions(-) diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index e8202405c8..795b877cfe 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -1,5 +1,6 @@ import React, { useMemo, + useEffect, useCallback, useState, } from 'react'; @@ -10,10 +11,17 @@ import { noOp, listToMap, } from '@togglecorp/fujs'; -import { gql, useQuery } from '@apollo/client'; +import { + gql, + useQuery, + useMutation, +} from '@apollo/client'; import { Modal, ListView, + useAlert, + Button, + Message, } from '@the-deep/deep-ui'; import { type Framework } from '#components/entry/types'; @@ -37,6 +45,10 @@ import { import { AutoEntriesForLeadQuery, AutoEntriesForLeadQueryVariables, + CreateAutoDraftEntriesMutation, + CreateAutoDraftEntriesMutationVariables, + AutoDraftEntriesStatusQuery, + AutoDraftEntriesStatusQueryVariables, } from '#generated/types'; import AssistPopup from '../AssistItem/AssistPopup'; @@ -58,6 +70,7 @@ const AUTO_ENTRIES_FOR_LEAD = gql` $leadIds: [ID!], ) { project(id: $projectId) { + id assistedTagging { draftEntryByLeads( filter: { @@ -88,6 +101,39 @@ const AUTO_ENTRIES_FOR_LEAD = gql` } `; +const CREATE_AUTO_DRAFT_ENTRIES = gql` + mutation CreateAutoDraftEntries ( + $projectId: ID!, + $leadId: ID!, + ) { + project(id: $projectId) { + id + assistedTagging { + autoDraftEntryCreate(data: {lead: $leadId}) { + ok + errors + } + } + } + } +`; + +const AUTO_DRAFT_ENTRIES_STATUS = gql` + query AutoDraftEntriesStatus ( + $projectId: ID!, + $leadId: ID!, + ) { + project(id: $projectId) { + id + assistedTagging { + extractionStatusByLead(leadId: $leadId) { + autoEntryExtractionStatus + } + } + } + } +`; + interface EntryAttributes { predictions: { tags: string[]; @@ -289,15 +335,124 @@ function AutoEntriesModal(props: Props) { frameworkDetails, } = props; + const alert = useAlert(); + const [ geoAreaOptions, setGeoAreaOptions, - ] = useState<{ - entryId: string; - geoAreas: GeoArea[] | undefined | null; - } | undefined>(); + ] = useState | undefined>(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 [messageText, setMessageText] = useState(); + const autoEntryStatusVariables = useMemo(() => { + if (isNotDefined(projectId)) { + return undefined; + } + return ({ + leadId, + randomId, + projectId, + }); + }, [ + randomId, + leadId, + projectId, + ]); + + const [draftEntriesLoading, setDraftEntriesLoading] = useState(true); + + const { + data: autoEntryExtractionStatus, + refetch: retriggerEntryExtractionStatus, + } = useQuery( + AUTO_DRAFT_ENTRIES_STATUS, + { + skip: isNotDefined(autoEntryStatusVariables), + variables: autoEntryStatusVariables, + onCompleted: (response) => { + const status = response?.project + ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus; + if (status === 'SUCCESS') { + setDraftEntriesLoading(false); + } + }, + }, + ); + + const extractionStatus = autoEntryExtractionStatus?.project + ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus; + + // TODO: This polling calls two queries at a time. Fix this. + useEffect(() => { + const timeout = setTimeout( + () => { + const shouldPoll = autoEntryExtractionStatus?.project + ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus === 'PENDING'; + if (shouldPoll) { + setDraftEntriesLoading(true); + setRandomId(randomString()); + retriggerEntryExtractionStatus(); + } else { + setDraftEntriesLoading(false); + } + }, + 2000, + ); + + return () => { + clearTimeout(timeout); + }; + }, [ + autoEntryExtractionStatus?.project?.assistedTagging + ?.extractionStatusByLead?.autoEntryExtractionStatus, + leadId, + retriggerEntryExtractionStatus, + ]); + + const [ + triggerAutoEntriesCreate, + ] = useMutation( + CREATE_AUTO_DRAFT_ENTRIES, + { + onCompleted: (response) => { + const autoEntriesResponse = response?.project?.assistedTagging + ?.autoDraftEntryCreate; + if (autoEntriesResponse?.ok) { + retriggerEntryExtractionStatus(); + } else { + alert.show( + 'Failed to extract entries using NLP.', + { + variant: 'error', + }, + ); + } + }, + onError: () => { + alert.show( + 'Failed to extract entries using NLP.', + { + variant: 'error', + }, + ); + }, + }, + ); + + const handleAutoExtractClick = useCallback(() => { + triggerAutoEntriesCreate({ + variables: { + projectId, + leadId, + }, + }); + }, [ + projectId, + leadId, + triggerAutoEntriesCreate, + ]); const [ allRecommendations, @@ -311,7 +466,6 @@ function AutoEntriesModal(props: Props) { const mappings = frameworkDetails?.predictionTagsMapping; const { - allWidgets, filteredWidgets, } = useMemo(() => { const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( @@ -330,182 +484,6 @@ function AutoEntriesModal(props: Props) { frameworkDetails, ]); - /* - const handleMappingsFetchTest = 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: [], - }, - ); - - if (recommendedAttributes.length <= 0 && widgetsHints.length <= 0) { - setMessageText( - 'The provided recommendations for this text did - not fit any tags in this project.', - ); - return; - } - setAllHints(widgetsHints); - setAllRecommendations(recommendedAttributes); - return { - hints: widgetsHints, - recommendations: recommendedAttributes, - - }; - }, - [ - mappings, - filteredWidgets, - ], - ); - */ - const [ draftEntries, setDraftEntries, @@ -524,7 +502,9 @@ function AutoEntriesModal(props: Props) { } = useQuery( AUTO_ENTRIES_FOR_LEAD, { - skip: isNotDefined(autoEntriesVariables), + skip: isNotDefined(extractionStatus) + || extractionStatus !== 'SUCCESS' + || isNotDefined(autoEntriesVariables), variables: autoEntriesVariables, onCompleted: (response) => { const entries = response.project?.assistedTagging?.draftEntryByLeads; @@ -616,7 +596,7 @@ function AutoEntriesModal(props: Props) { leadId, hints: allHints?.[entryId], recommendations: allRecommendations?.[entryId], - geoAreaOptions: undefined, + geoAreaOptions: geoAreaOptions?.[entryId], onEntryDiscardButtonClick: noOp, onEntryCreateButtonClick: noOp, onNormalEntryCreateButtonClick: noOp, @@ -631,23 +611,45 @@ function AutoEntriesModal(props: Props) { allRecommendations, frameworkDetails, leadId, + geoAreaOptions, ]); + const hideList = draftEntriesLoading + || extractionStatus === 'NONE'; + return ( + Recommend entries + + )} + messageShown + messageIconShown + borderBetweenItem + /> + ); diff --git a/app/views/EntryEdit/LeftPane/index.tsx b/app/views/EntryEdit/LeftPane/index.tsx index ff4c6ad1ef..6db38243ea 100644 --- a/app/views/EntryEdit/LeftPane/index.tsx +++ b/app/views/EntryEdit/LeftPane/index.tsx @@ -5,7 +5,7 @@ import { isDefined, randomString, } from '@togglecorp/fujs'; -import { gql, useQuery, useMutation } from '@apollo/client'; +import { gql, useQuery } from '@apollo/client'; import { Tabs, Container, @@ -45,10 +45,6 @@ import { UserContext } from '#base/context/UserContext'; import { LeadPreviewForTextQuery, LeadPreviewForTextQueryVariables, - CreateAutoDraftEntriesMutation, - CreateAutoDraftEntriesMutationVariables, - AutoDraftEntriesStatusQuery, - AutoDraftEntriesStatusQueryVariables, } from '#generated/types'; import { PartialEntryType as EntryInput } from '#components/entry/schema'; @@ -79,38 +75,6 @@ const LEAD_PREVIEW = gql` } `; -const CREATE_AUTO_DRAFT_ENTRIES = gql` - mutation CreateAutoDraftEntries ( - $projectId: ID!, - $leadId: ID!, - ) { - project(id: $projectId) { - assistedTagging { - autoDraftEntryCreate(data: {lead: $leadId}) { - ok - errors - } - } - } - } -`; - -const AUTO_DRAFT_ENTRIES_STATUS = gql` - query AutoDraftEntriesStatus ( - $projectId: ID!, - $leadId: ID!, - ) { - project(id: $projectId) { - id - assistedTagging { - extractionStatusByLead(leadId: $leadId) { - autoEntryExtractionStatus - } - } - } - } -`; - const entryKeySelector = (e: EntryInput) => e.clientId; export type TabOptions = 'simplified' | 'original' | 'entries' | undefined; @@ -237,117 +201,6 @@ function LeftPane(props: Props) { }, ); - // 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 autoEntryStatusVariables = useMemo(() => { - if (isNotDefined(projectId)) { - return undefined; - } - return ({ - leadId, - randomId, - projectId, - }); - }, [ - randomId, - leadId, - projectId, - ]); - - const { - data: autoEntryExtractionStatus, - refetch: retriggerEntryExtractionStatus, - } = useQuery( - AUTO_DRAFT_ENTRIES_STATUS, - { - skip: isNotDefined(autoEntryStatusVariables), - variables: autoEntryStatusVariables, - }, - ); - - const [draftEntriesLoading, setDraftEntriesLoading] = useState(false); - - // TODO: This polling calls two queries at a time. Fix this. - useEffect(() => { - const timeout = setTimeout( - () => { - const shouldPoll = autoEntryExtractionStatus?.project - ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus === 'PENDING'; - if (shouldPoll) { - setDraftEntriesLoading(true); - setRandomId(randomString()); - retriggerEntryExtractionStatus(); - } else { - setDraftEntriesLoading(false); - } - }, - 2000, - ); - - return () => { - clearTimeout(timeout); - }; - }, [ - autoEntryExtractionStatus?.project?.assistedTagging - ?.extractionStatusByLead?.autoEntryExtractionStatus, - leadId, - retriggerEntryExtractionStatus, - ]); - - const [ - triggerAutoEntriesCreate, - ] = useMutation( - CREATE_AUTO_DRAFT_ENTRIES, - { - onCompleted: (response) => { - const autoEntriesResponse = response?.project?.assistedTagging - ?.autoDraftEntryCreate; - if (autoEntriesResponse?.ok) { - alert.show( - 'The entries extraction has started.', - { variant: 'success' }, - ); - showAutoEntriesModal(); - } else { - alert.show( - 'Failed to extract entries using NLP.', - { - variant: 'error', - }, - ); - } - }, - onError: (error) => { - alert.show( - 'Failed to extract entries using NLP.', - { - variant: 'error', - }, - ); - showAutoEntriesModal(); - }, - }, - ); - - const handleAutoExtractClick = useCallback(() => { - if (isNotDefined(projectId)) { - return; - } - - triggerAutoEntriesCreate({ - variables: { - projectId, - leadId, - }, - }); - }, [ - projectId, - leadId, - triggerAutoEntriesCreate, - ]); - const leadPreview = leadPreviewData?.project?.lead?.leadPreview; const extractionStatus = leadPreviewData?.project?.lead?.extractionStatus; @@ -694,8 +547,9 @@ function LeftPane(props: Props) { <> From edd00475ba8eef699d8871cca571a951a8ad3726 Mon Sep 17 00:00:00 2001 From: Aditya Khatri Date: Mon, 4 Dec 2023 16:02:50 +0545 Subject: [PATCH 08/11] Add ability to add entries from recommendations --- .../LeftPane/AssistItem/AssistPopup/index.tsx | 14 +- .../EntryEdit/LeftPane/AssistItem/index.tsx | 1 + .../LeftPane/AutoEntriesModal/index.tsx | 241 +++++++++++++----- .../LeftPane/AutoEntriesModal/styles.css | 7 + app/views/EntryEdit/LeftPane/index.tsx | 2 + 5 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css diff --git a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx index 5625b58788..dfaea0852f 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx @@ -26,12 +26,13 @@ import { import styles from './styles.css'; -interface Props { +interface Props { className?: string; + entryInputClassName?: string; frameworkDetails: Framework; leadId: string; value: PartialEntryType; - onChange: (val: SetValueArg, name: undefined) => void; + onChange: (val: SetValueArg, name: NAME) => void; error: Error | undefined; onEntryCreateButtonClick: () => void; variant?: 'normal' | 'compact' | 'nlp'; @@ -45,16 +46,19 @@ interface Props { hints: WidgetHint[] | undefined; recommendations: PartialAttributeType[] | undefined; predictionsErrored: boolean; + name: NAME; messageText: string | undefined; } -function AssistPopup(props: Props) { +function AssistPopup(props: Props) { const { className, + entryInputClassName, variant = 'nlp', leadId, value, onChange, + name, error, frameworkDetails, onEntryCreateButtonClick, @@ -138,9 +142,9 @@ function AssistPopup(props: Props) { /> ) : ( void + ) | undefined; } function AutoEntriesModal(props: Props) { @@ -332,10 +344,61 @@ function AutoEntriesModal(props: Props) { onModalClose, projectId, leadId, + onAssistedEntryAdd, frameworkDetails, + createdEntries, } = props; const alert = useAlert(); + const draftEntriesMap = useMemo(() => ( + listToMap( + createdEntries?.filter((item) => isDefined(item.draftEntry)) ?? [], + (item) => item.draftEntry ?? '', + () => true, + ) + ), [createdEntries]); + + const { + allWidgets, + filteredWidgets, + } = useMemo(() => { + const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( + (item) => (item.widgets ?? []), + ) ?? []; + const widgetsFromSecondary = frameworkDetails?.secondaryTagging ?? []; + const widgets = [ + ...widgetsFromPrimary, + ...widgetsFromSecondary, + ]; + return { + allWidgets: widgets, + filteredWidgets: widgets.filter((w) => mappingsSupportedWidgets.includes(w.widgetId)), + }; + }, [ + frameworkDetails, + ]); + + const schema = useMemo( + () => { + const widgetsMapping = listToMap( + allWidgets, + (item) => item.id, + (item) => item, + ); + + return getSchema(widgetsMapping); + }, + [allWidgets], + ); + const { + setValue, + value, + setFieldValue, + } = useForm(schema, defaultFormValues); + + const { + setValue: onEntryChange, + } = useFormArray<'entries', PartialEntryType>('entries', setFieldValue); const [ geoAreaOptions, @@ -388,8 +451,7 @@ function AutoEntriesModal(props: Props) { useEffect(() => { const timeout = setTimeout( () => { - const shouldPoll = autoEntryExtractionStatus?.project - ?.assistedTagging?.extractionStatusByLead?.autoEntryExtractionStatus === 'PENDING'; + const shouldPoll = extractionStatus === 'PENDING' || extractionStatus === 'STARTED'; if (shouldPoll) { setDraftEntriesLoading(true); setRandomId(randomString()); @@ -405,8 +467,7 @@ function AutoEntriesModal(props: Props) { clearTimeout(timeout); }; }, [ - autoEntryExtractionStatus?.project?.assistedTagging - ?.extractionStatusByLead?.autoEntryExtractionStatus, + extractionStatus, leadId, retriggerEntryExtractionStatus, ]); @@ -465,30 +526,6 @@ function AutoEntriesModal(props: Props) { const mappings = frameworkDetails?.predictionTagsMapping; - const { - filteredWidgets, - } = useMemo(() => { - const widgetsFromPrimary = frameworkDetails?.primaryTagging?.flatMap( - (item) => (item.widgets ?? []), - ) ?? []; - const widgetsFromSecondary = frameworkDetails?.secondaryTagging ?? []; - const widgets = [ - ...widgetsFromPrimary, - ...widgetsFromSecondary, - ]; - return { - allWidgets: widgets, - filteredWidgets: widgets.filter((w) => mappingsSupportedWidgets.includes(w.widgetId)), - }; - }, [ - frameworkDetails, - ]); - - const [ - draftEntries, - setDraftEntries, - ] = useState([]); - const autoEntriesVariables = useMemo(() => ({ projectId, leadIds: [leadId], @@ -540,6 +577,7 @@ function AutoEntriesModal(props: Props) { entryType: 'EXCERPT' as const, lead: leadId, excerpt: entry.excerpt, + draftEntry: entry.id, droppedExcerpt: entry.excerpt, attributes: entryRecommendations?.map((attr) => { if (attr.widgetType !== 'GEO') { @@ -581,7 +619,9 @@ function AutoEntriesModal(props: Props) { (item) => item.entryId, (item) => item.geoLocations, ); - setDraftEntries(requiredDraftEntries); + setValue({ + entries: requiredDraftEntries, + }); setAllRecommendations(entryRecommendations); setAllHints(entryHints); setGeoAreaOptions(entryGeoAreas); @@ -589,24 +629,100 @@ function AutoEntriesModal(props: Props) { }, ); - const rendererParams = useCallback((entryId: string, datum: PartialEntryType) => ({ - frameworkDetails, - value: datum, - onChange: noOp, - leadId, - hints: allHints?.[entryId], - recommendations: allRecommendations?.[entryId], - geoAreaOptions: geoAreaOptions?.[entryId], - onEntryDiscardButtonClick: noOp, - onEntryCreateButtonClick: noOp, - onNormalEntryCreateButtonClick: noOp, - onGeoAreaOptionsChange: noOp, - predictionsLoading: false, - predictionsErrored: false, - messageText: undefined, - variant: 'normal' as const, - error: undefined, - }), [ + const handleEntryCreateButtonClick = useCallback((entryId: string) => { + if (!allRecommendations?.[entryId]) { + return; + } + + const selectedEntry = value?.entries?.find((item) => item.clientId === entryId); + if (onAssistedEntryAdd && selectedEntry) { + const defaultAttributes = createDefaultAttributes(allWidgets); + + const newAttributes = mergeLists( + defaultAttributes, + selectedEntry?.attributes ?? [], + (attr) => attr.widget, + (defaultAttr, newAttr) => ({ + ...newAttr, + clientId: defaultAttr.clientId, + widget: defaultAttr.widget, + id: defaultAttr.id, + widgetVersion: defaultAttr.widgetVersion, + }), + ); + + onAssistedEntryAdd( + { + ...selectedEntry, + attributes: newAttributes, + }, + geoAreaOptions?.[entryId] ?? undefined, + ); + + alert.show( + 'Successfully added entry from recommendation.', + { + variant: 'success', + }, + ); + } else { + alert.show( + 'Failed to add entry from recommendations.', + { + variant: 'error', + }, + ); + } + }, [ + alert, + value?.entries, + allWidgets, + geoAreaOptions, + allRecommendations, + onAssistedEntryAdd, + ]); + + const filteredEntries = useMemo(() => ( + value?.entries?.filter( + (item) => item.draftEntry && !draftEntriesMap[item.draftEntry], + ) + ), [ + value?.entries, + draftEntriesMap, + ]); + + const rendererParams = useCallback(( + entryId: string, + datum: PartialEntryType, + ) => { + const onEntryCreateButtonClick = () => handleEntryCreateButtonClick(entryId); + const index = value?.entries?.findIndex((item) => item.clientId === entryId); + + return ({ + frameworkDetails, + value: datum, + className: styles.listItem, + entryInputClassName: styles.entryInput, + name: index, + onChange: onEntryChange, + leadId, + hints: allHints?.[entryId], + recommendations: allRecommendations?.[entryId], + geoAreaOptions: geoAreaOptions?.[entryId], + onEntryDiscardButtonClick: noOp, + onEntryCreateButtonClick, + onNormalEntryCreateButtonClick: noOp, + onGeoAreaOptionsChange: noOp, + predictionsLoading: false, + predictionsErrored: false, + messageText: undefined, + variant: 'normal' as const, + error: undefined, + }); + }, [ + value?.entries, + handleEntryCreateButtonClick, + onEntryChange, allHints, allRecommendations, frameworkDetails, @@ -614,26 +730,39 @@ function AutoEntriesModal(props: Props) { geoAreaOptions, ]); + const isFiltered = useMemo(() => ( + (filteredEntries?.length ?? 0) < (value?.entries?.length ?? 0) + ), [ + filteredEntries, + value?.entries, + ]); + const hideList = draftEntriesLoading + || autoEntriesLoading || extractionStatus === 'NONE'; return ( - ); } diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css new file mode 100644 index 0000000000..065f48366c --- /dev/null +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css @@ -0,0 +1,7 @@ +.modal-body { + .list-item { + .entry-input { + height: 360px; + } + } +} diff --git a/app/views/EntryEdit/LeftPane/index.tsx b/app/views/EntryEdit/LeftPane/index.tsx index 6db38243ea..7cee0ecd29 100644 --- a/app/views/EntryEdit/LeftPane/index.tsx +++ b/app/views/EntryEdit/LeftPane/index.tsx @@ -658,8 +658,10 @@ function LeftPane(props: Props) { {autoEntriesModalShown && isDefined(projectId) && frameworkDetails && ( From 138634f40ac7513c8980de569395d4f44366339a Mon Sep 17 00:00:00 2001 From: Subina Date: Wed, 6 Dec 2023 13:43:45 +0545 Subject: [PATCH 09/11] Make entry input similar to assisted tagging --- app/components/entry/EntryInput/index.tsx | 8 +++++++- app/components/entry/EntryInput/styles.css | 8 ++++++++ .../EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx | 6 ++++++ app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/components/entry/EntryInput/index.tsx b/app/components/entry/EntryInput/index.tsx index 735af63fc6..90d07459e3 100644 --- a/app/components/entry/EntryInput/index.tsx +++ b/app/components/entry/EntryInput/index.tsx @@ -81,6 +81,9 @@ interface EntryInputProps { allWidgets: Widget[] | undefined | null; rightComponent?: React.ReactNode; noPaddingInWidgetContainer?: boolean; + + excerptShown?: boolean; + displayHorizontally?: boolean; } function EntryInput(props: EntryInputProps) { @@ -111,6 +114,8 @@ function EntryInput(props: EntryInputProp onApplyToAll, rightComponent, noPaddingInWidgetContainer = false, + excerptShown = false, + displayHorizontally = false, } = props; const error = getErrorObject(riskyError); @@ -194,11 +199,12 @@ function EntryInput(props: EntryInputProp className={_cs( className, compactMode && styles.compact, + displayHorizontally && styles.horizontal, styles.entryInput, )} > - {!compactMode && ( + {(!compactMode || excerptShown) && ( { predictionsErrored: boolean; name: NAME; messageText: string | undefined; + excerptShown?: boolean; + displayHorizontally?: boolean; } function AssistPopup(props: Props) { @@ -71,6 +73,8 @@ function AssistPopup(props: Props { @@ -160,6 +164,8 @@ function AssistPopup(props: Props )} diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index 4493c4b39b..1e3db429da 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -716,8 +716,10 @@ function AutoEntriesModal(props: Props) { predictionsLoading: false, predictionsErrored: false, messageText: undefined, - variant: 'normal' as const, + variant: 'nlp' as const, error: undefined, + excerptShown: true, + displayHorizontally: true, }); }, [ value?.entries, From 42abd4abf24d1f8cc7aa304b6fdac7b56940b865 Mon Sep 17 00:00:00 2001 From: Subina Date: Wed, 13 Dec 2023 10:07:28 +0545 Subject: [PATCH 10/11] Enable discard/undiscard entry - Disallow duplicate entries creation. - Remove selection of entry after creation from auto draft entries --- app/components/entry/EntryInput/index.tsx | 1 + app/components/entry/EntryInput/styles.css | 3 + .../LeftPane/AssistItem/AssistPopup/index.tsx | 37 +-- .../EntryEdit/LeftPane/AssistItem/index.tsx | 39 ++- .../LeftPane/AutoEntriesModal/index.tsx | 308 +++++++++++++++--- .../LeftPane/AutoEntriesModal/styles.css | 5 + app/views/EntryEdit/index.tsx | 14 +- 7 files changed, 326 insertions(+), 81 deletions(-) diff --git a/app/components/entry/EntryInput/index.tsx b/app/components/entry/EntryInput/index.tsx index 90d07459e3..48bc774b2b 100644 --- a/app/components/entry/EntryInput/index.tsx +++ b/app/components/entry/EntryInput/index.tsx @@ -250,6 +250,7 @@ function EntryInput(props: EntryInputProp styles.section, sectionContainerClassName, compactMode && styles.compact, + displayHorizontally && styles.horizontal, )} renderer={CompactSection} keySelector={sectionKeySelector} diff --git a/app/components/entry/EntryInput/styles.css b/app/components/entry/EntryInput/styles.css index dd68ab066b..86d0ff405c 100644 --- a/app/components/entry/EntryInput/styles.css +++ b/app/components/entry/EntryInput/styles.css @@ -69,6 +69,9 @@ &.compact { border-bottom: var(--dui-width-separator-thin) solid var(--dui-color-separator); } + &.horizontal { + border-bottom: unset; + } } .secondary-tagging { diff --git a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx index 1aeb173ba0..7aeab3323a 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/AssistPopup/index.tsx @@ -4,16 +4,11 @@ import { Kraken, Container, Message, - QuickActionButton, } from '@the-deep/deep-ui'; import { SetValueArg, Error, } from '@togglecorp/toggle-form'; -import { - IoClose, -} from 'react-icons/io5'; -import { FiEdit2 } from 'react-icons/fi'; import EntryInput from '#components/entry/EntryInput'; import { GeoArea } from '#components/GeoMultiSelectInput'; @@ -34,12 +29,9 @@ interface Props { value: PartialEntryType; onChange: (val: SetValueArg, name: NAME) => void; error: Error | undefined; - onEntryCreateButtonClick: () => void; variant?: 'normal' | 'compact' | 'nlp'; // NOTE: Normal entry creation refers to entry created without use of // recommendations - onNormalEntryCreateButtonClick: () => void; - onEntryDiscardButtonClick: () => void; geoAreaOptions: GeoArea[] | undefined | null; onGeoAreaOptionsChange: React.Dispatch>; predictionsLoading?: boolean; @@ -50,6 +42,8 @@ interface Props { messageText: string | undefined; excerptShown?: boolean; displayHorizontally?: boolean; + + footerActions: React.ReactNode; } function AssistPopup(props: Props) { @@ -63,9 +57,6 @@ function AssistPopup(props: Props(props: Props { @@ -99,28 +91,7 @@ function AssistPopup(props: Props - - - - - - - - )} + footerQuickActions={footerActions} contentClassName={styles.body} > {isMessageShown ? ( diff --git a/app/views/EntryEdit/LeftPane/AssistItem/index.tsx b/app/views/EntryEdit/LeftPane/AssistItem/index.tsx index 865542478a..af2cbfa8b4 100644 --- a/app/views/EntryEdit/LeftPane/AssistItem/index.tsx +++ b/app/views/EntryEdit/LeftPane/AssistItem/index.tsx @@ -18,6 +18,7 @@ import { Container, } from '@the-deep/deep-ui'; import { IoClose } from 'react-icons/io5'; +import { FiEdit2 } from 'react-icons/fi'; import { GeoArea } from '#components/GeoMultiSelectInput'; import brainIcon from '#resources/img/brain.svg'; @@ -152,7 +153,11 @@ interface Props { className?: string; text: string; onAssistedEntryAdd: ( - (newEntry: EntryInput, locations?: GeoArea[]) => void + ( + newEntry: EntryInput, + locations?: GeoArea[], + selectCreatedEntry?: boolean, + ) => void ) | undefined; frameworkDetails?: Framework; leadId: string; @@ -641,6 +646,7 @@ function AssistItem(props: Props) { draftEntry: data?.project?.assistedTagging?.draftEntry?.id, }, geoAreaOptions ?? undefined, + true, ); } }, @@ -746,9 +752,6 @@ function AssistItem(props: Props) { leadId={leadId} hints={allHints} recommendations={allRecommendations} - onEntryDiscardButtonClick={handleDiscardButtonClick} - onEntryCreateButtonClick={handleEntryCreateButtonClick} - onNormalEntryCreateButtonClick={handleNormalEntryCreateButtonClick} geoAreaOptions={geoAreaOptions} onGeoAreaOptionsChange={setGeoAreaOptions} predictionsLoading={ @@ -758,6 +761,34 @@ function AssistItem(props: Props) { } predictionsErrored={!!fetchErrors || !!createErrors || isErrored} messageText={messageText} + footerActions={( + <> + + + + + + + + )} /> )} diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index 1e3db429da..3dacbbc298 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -23,6 +23,10 @@ import { import { Modal, ListView, + Tab, + Tabs, + TabPanel, + TabList, useAlert, Button, } from '@the-deep/deep-ui'; @@ -54,12 +58,11 @@ import { CreateAutoDraftEntriesMutationVariables, AutoDraftEntriesStatusQuery, AutoDraftEntriesStatusQueryVariables, + UpdateDraftEntryMutation, + UpdateDraftEntryMutationVariables, } from '#generated/types'; import AssistPopup from '../AssistItem/AssistPopup'; import { createDefaultAttributes } from '../../utils'; - -import styles from './styles.css'; - import { createOrganigramAttr, createMatrix1dAttr, @@ -70,20 +73,30 @@ import { createGeoAttr, } from '../AssistItem/utils'; +import styles from './styles.css'; + const GEOLOCATION_DEEPL_MODEL_ID = 'geolocation'; const AUTO_ENTRIES_FOR_LEAD = gql` query AutoEntriesForLead( $projectId: ID!, - $leadIds: [ID!], + $leadId: ID!, + $isDiscarded: Boolean, ) { project(id: $projectId) { id + lead(id: $leadId) { + draftEntryStat { + discardedDraftEnrty + undiscardedDraftEntry + } + } assistedTagging { draftEntryByLeads( filter: { - draftEntryType: AUTO, - lead: $leadIds, + draftEntryTypes: AUTO, + leads: $leadId, + isDiscarded: $isDiscarded, }) { id excerpt @@ -117,7 +130,7 @@ const CREATE_AUTO_DRAFT_ENTRIES = gql` project(id: $projectId) { id assistedTagging { - autoDraftEntryCreate(data: {lead: $leadId}) { + triggerAutoDraftEntry(data: {lead: $leadId}) { ok errors } @@ -142,6 +155,27 @@ const AUTO_DRAFT_ENTRIES_STATUS = gql` } `; +const UPDATE_DRAFT_ENTRY = gql` + mutation UpdateDraftEntry( + $projectId: ID!, + $draftEntryId: ID!, + $input: UpdateDraftEntryInputType!, + ){ + project(id: $projectId) { + id + assistedTagging { + updateDraftEntry( + data: $input, + id: $draftEntryId, + ) { + errors + ok + } + } + } + } +`; + interface EntryAttributes { predictions: { tags: string[]; @@ -328,6 +362,8 @@ function handleMappingsFetch(entryAttributes: EntryAttributes) { const entryKeySelector = (entry: PartialEntryType) => entry.clientId; +type EntriesTabType = 'extracted' | 'discarded'; + interface Props { onModalClose: () => void; projectId: string; @@ -358,6 +394,11 @@ function AutoEntriesModal(props: Props) { ) ), [createdEntries]); + const [ + selectedTab, + setSelectedTab, + ] = useState('extracted'); + const { allWidgets, filteredWidgets, @@ -479,7 +520,7 @@ function AutoEntriesModal(props: Props) { { onCompleted: (response) => { const autoEntriesResponse = response?.project?.assistedTagging - ?.autoDraftEntryCreate; + ?.triggerAutoDraftEntry; if (autoEntriesResponse?.ok) { retriggerEntryExtractionStatus(); } else { @@ -528,14 +569,18 @@ function AutoEntriesModal(props: Props) { const autoEntriesVariables = useMemo(() => ({ projectId, - leadIds: [leadId], + leadId, + isDiscarded: selectedTab === 'discarded', }), [ projectId, leadId, + selectedTab, ]); const { + data: autoEntries, loading: autoEntriesLoading, + refetch: retriggerAutoEntriesFetch, } = useQuery( AUTO_ENTRIES_FOR_LEAD, { @@ -543,6 +588,8 @@ function AutoEntriesModal(props: Props) { || extractionStatus !== 'SUCCESS' || isNotDefined(autoEntriesVariables), variables: autoEntriesVariables, + // TODO: This is due to caching issue in apollo. + notifyOnNetworkStatusChange: true, onCompleted: (response) => { const entries = response.project?.assistedTagging?.draftEntryByLeads; const transformedEntries = (entries ?? [])?.map((entry) => { @@ -629,6 +676,11 @@ function AutoEntriesModal(props: Props) { }, ); + const discardedEntriesCount = autoEntries?.project?.lead + ?.draftEntryStat?.discardedDraftEnrty ?? 0; + const undiscardedEntriesCount = autoEntries?.project?.lead + ?.draftEntryStat?.undiscardedDraftEntry ?? 0; + const handleEntryCreateButtonClick = useCallback((entryId: string) => { if (!allRecommendations?.[entryId]) { return; @@ -636,6 +688,20 @@ function AutoEntriesModal(props: Props) { const selectedEntry = value?.entries?.find((item) => item.clientId === entryId); if (onAssistedEntryAdd && selectedEntry) { + const duplicateEntryCheck = createdEntries?.find( + (entry) => entry.droppedExcerpt === selectedEntry.droppedExcerpt, + ); + + if (isDefined(duplicateEntryCheck)) { + alert.show( + 'Similar entry found. Failed to add entry from recommendations.', + { + variant: 'error', + }, + ); + return; + } + const defaultAttributes = createDefaultAttributes(allWidgets); const newAttributes = mergeLists( @@ -680,6 +746,79 @@ function AutoEntriesModal(props: Props) { geoAreaOptions, allRecommendations, onAssistedEntryAdd, + createdEntries, + ]); + + const [ + triggerUpdateDraftEntry, + ] = useMutation( + UPDATE_DRAFT_ENTRY, + { + onCompleted: (response) => { + const updateDraftEntryResponse = response?.project?.assistedTagging + ?.updateDraftEntry; + retriggerAutoEntriesFetch(); + if (updateDraftEntryResponse?.ok) { + alert.show( + 'Successfully changed the discard status.', + { + variant: 'success', + }, + ); + } else { + alert.show( + 'Failed to change the discard status.', + { + variant: 'error', + }, + ); + } + }, + onError: () => { + alert.show( + 'Failed to change the discard status.', + { + variant: 'error', + }, + ); + }, + }, + ); + + const handleUpdateDraftEntryClick = useCallback((entryId: string | undefined) => { + triggerUpdateDraftEntry({ + variables: { + projectId, + input: { + lead: leadId, + isDiscarded: true, + }, + // FIXME: Handle this better + draftEntryId: entryId ?? '', + }, + }); + }, [ + triggerUpdateDraftEntry, + leadId, + projectId, + ]); + + const handleUndiscardEntryClick = useCallback((entryId: string | undefined) => { + triggerUpdateDraftEntry({ + variables: { + projectId, + input: { + lead: leadId, + isDiscarded: false, + }, + // FIXME: Handle this better + draftEntryId: entryId ?? '', + }, + }); + }, [ + triggerUpdateDraftEntry, + leadId, + projectId, ]); const filteredEntries = useMemo(() => ( @@ -697,6 +836,36 @@ function AutoEntriesModal(props: Props) { ) => { const onEntryCreateButtonClick = () => handleEntryCreateButtonClick(entryId); const index = value?.entries?.findIndex((item) => item.clientId === entryId); + const footerActions = (selectedTab === 'extracted' ? ( +
+ + +
+ ) : ( + + )); return ({ frameworkDetails, @@ -709,17 +878,15 @@ function AutoEntriesModal(props: Props) { hints: allHints?.[entryId], recommendations: allRecommendations?.[entryId], geoAreaOptions: geoAreaOptions?.[entryId], - onEntryDiscardButtonClick: noOp, - onEntryCreateButtonClick, - onNormalEntryCreateButtonClick: noOp, onGeoAreaOptionsChange: noOp, predictionsLoading: false, predictionsErrored: false, messageText: undefined, - variant: 'nlp' as const, + variant: 'normal' as const, error: undefined, excerptShown: true, displayHorizontally: true, + footerActions, }); }, [ value?.entries, @@ -730,6 +897,9 @@ function AutoEntriesModal(props: Props) { frameworkDetails, leadId, geoAreaOptions, + handleUpdateDraftEntryClick, + handleUndiscardEntryClick, + selectedTab, ]); const isFiltered = useMemo(() => ( @@ -739,45 +909,101 @@ function AutoEntriesModal(props: Props) { value?.entries, ]); + /* const hideList = draftEntriesLoading || autoEntriesLoading || extractionStatus === 'NONE'; + */ return ( - + + + {`All Recommendations (${undiscardedEntriesCount})`} + + - Recommend entries - - )} - messageShown - messageIconShown - borderBetweenItem - /> + {`Discarded Recommendations (${discardedEntriesCount})`} + + + + + Recommend entries + + )} + messageShown + messageIconShown + borderBetweenItem + /> + + + + Recommend entries + + )} + messageShown + messageIconShown + borderBetweenItem + /> + + ); } diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css index 065f48366c..1abbc56c75 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/styles.css @@ -4,4 +4,9 @@ height: 360px; } } + + .footer-buttons { + display: flex; + gap: var(--dui-spacing-medium); + } } diff --git a/app/views/EntryEdit/index.tsx b/app/views/EntryEdit/index.tsx index 8f4f081c3d..b45e888c7e 100644 --- a/app/views/EntryEdit/index.tsx +++ b/app/views/EntryEdit/index.tsx @@ -907,8 +907,14 @@ function EntryEdit(props: Props) { ); const handleAssistedEntryAdd = useCallback( - (newValue: PartialEntryType, newGeoAreaOptions?: GeoArea[]) => { - createRestorePoint(); + ( + newValue: PartialEntryType, + newGeoAreaOptions?: GeoArea[], + selectCreatedEntry = false, + ) => { + if (selectCreatedEntry) { + createRestorePoint(); + } setFormFieldValue( (prevValue: PartialFormType['entries']) => [ ...(prevValue ?? []), @@ -919,7 +925,9 @@ function EntryEdit(props: Props) { ], 'entries', ); - setSelectedEntry(newValue.clientId); + if (selectCreatedEntry) { + setSelectedEntry(newValue.clientId); + } if (newGeoAreaOptions && newGeoAreaOptions.length > 0) { setGeoAreaOptions((oldAreas) => { const newAreas = unique([ From 09cff4f001cebcaa6f8c12f9ccc9d49eec167edb Mon Sep 17 00:00:00 2001 From: Subina Date: Wed, 20 Dec 2023 09:43:08 +0545 Subject: [PATCH 11/11] Minor fixes - Remove entries count - Change empty message in auto entries modal --- .../LeftPane/AutoEntriesModal/index.tsx | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx index 3dacbbc298..4cb73df613 100644 --- a/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx +++ b/app/views/EntryEdit/LeftPane/AutoEntriesModal/index.tsx @@ -85,12 +85,6 @@ const AUTO_ENTRIES_FOR_LEAD = gql` ) { project(id: $projectId) { id - lead(id: $leadId) { - draftEntryStat { - discardedDraftEnrty - undiscardedDraftEntry - } - } assistedTagging { draftEntryByLeads( filter: { @@ -676,11 +670,6 @@ function AutoEntriesModal(props: Props) { }, ); - const discardedEntriesCount = autoEntries?.project?.lead - ?.draftEntryStat?.discardedDraftEnrty ?? 0; - const undiscardedEntriesCount = autoEntries?.project?.lead - ?.draftEntryStat?.undiscardedDraftEntry ?? 0; - const handleEntryCreateButtonClick = useCallback((entryId: string) => { if (!allRecommendations?.[entryId]) { return; @@ -931,12 +920,12 @@ function AutoEntriesModal(props: Props) { - {`All Recommendations (${undiscardedEntriesCount})`} + All Recommendations - {`Discarded Recommendations (${discardedEntriesCount})`} + Discarded Recommendations