diff --git a/src/components/projects/projectId/attributes/attributeId/ExecutionContainer.tsx b/src/components/projects/projectId/attributes/attributeId/ExecutionContainer.tsx index 70c461c6..d3e90b50 100644 --- a/src/components/projects/projectId/attributes/attributeId/ExecutionContainer.tsx +++ b/src/components/projects/projectId/attributes/attributeId/ExecutionContainer.tsx @@ -45,7 +45,7 @@ export default function ExecutionContainer(props: ExecutionContainerProps) { setRequestedSomething(false); props.setEnabledButton(false); setRunOn10HasError(sampleRecordsFinal.calculatedAttributes.length > 0 ? false : true); - if (currentAttributesRef.current.dataType == DataTypeEnum.EMBEDDING_LIST) { + if (currentAttributesRef.current.dataType == DataTypeEnum.EMBEDDING_LIST || currentAttributesRef.current.dataType == DataTypeEnum.TEXT_LIST) { sampleRecordsFinal.calculatedAttributesList = sampleRecordsFinal.calculatedAttributes.map((record: string) => JSON.parse(record)); sampleRecordsFinal.calculatedAttributesListDisplay = extendArrayElementsByUniqueId(sampleRecordsFinal.calculatedAttributesList); } @@ -75,7 +75,13 @@ export default function ExecutionContainer(props: ExecutionContainerProps) { const sampleRecordsFinal = useMemo(() => { if (sampleRecords && sampleRecords.calculatedAttributesDisplay) { return sampleRecords.calculatedAttributesDisplay.map((record: any, index) => { - const calculatedValue = currentAttributesRef.current.dataType != DataTypeEnum.EMBEDDING_LIST ? record : { id: record.id, value: JSON.parse(record.value) } + let calculatedValue; + if (currentAttributesRef.current.dataType == DataTypeEnum.EMBEDDING_LIST || currentAttributesRef.current.dataType == DataTypeEnum.TEXT_LIST) { + calculatedValue = { id: record.id, value: JSON.parse(record.value) }; + } + else { + calculatedValue = record + } return { ...record, onClick: viewRecordDetails(index), @@ -85,7 +91,6 @@ export default function ExecutionContainer(props: ExecutionContainerProps) { ); } }, [sampleRecords]); - return (
@@ -136,7 +141,7 @@ export default function ExecutionContainer(props: ExecutionContainerProps) {
- {String(record.value)} + {Array.isArray(record?.calculatedValue?.value) ? JSON.stringify(record?.calculatedValue?.value) : String(record?.calculatedValue?.value)}
{recordKeys.map((rk) => - {recordData.map((record) => rk.dataType == DataTypeEnum.EMBEDDING_LIST ?
+ {recordData.map((record) => (rk.dataType == DataTypeEnum.EMBEDDING_LIST || rk.dataType == DataTypeEnum.TEXT_LIST) ?
{record[rk.name].map((li, idx) => {li})}
: {record[rk.name]})} )} diff --git a/src/components/projects/projectId/attributes/attributeId/ViewRecordDetailsModal.tsx b/src/components/projects/projectId/attributes/attributeId/ViewRecordDetailsModal.tsx index 007be4cd..bfb1326f 100644 --- a/src/components/projects/projectId/attributes/attributeId/ViewRecordDetailsModal.tsx +++ b/src/components/projects/projectId/attributes/attributeId/ViewRecordDetailsModal.tsx @@ -4,14 +4,21 @@ import { ModalEnum } from "@/src/types/shared/modal"; import { useSelector } from "react-redux"; import style from '@/src/styles/components/projects/projectId/attribute-calculation.module.css'; import { RecordDisplay } from "@/src/components/shared/record-display/RecordDisplay"; -import { DataTypeEnum } from "@/src/types/shared/general"; import { ViewRecordDetailsModalProps } from "@/src/types/components/projects/projectId/settings/attribute-calculation"; import { selectVisibleAttributesHeuristics } from "@/src/reduxStore/states/pages/settings"; +import { useMemo } from "react"; export default function ViewRecordDetailsModal(props: ViewRecordDetailsModalProps) { const modalViewRecordDetails = useSelector(selectModal(ModalEnum.VIEW_RECORD_DETAILS)); const attributes = useSelector(selectVisibleAttributesHeuristics); + const displayValue = useMemo(() => { + if (!props.sampleRecords || !modalViewRecordDetails.open) return null; + return Array.isArray(props.sampleRecords[modalViewRecordDetails.recordIdx].calculatedValue.value) + ? JSON.stringify(props.sampleRecords[modalViewRecordDetails.recordIdx].calculatedValue.value) + : String(props.sampleRecords[modalViewRecordDetails.recordIdx].calculatedValue.value); + }, [props.sampleRecords, modalViewRecordDetails.recordIdx, modalViewRecordDetails.open]); + return (<> {modalViewRecordDetails.open && modalViewRecordDetails.record && props.sampleRecords && <> @@ -23,13 +30,9 @@ export default function ViewRecordDetailsModal(props: ViewRecordDetailsModalProp record={modalViewRecordDetails.record} />
Calculated value
- {props.currentAttribute.dataType != DataTypeEnum.EMBEDDING_LIST ? - {props.sampleRecords[modalViewRecordDetails.recordIdx].calculatedValue.value} - :
- {props.sampleRecords[modalViewRecordDetails.recordIdx].calculatedValue.value.map((item: any) => - {item} - )} -
} +
+ {displayValue} +
diff --git a/src/components/projects/projectId/data-browser/DataBrowser.tsx b/src/components/projects/projectId/data-browser/DataBrowser.tsx index 48ed77ae..d75fcce4 100644 --- a/src/components/projects/projectId/data-browser/DataBrowser.tsx +++ b/src/components/projects/projectId/data-browser/DataBrowser.tsx @@ -40,17 +40,24 @@ export default function DataBrowser() { const activeSlice = useSelector(selectActiveSlice); const [searchRequest, setSearchRequest] = useState(SEARCH_REQUEST); + const [clearRequest, setClearRequest] = useState(false); useEffect(() => { - if (!projectId) return; + if (!projectId || !attributes) return; if (!users || !user) return; refetchDataSlicesAndProcess(); refetchAttributesAndProcess(); refetchLabelingTasksAndProcess(); refetchEmbeddingsAndPostProcess(); - refetchUniqueValuesAndProcess(); }, [projectId, users, user]); + useEffect(() => { + if (!projectId || !attributes) return; + getUniqueValuesByAttributes(projectId, (res) => { + dispatch(setUniqueValuesDict(postProcessUniqueValues(res, attributes))); + }); + }, [projectId, attributes]); + useEffect(() => { if (!projectId || !labelingTasks || !recordList) return; refetchRecordCommentsAndProcess(recordList); @@ -140,14 +147,9 @@ export default function DataBrowser() { setSearchRequest({ offset: searchRequest.offset + searchRequest.limit, limit: searchRequest.limit }); } - function refetchUniqueValuesAndProcess() { - getUniqueValuesByAttributes(projectId, (res) => { - dispatch(setUniqueValuesDict(postProcessUniqueValues(res, attributes))); - }); - } - const setSearchRequestToInit = useCallback(() => { setSearchRequest(SEARCH_REQUEST); + setClearRequest(true); }, []); const handleWebsocketNotification = useCallback((msgParts: string[]) => { @@ -171,7 +173,7 @@ export default function DataBrowser() { return (<> {projectId &&
- +
} ) diff --git a/src/components/projects/projectId/data-browser/DataBrowserRecords.tsx b/src/components/projects/projectId/data-browser/DataBrowserRecords.tsx index 674de9db..afc6393e 100644 --- a/src/components/projects/projectId/data-browser/DataBrowserRecords.tsx +++ b/src/components/projects/projectId/data-browser/DataBrowserRecords.tsx @@ -1,5 +1,5 @@ import { openModal } from '@/src/reduxStore/states/modal'; -import { selectActiveSearchParams, selectActiveSlice, selectAdditionalData, selectRecords, selectSimilaritySearch, setActiveDataSlice, setActiveSearchParams, setIsTextHighlightNeeded, setRecordsInDisplay, setTextHighlight, updateAdditionalDataState } from '@/src/reduxStore/states/pages/data-browser'; +import { selectActiveSearchParams, selectActiveSlice, selectAdditionalData, selectRecords, selectSimilaritySearch, setActiveDataSlice, setActiveSearchParams, setFullSearchStore, setIsTextHighlightNeeded, setRecordsInDisplay, setTextHighlight, updateAdditionalDataState } from '@/src/reduxStore/states/pages/data-browser'; import style from '@/src/styles/components/projects/projectId/data-browser.module.css'; import { ModalEnum } from '@/src/types/shared/modal'; import { TOOLTIPS_DICT } from '@/src/util/tooltip-constants'; diff --git a/src/components/projects/projectId/data-browser/DataBrowserSidebar.tsx b/src/components/projects/projectId/data-browser/DataBrowserSidebar.tsx index 183c8bac..6ffeea16 100644 --- a/src/components/projects/projectId/data-browser/DataBrowserSidebar.tsx +++ b/src/components/projects/projectId/data-browser/DataBrowserSidebar.tsx @@ -1,9 +1,9 @@ import { selectAllUsers } from '@/src/reduxStore/states/general'; import { setModalStates } from '@/src/reduxStore/states/modal'; -import { selectActiveSlice, selectAdditionalData, selectDataSlicesAll, setActiveDataSlice, setActiveSearchParams, setFullSearchStore, setIsTextHighlightNeeded, setRecordsInDisplay, setTextHighlight, updateAdditionalDataState } from '@/src/reduxStore/states/pages/data-browser'; +import { selectActiveSlice, selectAdditionalData, selectDataSlicesAll, setActiveDataSlice, setActiveSearchParams, setFullSearchStore, setIsTextHighlightNeeded, setRecordsInDisplay, setTextHighlight, setUniqueValuesDict, updateAdditionalDataState } from '@/src/reduxStore/states/pages/data-browser'; import { selectProjectId } from '@/src/reduxStore/states/project'; import style from '@/src/styles/components/projects/projectId/data-browser.module.css'; -import { DataSlice } from '@/src/types/components/projects/projectId/data-browser/data-browser'; +import { DataBrowserSidebarProps, DataSlice } from '@/src/types/components/projects/projectId/data-browser/data-browser'; import { ModalEnum } from '@/src/types/shared/modal'; import { updateSliceInfoHelper } from '@/src/util/components/projects/projectId/data-browser/data-browser-helper'; import { Slice } from '@/submodules/javascript-functions/enums/enums'; @@ -17,7 +17,7 @@ import DataSliceInfoModal from './modals/DataSliceInfoModal'; import MultilineTooltip from '@/src/components/shared/multilines-tooltip/MultilineTooltip'; import { MemoIconAlertTriangle, MemoIconInfoCircle, MemoIconLayoutSidebar, MemoIconTrash } from '@/submodules/react-components/components/kern-icons/icons'; -export default function DataBrowserSidebar() { +export default function DataBrowserSidebar(props: DataBrowserSidebarProps) { const dispatch = useDispatch(); const projectId = useSelector(selectProjectId); @@ -130,7 +130,7 @@ export default function DataBrowserSidebar() {
You can filter and order all your data in the browser according to your needs. Selected filter criteria can be saved and used later on. - +
}
diff --git a/src/components/projects/projectId/data-browser/RecordList.tsx b/src/components/projects/projectId/data-browser/RecordList.tsx index ffe0204e..e8573ff2 100644 --- a/src/components/projects/projectId/data-browser/RecordList.tsx +++ b/src/components/projects/projectId/data-browser/RecordList.tsx @@ -15,6 +15,9 @@ import SimilaritySearchModal from "./modals/SimilaritySeachModal"; import RecordCommentsModal from "./modals/RecordCommentsModal"; import ButtonAsText from "@/submodules/react-components/components/kern-button/ButtonAsText"; import { MemoIconAngle, MemoIconArrowRight, MemoIconEdit, MemoIconNotes } from "@/submodules/react-components/components/kern-icons/icons"; +import { useEffect, useMemo } from "react"; +import { selectAllProjects, selectProject, setAllProjects } from "@/src/reduxStore/states/project"; +import { getAllProjects } from "@/src/services/base/project"; export default function RecordList(props: RecordListProps) { const dispatch = useDispatch(); @@ -25,6 +28,16 @@ export default function RecordList(props: RecordListProps) { const embeddings = useSelector(selectEmbeddings); const recordComments = useSelector(selectRecordComments); const attributes = useSelector(selectVisibleAttributesDataBrowser); + const project = useSelector(selectProject); + const allProjects = useSelector(selectAllProjects); + + useEffect(() => { + getAllProjects((projects) => dispatch(setAllProjects(projects))); + }, []); + + const isIntegrationProject = useMemo(() => { + return allProjects && allProjects.find(p => p.id === project.id)?.isIntegrationProject; + }, [allProjects, project.id]); return (<> {recordList && recordList.map((record, index) => (
@@ -71,7 +84,7 @@ export default function RecordList(props: RecordListProps) {
- {user?.role == UserRole.ENGINEER &&
+ {(user?.role == UserRole.ENGINEER && !isIntegrationProject) &&
props.editRecord(index)} />
} diff --git a/src/components/projects/projectId/data-browser/SearchGroups.tsx b/src/components/projects/projectId/data-browser/SearchGroups.tsx index 9c5a1b55..a30a0305 100644 --- a/src/components/projects/projectId/data-browser/SearchGroups.tsx +++ b/src/components/projects/projectId/data-browser/SearchGroups.tsx @@ -29,11 +29,12 @@ import { getRecordsByStaticSlice, searchRecordsExtended } from "@/src/services/b import { staticDataSlicesCurrentCount } from "@/src/services/base/dataSlices"; import KernButton from "@/submodules/react-components/components/kern-button/KernButton"; import { MemoIconArrowDown, MemoIconArrowsRandom, MemoIconFilterOff, MemoIconPlus, MemoIconPointerOff, MemoIconTrash } from "@/submodules/react-components/components/kern-icons/icons"; +import { SearchGroupsProps } from "@/src/types/components/projects/projectId/data-browser/data-browser"; const GROUP_SORT_ORDER = 0; let GLOBAL_SEARCH_GROUP_COUNT = 0; -export default function SearchGroups() { +export default function SearchGroups(props: SearchGroupsProps) { const dispatch = useDispatch(); const projectId = useSelector(selectProjectId); @@ -96,7 +97,7 @@ export default function SearchGroups() { }); setBackgroundColors(colors); setAttributeSortOrder(attributesSort); - }, [attributes]); + }, [attributes, props.clearRequest]); useEffect(() => { if (!attributesSortOrder || !searchGroupsStore) return; @@ -336,7 +337,14 @@ export default function SearchGroups() { const attributeName = formControlsIdx['name']; attributeType = getAttributeType(attributesSortOrder, attributeName); } - if (attributeType !== DataTypeEnum.BOOLEAN) { + if (attributeType == DataTypeEnum.TEXT_LIST) { + operatorsCopy.push({ + value: SearchOperator.CONTAINS.split("_").join(" "), + }); + tooltipsCopy.push(getSearchOperatorTooltip(SearchOperator.CONTAINS)); + formControlsIdx['operator'] = SearchOperator.CONTAINS; + + } else if (attributeType !== DataTypeEnum.BOOLEAN) { for (let t of Object.values(SearchOperator)) { operatorsCopy.push({ value: t.split("_").join(" "), @@ -612,17 +620,17 @@ export default function SearchGroups() {
- selectValueDropdown(option.name, index, 'name', group.key)} fontClass="font-dmMono" buttonClasses="text-xs" /> + {attributesSortOrder && selectValueDropdown(option.name, index, 'name', group.key)} fontClass="font-dmMono" buttonClasses="text-xs" />}
- {groupItem.operator != '' && + {(groupItem.operator != '' && operatorsDropdown) && selectValueDropdown(option.value, index, 'operator', group.key)} fontClass="font-dmMono" /> }
- {uniqueValuesDict[groupItem['name']] && groupItem['operator'] != '' && groupItem['operator'] != 'BETWEEN' && groupItem['operator'] != 'IN' && groupItem['operator'] != 'IN WC' ? ( + {(uniqueValuesDict && uniqueValuesDict[groupItem['name']] && groupItem['operator'] != '' && groupItem['operator'] != 'BETWEEN' && groupItem['operator'] != 'IN' && groupItem['operator'] != 'IN WC' || saveAttributeType == DataTypeEnum.TEXT_LIST) ? (
selectValueDropdown(option, index, 'searchValue', group.key)} fontClass="font-dmMono" /> @@ -641,7 +649,7 @@ export default function SearchGroups() {
)} - {(groupItem['operator'] == "BEGINS WITH" || groupItem['operator'] == "ENDS WITH" || groupItem['operator'] == SearchOperator.CONTAINS || groupItem['operator'] == "IN WC") && (saveAttributeType != DataTypeEnum.INTEGER && saveAttributeType != DataTypeEnum.FLOAT) && + {(groupItem['operator'] == "BEGINS WITH" || groupItem['operator'] == "ENDS WITH" || groupItem['operator'] == SearchOperator.CONTAINS || groupItem['operator'] == "IN WC") && (saveAttributeType != DataTypeEnum.INTEGER && saveAttributeType != DataTypeEnum.FLOAT && saveAttributeType != DataTypeEnum.TEXT_LIST) && } diff --git a/src/components/projects/projectId/data-browser/modals/ConfigurationModal.tsx b/src/components/projects/projectId/data-browser/modals/ConfigurationModal.tsx index 56d6446f..5fbc124b 100644 --- a/src/components/projects/projectId/data-browser/modals/ConfigurationModal.tsx +++ b/src/components/projects/projectId/data-browser/modals/ConfigurationModal.tsx @@ -3,43 +3,55 @@ import { setModalStates } from "@/src/reduxStore/states/modal"; import { selectConfiguration, updateConfigurationState } from "@/src/reduxStore/states/pages/data-browser"; import { LineBreaksType } from "@/src/types/components/projects/projectId/data-browser/data-browser"; import { ModalEnum } from "@/src/types/shared/modal"; +import { useCallback, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; export default function ConfigurationModal() { const dispatch = useDispatch(); const configuration = useSelector(selectConfiguration); + useEffect(() => { + const lineBreaks = localStorage.getItem('lineBreaks'); + if (lineBreaks) { + dispatch(updateConfigurationState('lineBreaks', JSON.parse(lineBreaks) as LineBreaksType)); + } + }, []); + - function toggleConfigurationOption(field: string) { + const toggleConfigurationOption = useCallback((field: string) => { dispatch(updateConfigurationState(field, !configuration[field])); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); - } + }, [configuration]); - function toggleLineBreaks() { + const toggleLineBreaks = useCallback(() => { if (configuration.lineBreaks == LineBreaksType.IS_PRE_WRAP || configuration.lineBreaks == LineBreaksType.IS_PRE_LINE) { dispatch(updateConfigurationState('lineBreaks', LineBreaksType.NORMAL)); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); + localStorage.setItem('lineBreaks', JSON.stringify(LineBreaksType.NORMAL)); } else { dispatch(updateConfigurationState('lineBreaks', LineBreaksType.IS_PRE_WRAP)); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); - + localStorage.setItem('lineBreaks', JSON.stringify(LineBreaksType.IS_PRE_WRAP)); } - } + }, [configuration]); + - function toggleLineBreaksPreWrap() { + const toggleLineBreaksPreWrap = useCallback(() => { if (configuration.lineBreaks === LineBreaksType.IS_PRE_WRAP) { dispatch(updateConfigurationState('lineBreaks', LineBreaksType.IS_PRE_LINE)); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); + localStorage.setItem('lineBreaks', JSON.stringify(LineBreaksType.IS_PRE_LINE)); } else if (configuration.lineBreaks === LineBreaksType.IS_PRE_LINE) { dispatch(updateConfigurationState('lineBreaks', LineBreaksType.IS_PRE_WRAP)); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); - + localStorage.setItem('lineBreaks', JSON.stringify(LineBreaksType.IS_PRE_WRAP)); } - } + }, [configuration]); + - function toggleSeparator() { + const toggleSeparator = useCallback(() => { if (configuration.separator === ',') { dispatch(updateConfigurationState('separator', '-')); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); @@ -47,9 +59,8 @@ export default function ConfigurationModal() { } else { dispatch(updateConfigurationState('separator', ',')); dispatch(setModalStates(ModalEnum.CONFIGURATION, { open: true })); - } - } + }, [configuration]); return (
View configuration
diff --git a/src/components/projects/projectId/data-browser/modals/SimilaritySeachModal.tsx b/src/components/projects/projectId/data-browser/modals/SimilaritySeachModal.tsx index 16d1cae1..7a759776 100644 --- a/src/components/projects/projectId/data-browser/modals/SimilaritySeachModal.tsx +++ b/src/components/projects/projectId/data-browser/modals/SimilaritySeachModal.tsx @@ -95,8 +95,13 @@ export default function SimilaritySearchModal() { filterAttributesSS.forEach((attribute: string) => { const attributeType = attributes.find(att => att.name == attribute)?.dataType if (attributeType !== DataTypeEnum.INTEGER) { - operators = operators.filter(operator => operator !== FilterIntegrationOperator.BETWEEN); - tooltips = tooltips.filter(tooltip => tooltip !== getFilterIntegrationOperatorTooltip(FilterIntegrationOperator.BETWEEN)); + if (attributeType == DataTypeEnum.TEXT_LIST) { + operators = operators.filter(operator => operator !== FilterIntegrationOperator.BETWEEN && operator !== FilterIntegrationOperator.IN); + tooltips = tooltips.filter(tooltip => tooltip !== getFilterIntegrationOperatorTooltip(FilterIntegrationOperator.BETWEEN) && tooltip !== getFilterIntegrationOperatorTooltip(FilterIntegrationOperator.IN)); + } else { + operators = operators.filter(operator => operator !== FilterIntegrationOperator.BETWEEN); + tooltips = tooltips.filter(tooltip => tooltip !== getFilterIntegrationOperatorTooltip(FilterIntegrationOperator.BETWEEN)); + } } operatorsCopy[attribute] = operators; tooltipsCopy[attribute] = tooltips; diff --git a/src/components/projects/projectId/edit-records/EditField.tsx b/src/components/projects/projectId/edit-records/EditField.tsx index 94cfd4e8..f7ebe1cf 100644 --- a/src/components/projects/projectId/edit-records/EditField.tsx +++ b/src/components/projects/projectId/edit-records/EditField.tsx @@ -114,7 +114,7 @@ export default function EditField(props: EditFieldProps) { type="checkbox" className="w-6 " />} - {props.attribute.dataType == DataTypeEnum.EMBEDDING_LIST && + {(props.attribute.dataType == DataTypeEnum.EMBEDDING_LIST || props.attribute.dataType == DataTypeEnum.TEXT_LIST) &&