diff --git a/frontend/src/components/widgets/modals/CreateExtractModal.tsx b/frontend/src/components/widgets/modals/CreateExtractModal.tsx index fc14d8fd..92b92ab0 100644 --- a/frontend/src/components/widgets/modals/CreateExtractModal.tsx +++ b/frontend/src/components/widgets/modals/CreateExtractModal.tsx @@ -1,44 +1,139 @@ -import React, { ReactNode, useEffect, useState } from "react"; -import { Button, Modal, Icon } from "semantic-ui-react"; -import _ from "lodash"; -import { CorpusType, FieldsetType } from "../../../graphql/types"; +import React, { useState, useEffect } from "react"; +import { + Modal, + Form, + FormInput, + FormGroup, + FormButton, + FormField, +} from "semantic-ui-react"; -export interface CreateExtractModalProps { +import { useReactiveVar, useQuery, useMutation } from "@apollo/client"; +import { selectedCorpus, selectedFieldset } from "../../../graphql/cache"; +import { + REQUEST_GET_EXTRACT, + RequestGetExtractInput, + GetExtractOutput, +} from "../../../graphql/queries"; +import { CorpusDropdown } from "../selectors/CorpusDropdown"; +import { FieldsetDropdown } from "../selectors/FieldsetDropdown"; +import { + REQUEST_CREATE_EXTRACT, + RequestCreateExtractInputType, + RequestCreateExtractOutputType, +} from "../../../graphql/mutations"; + +interface ExtractModalProps { open: boolean; onClose: () => void; - onSubmit: () => void; - corpus?: CorpusType | undefined; - fieldset?: FieldsetType | undefined; - children?: React.ReactChild | React.ReactChild[]; + extractId?: string; + fieldsetId?: string; + corpusId?: string; } -export function CreateExtractModal({ +export const CreateExtractModal: React.FC = ({ open, - corpus, - fieldset, - children, onClose, - onSubmit, -}: CreateExtractModalProps) { + extractId, + fieldsetId, + corpusId, +}) => { const [name, setName] = useState(""); - const [description, setDescription] = useState(""); + const selected_corpus = useReactiveVar(selectedCorpus); + const selected_fieldset = useReactiveVar(selectedFieldset); + + const { loading, error, data } = useQuery< + GetExtractOutput, + RequestGetExtractInput + >(REQUEST_GET_EXTRACT, { + variables: { id: extractId || "" }, + skip: !extractId, + }); + + const [ + createExtract, + { loading: createExtractLoading, error: createExtractError }, + ] = useMutation< + RequestCreateExtractOutputType, + RequestCreateExtractInputType + >(REQUEST_CREATE_EXTRACT); + + useEffect(() => { + if (data?.extract) { + setName(data.extract.name); + } + }, [data]); + + const handleSubmit = async () => { + if (!extractId) { + try { + const { data } = await createExtract({ + variables: { + corpusId: selected_corpus?.id || corpusId || "", + name, + fieldsetId: selected_fieldset?.id || fieldsetId || "", + }, + }); + if (data?.createExtract.ok) { + onClose(); + } else { + console.error("Failed to create extract:", data?.createExtract.msg); + } + } catch (error) { + console.error("Error creating extract:", error); + } + } + }; + + if (loading) { + return
Loading...
; + } + + if (error) { + return
Error: {error.message}
; + } return ( - onClose()}> - - - - - + + + {extractId ? "Edit Extract" : "Create Extract"} + + +
+ + setName(value)} + style={{ minWidth: "50vw important!" }} + /> + + {!corpusId && ( + + + + + + + )} + + {!fieldsetId && ( + + + + + + + )} + + +
); -} +}; diff --git a/frontend/src/components/widgets/selectors/CorpusDropdown.tsx b/frontend/src/components/widgets/selectors/CorpusDropdown.tsx index 52c5a72f..e6386281 100644 --- a/frontend/src/components/widgets/selectors/CorpusDropdown.tsx +++ b/frontend/src/components/widgets/selectors/CorpusDropdown.tsx @@ -1,4 +1,4 @@ -import React, { SyntheticEvent, useEffect, useState } from "react"; +import React, { SyntheticEvent, useCallback, useEffect, useState } from "react"; import { useQuery, useReactiveVar } from "@apollo/client"; import { Dropdown, DropdownProps } from "semantic-ui-react"; import { @@ -24,9 +24,11 @@ export const CorpusDropdown: React.FC = () => { GetCorpusesOutputs, GetCorpusesInputs >(GET_CORPUSES, { - variables: { - textSearch: searchQuery, - }, + variables: searchQuery + ? { + textSearch: searchQuery, + } + : {}, }); // If the searchQuery changes... refetch corpuses. @@ -38,11 +40,18 @@ export const CorpusDropdown: React.FC = () => { ? data.corpuses.edges.map((edge) => edge.node) : []; + const debouncedSetSearchQuery = useCallback( + _.debounce((query: string) => { + setSearchQuery(query); + }, 500), + [] + ); + const handleSearchChange = ( event: React.SyntheticEvent, { searchQuery }: { searchQuery: string } ) => { - setSearchQuery(searchQuery); + debouncedSetSearchQuery(searchQuery); }; const handleSelectionChange = ( diff --git a/frontend/src/components/widgets/selectors/FieldsetDropdown.tsx b/frontend/src/components/widgets/selectors/FieldsetDropdown.tsx index fd3af53c..6f891888 100644 --- a/frontend/src/components/widgets/selectors/FieldsetDropdown.tsx +++ b/frontend/src/components/widgets/selectors/FieldsetDropdown.tsx @@ -1,4 +1,4 @@ -import React, { SyntheticEvent, useEffect, useState } from "react"; +import React, { SyntheticEvent, useCallback, useEffect, useState } from "react"; import { useQuery, useReactiveVar } from "@apollo/client"; import { Dropdown, DropdownProps } from "semantic-ui-react"; import { @@ -20,7 +20,16 @@ export const FieldsetDropdown: React.FC = () => { const selected_fieldset = useReactiveVar(selectedFieldset); const { loading, error, data, refetch } = useQuery( - REQUEST_GET_FIELDSETS + REQUEST_GET_FIELDSETS, + { + variables: searchQuery + ? { + searchText: searchQuery, + } + : {}, + fetchPolicy: "network-only", + notifyOnNetworkStatusChange: true, + } ); // If the searchQuery changes... refetch fieldsets. @@ -32,11 +41,18 @@ export const FieldsetDropdown: React.FC = () => { ? data.fieldsets.edges.map((edge) => edge.node) : []; + const debouncedSetSearchQuery = useCallback( + _.debounce((query: string) => { + setSearchQuery(query); + }, 500), + [] + ); + const handleSearchChange = ( event: React.SyntheticEvent, { searchQuery }: { searchQuery: string } ) => { - setSearchQuery(searchQuery); + debouncedSetSearchQuery(searchQuery); }; const handleSelectionChange = ( @@ -83,6 +99,7 @@ export const FieldsetDropdown: React.FC = () => { onChange={handleSelectionChange} onSearchChange={handleSearchChange} loading={loading} + style={{ minWidth: "50vw important!" }} /> ); }; diff --git a/frontend/src/extracts/ExtractDataGrid.tsx b/frontend/src/extracts/ExtractDataGrid.tsx index 0061cbf0..7759c1c5 100644 --- a/frontend/src/extracts/ExtractDataGrid.tsx +++ b/frontend/src/extracts/ExtractDataGrid.tsx @@ -4,9 +4,9 @@ import { useMutation, useQuery } from "@apollo/client"; import { toast } from "react-toastify"; import { ColumnDetails } from "./ColumnDetails"; -import { ColumnType, ExtractType, DatacellType } from "../graphql/types"; +import { ExtractType } from "../graphql/types"; import { REQUEST_GET_EXTRACT } from "../graphql/queries"; -import { REQUEST_START_EXTRACT } from "../graphql/mutations"; +import { REQUEST_CREATE_EXTRACT } from "../graphql/mutations"; interface ExtractDataGridProps { extractId: string; @@ -21,7 +21,7 @@ export const ExtractDataGrid: React.FC = ({ variables: { id: extractId }, } ); - const [startExtract] = useMutation(REQUEST_START_EXTRACT); + const [startExtract] = useMutation(REQUEST_CREATE_EXTRACT); const handleStartExtract = async () => { try { @@ -49,9 +49,9 @@ export const ExtractDataGrid: React.FC = ({ - {columns.map((column) => ( - - {column.query} + {columns.edges.map((columnEdge) => ( + + {columnEdge.node.query} ))} @@ -60,9 +60,9 @@ export const ExtractDataGrid: React.FC = ({ {datacells?.edges ? ( datacells.edges.map((row) => ( - {columns.map((column: ColumnType) => ( - - {row && row.node ? row.node.data[column.id] : ""} + {columns.edges.map((columnEdge) => ( + + {row && row.node ? row.node.data[columnEdge.node.id] : ""} ))} @@ -75,10 +75,10 @@ export const ExtractDataGrid: React.FC = ({ {!extract.started && ( <>

Edit Columns

- {columns.map((column) => ( + {columns.edges.map((columnEdge) => ( diff --git a/frontend/src/extracts/FieldsetDetails.tsx b/frontend/src/extracts/FieldsetDetails.tsx index 2a4b3369..a037a9c5 100644 --- a/frontend/src/extracts/FieldsetDetails.tsx +++ b/frontend/src/extracts/FieldsetDetails.tsx @@ -53,10 +53,10 @@ export const FieldsetDetails: React.FC = ({

Columns

- {fieldset.columns.map((column: ColumnType) => ( + {fieldset.columns.edges.map((columnEdge) => ( diff --git a/frontend/src/graphql/mutations.ts b/frontend/src/graphql/mutations.ts index ff48f3e5..47bb329e 100644 --- a/frontend/src/graphql/mutations.ts +++ b/frontend/src/graphql/mutations.ts @@ -1293,23 +1293,23 @@ export const REQUEST_UPDATE_COLUMN = gql` } `; -export interface RequestStartExtractOutputType { - startExtract: { +export interface RequestCreateExtractOutputType { + createExtract: { msg: string; ok: boolean; obj: ExtractType; }; } -export interface RequestStartExtractInputType { +export interface RequestCreateExtractInputType { corpusId: string; name: string; fieldsetId: string; } -export const REQUEST_START_EXTRACT = gql` - mutation StartExtract($corpusId: ID!, $name: String!, $fieldsetId: ID!) { - startExtract(corpusId: $corpusId, name: $name, fieldsetId: $fieldsetId) { +export const REQUEST_CREATE_EXTRACT = gql` + mutation CreateExtract($corpusId: ID!, $name: String!, $fieldsetId: ID!) { + createExtract(corpusId: $corpusId, name: $name, fieldsetId: $fieldsetId) { msg ok obj { @@ -1319,3 +1319,23 @@ export const REQUEST_START_EXTRACT = gql` } } `; + +export interface RequestStartExtractOutputType { + startExtract: { + msg: string; + ok: boolean; + }; +} + +export interface RequestStartExtractInputType { + extractId: string; +} + +export const REQUEST_START_EXTRACT = gql` + mutation StartExtract($extractId: ID!) { + startExtract(extractId: $extractId) { + msg + ok + } + } +`; diff --git a/frontend/src/graphql/queries.ts b/frontend/src/graphql/queries.ts index e2ccc848..77414853 100644 --- a/frontend/src/graphql/queries.ts +++ b/frontend/src/graphql/queries.ts @@ -999,6 +999,10 @@ export const GET_LANGUAGEMODELS = gql` } `; +export interface GetFieldsetsInputs { + searchText?: string; +} + export interface GetFieldsetsOutputs { fieldsets: { pageInfo: PageInfo; @@ -1009,8 +1013,8 @@ export interface GetFieldsetsOutputs { } export const REQUEST_GET_FIELDSETS = gql` - query GetFieldsets { - fieldsets { + query GetFieldsets($searchText: String) { + fieldsets(name_Contains: $searchText) { edges { node { id @@ -1021,17 +1025,21 @@ export const REQUEST_GET_FIELDSETS = gql` name description columns { - id - query - matchText - outputType - limitToLabel - instructions - languageModel { - id - model + edges { + node { + id + query + matchText + outputType + limitToLabel + instructions + languageModel { + id + model + } + agentic + } } - agentic } } } @@ -1123,10 +1131,7 @@ export const REQUEST_GET_EXTRACT = gql` `; export interface GetExtractsInput { - name?: string; - created?: string; - started?: string; - finished?: string; + name_Contains?: string; } export interface GetExtractsOutput { @@ -1139,8 +1144,8 @@ export interface GetExtractsOutput { } export const REQUEST_GET_EXTRACTS = gql` - query GetExtracts { - extracts { + query GetExtracts($searchText: String) { + extracts(name_Contains: $searchText) { edges { node { id diff --git a/frontend/src/graphql/types.ts b/frontend/src/graphql/types.ts index 81eb9f2b..ebef7cda 100644 --- a/frontend/src/graphql/types.ts +++ b/frontend/src/graphql/types.ts @@ -1180,7 +1180,7 @@ export interface FieldsetType extends Node { owner: UserType; name: string; description: string; - columns: ColumnType[]; + columns: ColumnTypeEdge; } export interface ColumnType extends Node { @@ -1194,6 +1194,14 @@ export interface ColumnType extends Node { agentic: boolean; } +export interface ColumnTypeEdge { + __typename?: "ColumnTypeEdge"; + pageInfo?: PageInfo; + edges: { + node: ColumnType; + }[]; +} + export type DatacellTypeConnection = { __typename?: "DatacellTypeConnection"; pageInfo: PageInfo; diff --git a/frontend/src/tests/ColumnDetails.test.tsx b/frontend/src/tests/ColumnDetails.test.tsx index 85d10b77..00d3e40d 100644 --- a/frontend/src/tests/ColumnDetails.test.tsx +++ b/frontend/src/tests/ColumnDetails.test.tsx @@ -25,7 +25,9 @@ const mockColumn: ColumnType = { owner: generateMockUser(), name: "The set to be", description: "Everyone's favorite hang", - columns: [], + columns: { + edges: [], + }, }, }; diff --git a/frontend/src/tests/utils/factories.ts b/frontend/src/tests/utils/factories.ts index 08caff00..ef12bfd0 100644 --- a/frontend/src/tests/utils/factories.ts +++ b/frontend/src/tests/utils/factories.ts @@ -153,7 +153,9 @@ export function generateMockFieldset(owner: UserType): FieldsetType { owner, name: "mockFieldset", description: "Mock fieldset description", - columns: [], + columns: { + edges: [], + }, }; } diff --git a/frontend/src/views/Extracts.tsx b/frontend/src/views/Extracts.tsx index 1df70ee3..a52f51e4 100644 --- a/frontend/src/views/Extracts.tsx +++ b/frontend/src/views/Extracts.tsx @@ -30,6 +30,7 @@ import { ExtractType } from "../graphql/types"; import { ConfirmModal } from "../components/widgets/modals/ConfirmModal"; import { ExtractList } from "../extracts/list/ExtractList"; import { CreateAndSearchBar } from "../components/layout/CreateAndSearchBar"; +import { CreateExtractModal } from "../components/widgets/modals/CreateExtractModal"; export const Extracts = () => { const auth_token = useReactiveVar(authToken); @@ -57,13 +58,12 @@ export const Extracts = () => { notifyOnNetworkStatusChange: true, // required to get loading signal on fetchMore }); - // const extract_nodes = extracts_data?.extracts?.edges - // ? extracts_data.extracts.edges - // : []; - // const extract_items = extract_nodes - // .map((edge) => (edge?.node ? edge.node : undefined)) - // .filter((item): item is ExtractType => !!item); - const extract_items: ExtractType[] = []; + const extract_nodes = extracts_data?.extracts?.edges + ? extracts_data.extracts.edges + : []; + const extract_items = extract_nodes + .map((edge) => (edge?.node ? edge.node : undefined)) + .filter((item): item is ExtractType => !!item); // If we just logged in, refetch extracts in case there are extracts that are not public and are only visible to current user useEffect(() => { @@ -142,6 +142,10 @@ export const Extracts = () => { toggleModal={() => showDeleteExtractModal(false)} visible={show_delete_extract_modal} /> + showCreateExtractModal(false)} + /> } SearchBar={}