From b452d67bdddb5c9be2bdeb8c91150a19592aeb2e Mon Sep 17 00:00:00 2001 From: bdeining Date: Fri, 28 Feb 2020 16:27:01 -0700 Subject: [PATCH] Added Text Search form and query creation to workspaces --- schema.json | 41 ++ .../query-selector/query-selector.js | 1 + .../components/workspaces/workspaces.js | 401 ++++++++++++++---- .../intrigue-api/metacards/metacards.js | 6 +- 4 files changed, 354 insertions(+), 95 deletions(-) diff --git a/schema.json b/schema.json index d573cbf9..0630ffdf 100644 --- a/schema.json +++ b/schema.json @@ -4952,6 +4952,47 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "saveMetacardFromJson", + "description": "TBD: Should only be used when updating assocations with IDs\ncreateMetacardFromJson(attrs: Json!): MetacardAttributes", + "args": [ + { + "name": "id", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "attributes", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Json", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "MetacardAttributes", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "deleteMetacard", "description": "", diff --git a/src/main/webapp/components/query-selector/query-selector.js b/src/main/webapp/components/query-selector/query-selector.js index 33d2fe2f..e4abc4b6 100644 --- a/src/main/webapp/components/query-selector/query-selector.js +++ b/src/main/webapp/components/query-selector/query-selector.js @@ -11,6 +11,7 @@ import { IndexCardItem, } from '../index-cards' import AdvancedSearchQueryBuilder from '../query-builder/query-builder' + const { useDrawInterface } = require('../../react-hooks') const useOpenClose = props => { diff --git a/src/main/webapp/components/workspaces/workspaces.js b/src/main/webapp/components/workspaces/workspaces.js index f6c0563b..dd0aae03 100644 --- a/src/main/webapp/components/workspaces/workspaces.js +++ b/src/main/webapp/components/workspaces/workspaces.js @@ -5,9 +5,13 @@ import Tab from '@material-ui/core/Tab' import Tabs from '@material-ui/core/Tabs' import Typography from '@material-ui/core/Typography' import gql from 'graphql-tag' +import Popover from '@material-ui/core/Popover' +import Button from '@material-ui/core/Button' import { getIn } from 'immutable' import React, { useState } from 'react' import loadable from 'react-loadable' +import SaveAltIcon from '@material-ui/icons/SaveAlt' +import TextField from '@material-ui/core/TextField' import { Link, Redirect, useParams } from 'react-router-dom' import { useQueryExecutor } from '../../react-hooks' import { @@ -23,9 +27,14 @@ import { InlineRetry, SnackbarRetry } from '../network-retry' import QueryEditor from '../query-editor' import QuerySelector from '../query-selector' import QueryStatus from '../query-status' +import SearchIcon from '@material-ui/icons/Search' const LoadingComponent = () => +const { + transformFilterToCQL, +} = require('../../intrigue-api/metacards/CQLUtils') + let Visualizations = () => null if (typeof window !== 'undefined') { Visualizations = loadable({ @@ -59,6 +68,181 @@ const workspaceById = gql` } } ` +const queryAttributes = gql` + fragment QueryAttributes on MetacardAttributes { + title + cql + metacard_tags + metacard_type + } +` + +const useCreateQuery = workspaceId => { + const [queryId, setQueryId] = React.useState('') + const mutation = gql` + mutation CreateQuery($attrs: MetacardAttributesInput!) { + createMetacard(attrs: $attrs) { + id: id + ...QueryAttributes + } + } + ${queryAttributes} + ` + + const saveMutation = gql` + mutation SaveWorkspace($id: ID!, $attributes: Json!) { + saveMetacardFromJson(id: $id, attributes: $attributes) { + ...WorkspaceAttributes + } + } + ${workspaceAttributes} + ` + + const [save] = useMutation(saveMutation) + + const [create] = useMutation(mutation, { + onCompleted: data => { + save({ + variables: { + id: workspaceId, + attributes: { + queries: [data.createMetacard.id], + metacard_type: 'workspace', + }, + }, + refetchQueries: ['WorkspaceById'], + }), + setQueryId(data.createMetacard.id) + }, + }) + + return [create, queryId] +} + +const TextSearchForm = props => { + const [textValue, setTextValue] = React.useState('') + const [title, setTitle] = React.useState('') + const [create, queryId] = useCreateQuery(props.workspaceId) + const { runQuery } = React.useContext(WorkspaceContext) + + const onCreate = async () => { + const filter = { + property: 'anyText', + type: 'ILIKE', + value: textValue, + } + + const cql = transformFilterToCQL(filter) + try { + await create({ + variables: { + attrs: { + title: title, + cql: cql, + metacard_tags: ['query'], + metacard_type: 'metacard.query', + }, + }, + }) + } catch (err) { + //eslint-disable-next-line + console.err('Error creating search or updating workspace: ', err) + } + } + + // When the query id actually exists, run it. + React.useEffect( + () => { + if (queryId) { + // Get query + runQuery(queryId) + } + }, + [queryId, runQuery] + ) + + const onClickSearch = async () => { + await onCreate(true) + } + + return ( +
+ setTitle(e.target.value)} + /> +
+ setTextValue(e.target.value)} + /> +
+ + +
+ ) +} + +const useOpenClose = () => { + const [anchorEl, setAnchorEl] = React.useState(null) + const handleClick = () => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + const open = Boolean(anchorEl) + return [open, anchorEl, handleClose, handleClick] +} + +const EmptySearchCard = props => { + const [open, anchorEl, handleClose, handleClick] = useOpenClose() + return ( +
+ + New searches will appear here +
+ +
+ + + + +
+ ) +} //TODO add paging const Results = ({ results }) => @@ -74,30 +258,58 @@ const Results = ({ results }) => [results] ) +const WorkspaceContext = React.createContext({ + runQuery: () => {}, +}) + export const Workspace = () => { const { id } = useParams() const [listResults, setListResults] = React.useState([]) const [currentQuery, setCurrentQuery] = useState(null) const [queries, setQueries] = useState([]) + const [queryIdToRun, setQueryIdToRun] = useState(null) const { results, status, onSearch, onCancel, onClear } = useQueryExecutor() - const [tab, setTab] = React.useState(0) const { loading, error, data } = useQuery(workspaceById, { variables: { ids: [id] }, - onCompleted: data => { - const queries = data.metacardsById[0].attributes[0].queries - setQueries( - queries.map(query => { - const { cql, __typename, ...rest } = query // eslint-disable-line no-unused-vars - return rest - }) - ) - setCurrentQuery(queries[0] ? queries[0].id : null) - }, }) + React.useEffect( + () => { + if (data) { + const queries = data.metacardsById[0].attributes[0].queries + setQueries( + queries.map(query => { + const { cql, __typename, ...rest } = query // eslint-disable-line no-unused-vars + return rest + }) + ) + setCurrentQuery(queries[0] ? queries[0].id : null) + } + }, + [data] + ) + + React.useEffect( + () => { + if (data && queryIdToRun) { + const queryToRun = data.metacardsById[0].attributes[0].queries.find( + q => q.id === queryIdToRun + ) + if (queryToRun !== undefined) { + onSearch(queryToRun) + } + } + }, + [queryIdToRun, data, onSearch] + ) + + const runQuery = queryId => { + setQueryIdToRun(queryId) + } + if (loading) { return } @@ -107,103 +319,106 @@ export const Workspace = () => { } const attributes = data.metacardsById[0].attributes[0] - const { title, lists } = attributes const hasQueries = queries && queries.length > 0 return ( -
+
- - {title} - - - - setTab(selectedIndex)} - indicatorColor="primary" - textColor="primary" - variant="fullWidth" +
- - - + + {title} + + + + setTab(selectedIndex)} + indicatorColor="primary" + textColor="primary" + variant="fullWidth" + > + + + + + {tab === 0 && + (hasQueries ? ( + + {/* //TODO mutate cache on search so that the queries reflect edits made in queryEditor */} + { + onClear() + setCurrentQuery(query.id) + onSearch(query) + }} + onChange={queries => setQueries(queries)} + /> - {tab === 0 && - hasQueries && ( + { + //setPageIndex(0) + onSearch({ + ...queries.find(query => (query.id = currentQuery)), + srcs, + }) + }} + onCancel={srcs => { + srcs.forEach(src => { + onCancel(src) + }) + }} + /> + + + ) : ( + + ))} + + {tab === 1 && ( - {/* //TODO mutate cache on search so that the queries reflect edits made in queryEditor */} - { - onClear() - setCurrentQuery(query.id) - onSearch(query) + { + const results = data.metacardsById.reduce((acc, metacard) => { + return acc.concat(metacard.results) + }, []) + setListResults(results) }} - onChange={queries => setQueries(queries)} /> - { - //setPageIndex(0) - onSearch({ - ...queries.find(query => (query.id = currentQuery)), - srcs, - }) - }} - onCancel={srcs => { - srcs.forEach(src => { - onCancel(src) - }) - }} - /> - + {listResults.map(({ metacard }) => ( + + ))} )} - - {tab === 1 && ( - - { - const results = data.metacardsById.reduce((acc, metacard) => { - return acc.concat(metacard.results) - }, []) - setListResults(results) - }} - /> - - {listResults.map(({ metacard }) => ( - - ))} - - )} -
-
- +
+
+ +
-
+ ) } @@ -259,7 +474,7 @@ const workspaceAttributes = gql` } ` -const useCreate = () => { +const useCreateWorkspace = () => { const [redirectId, setRedirectId] = React.useState(null) const mutation = gql` mutation CreateWorkspace($attrs: MetacardAttributesInput!) { @@ -326,7 +541,7 @@ const useDelete = () => { export default () => { const { refetch, loading, error, data } = useQuery(workspaces) - const [create, redirectId] = useCreate() + const [create, redirectId] = useCreateWorkspace() const [_delete] = useDelete() if (loading) { diff --git a/src/main/webapp/intrigue-api/metacards/metacards.js b/src/main/webapp/intrigue-api/metacards/metacards.js index 66ae5e8b..d5c10f3e 100644 --- a/src/main/webapp/intrigue-api/metacards/metacards.js +++ b/src/main/webapp/intrigue-api/metacards/metacards.js @@ -123,9 +123,9 @@ const typeDefs = ` createMetacard(attrs: MetacardAttributesInput!): MetacardAttributes saveMetacard(id: ID!, attributes: MetacardAttributesInput!): MetacardAttributes - # TBD: Should only be used when... + # TBD: Should only be used when updating assocations with IDs # createMetacardFromJson(attrs: Json!): MetacardAttributes - # saveMetacardFromJson(id: ID!, attrs: Json!): MetacardAttributes + saveMetacardFromJson(id: ID!, attributes: Json!): MetacardAttributes deleteMetacard(id: ID!): ID } @@ -423,6 +423,7 @@ const saveMetacard = async (parent, args, context) => { ) const [oldMetacardAttrs] = oldMetacard.attributes const { catalog, fromGraphqlName, toGraphqlName } = context + if (attributes.filterTree) { attributes = setIn( attributes, @@ -475,6 +476,7 @@ const resolvers = { Mutation: { createMetacard, saveMetacard, + saveMetacardFromJson: saveMetacard, deleteMetacard, }, }