Skip to content

Commit

Permalink
Search entities and records (#11)
Browse files Browse the repository at this point in the history
* Add fragments folder alias

* Add Project Search page

* Add Fields and Entity Edit pages

* Add Records, Fields  search pages

* Move getEntities, getRecord to RecordForm to prevent duplication

* Remove extra variable

* Trim search text before search

* Remove `recursiveKeyValuesToJSON`

* review changes
  • Loading branch information
mahaveer0496 authored Nov 2, 2020
1 parent da8c2cb commit 1d57011
Show file tree
Hide file tree
Showing 21 changed files with 914 additions and 318 deletions.
3 changes: 3 additions & 0 deletions ui/jsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
],
"styles/*": [
"styles/*"
],
"fragments/*": [
"fragments/*"
]
}
},
Expand Down
24 changes: 24 additions & 0 deletions ui/src/components/internal/forms/ProjectSearchForm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import { Field, Form } from 'react-final-form'

import FilledButton from 'components/buttons/FilledButton'
import FormFooter from 'components/internal/FormFooter'
import TextInput from 'components/inputs/TextInput'

function ProjectSearchForm(props) {
return (
<Form
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Field name="search" component={TextInput} placeholder="Search..." stretched={false} />
<FormFooter>
<FilledButton label="Search" disabled={submitting} />
</FormFooter>
</form>
)}
{...props}
/>
)
}

export default ProjectSearchForm
100 changes: 96 additions & 4 deletions ui/src/components/internal/forms/RecordForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
/* eslint-disable no-use-before-define */
import _ from 'lodash'
import arrayMutators from 'final-form-arrays'
import gql from 'graphql-tag'
import flat from 'flat'
import injectSheet from 'react-jss'
import React, { Fragment, useState, useRef, useEffect } from 'react'
import { Field, Form } from 'react-final-form'
import { FieldArray } from 'react-final-form-arrays'
import { ReactSortable } from 'react-sortablejs'
import { withApollo } from 'react-apollo'

import * as mixins from 'styles/mixins'
import ButtonGroupInput from 'components/internal/inputs/ButtonGroupInput'
Expand All @@ -29,6 +31,8 @@ import UploadInput from 'components/inputs/UploadInput'
import { LoaderText } from 'components/internal/typography'
import { SidePaneFormFooter } from 'components/internal/sidePane'

import RECORD_FRAGMENTS from 'fragments/record'

const REFERENCE_OPTIONS = [
{ value: 'new', label: 'New Record' },
{ value: 'edit', label: 'Existing Record' }
Expand Down Expand Up @@ -575,9 +579,54 @@ const toggleFormVisibilityMutator = ([ name, selectedRecord, fields ], state, {
})
}

function RecordForm({ initialValues, entities, fields, ...other }) {
function RecordForm({ initialValues, fields, client, ...other }) {
const newRecord = !initialValues.id

const [ entities, setEntities ] = useState([])
const [ entitiesLoading, setEntitiesLoading ] = useState(false)
const [ recordLoading, setRecordLoading ] = useState(false)

const getEntities = async (entityId) => {
setEntitiesLoading(true)
const { data } = await client.query({
query: RecordForm.ENTITIES_QUERY,
variables: {
entityId
},
fetchPolicy: 'network-only'
})

const newEntities = [ ...entities, ...data.referencedEntities ]

setEntities(newEntities)
setEntitiesLoading(false)
return newEntities
}

const getRecord = async (recordId) => {
setRecordLoading(true)
const { data: { record } } = await client.query({
query: RecordForm.RECORD_QUERY,
variables: {
recordId
},
fetchPolicy: 'network-only'
})

const newEntities = entities
.map((entity) => {
if (entity.id === record.entityId) {
entity.records.push(record)
}

return entity
})

setEntities(newEntities)
setRecordLoading(false)
return newEntities
}

return (
<Form
initialValues={initialValues}
Expand All @@ -592,12 +641,16 @@ function RecordForm({ initialValues, entities, fields, ...other }) {
render={({ handleSubmit, pristine, submitting, values, form: { mutators } }) => (
<form onSubmit={handleSubmit}>
{renderForm({
...other,
getEntities,
getRecord,
entitiesLoading,
recordLoading,
initialValues,
fields,
values,
entities,
mutators
mutators,
...other
})}
<SidePaneFormFooter>
<FilledButton label="Submit" disabled={(newRecord && pristine) || submitting} />
Expand All @@ -609,7 +662,7 @@ function RecordForm({ initialValues, entities, fields, ...other }) {
)
}

export default injectSheet(({ typography }) => ({
RecordForm = injectSheet(({ typography }) => ({
inputWrapper: {
marginBottom: 50
},
Expand All @@ -618,3 +671,42 @@ export default injectSheet(({ typography }) => ({
...typography.regularSquished
}
}))(RecordForm)

RecordForm.ENTITIES_QUERY = gql`
query RecordsPageEntitiesQuery($entityId: ID!) {
referencedEntities(entityId: $entityId) {
id
label
name
label
parentId
fields {
...Record_fields
}
records {
...Record_records
}
}
}
${RECORD_FRAGMENTS.records}
${RECORD_FRAGMENTS.fields}
`

RecordForm.RECORD_QUERY = gql`
query RecordsPageRecordQuery($recordId: ID!) {
record(recordId: $recordId) {
entityId
...Record_records
}
}
${RECORD_FRAGMENTS.records}
`

RecordForm = withApollo(RecordForm)

export default RecordForm
3 changes: 1 addition & 2 deletions ui/src/components/internal/modals/RecordModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import RecordForm from 'components/internal/forms/RecordForm'
import Spacer from 'components/Spacer'
import { DialogTitle } from 'components/internal/typography'

function RecordModal({ formValues, onFormSubmit, ...other }) {
function RecordModal({ formValues, ...other }) {
const action = formValues.id ? 'Edit' : 'New'

const title = `${action} Record`
Expand All @@ -17,7 +17,6 @@ function RecordModal({ formValues, onFormSubmit, ...other }) {
<RecordForm
{...other}
initialValues={formValues}
onSubmit={onFormSubmit}
/>
</Modal>
)
Expand Down
1 change: 1 addition & 0 deletions ui/src/components/internal/sidebars/ProjectSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function ProjectSidebar({ match, project: { name } = {} }) {
<SidebarItem item={{ name: 'Resources', url: `${match.url}/resources`, icon: 'segment' }} />
<Divider />

<SidebarItem item={{ name: 'Search', url: `${match.url}/search`, icon: 'search' }} />
<SidebarItem item={{ name: 'Settings', url: `${match.url}/settings`, icon: 'setting' }} />
</Fragment>
)
Expand Down
13 changes: 11 additions & 2 deletions ui/src/components/pages/EntityPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import React, { Fragment } from 'react'
import { Redirect, Route, Switch } from 'react-router-dom'

import FieldsPage from 'components/pages/FieldsPage'
import FieldsEditPage from 'components/pages/FieldsEditPage'
import Loader from 'components/internal/Loader'
import RecordsPage from 'components/pages/RecordsPage'
import RecordsEditPage from 'components/pages/RecordsEditPage'
import Spacer from 'components/Spacer'
import withConfirmation from 'components/internal/decorators/withConfirmation'
import { BackLink, PageSubTitle } from 'components/internal/typography'
Expand Down Expand Up @@ -47,8 +49,15 @@ function EntityPage({ entity = {}, loading, match }) {
<Spacer height={30} />

<Switch>
<Route path={`${match.path}/fields`} component={FieldsPage} />
<Route path={`${match.path}/records`} component={RecordsPage} />
<Route exact path={`${match.path}/fields`} component={FieldsPage} />
<Route exact path={`${match.path}/records`} component={RecordsPage} />
<Route exact path={`${match.path}/records/:recordId/edit`} component={RecordsEditPage} />
<Route
exact
path={`${match.path}/fields/:fieldId/edit`}
component={FieldsEditPage}
/>

<Redirect from={match.url} to={`${match.url}/fields`} />
</Switch>
</Fragment>
Expand Down
119 changes: 119 additions & 0 deletions ui/src/components/pages/FieldsEditPage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import _ from 'lodash'
import gql from 'graphql-tag'
import injectSheet from 'react-jss'
import React, { Fragment } from 'react'

import FieldForm from 'components/internal/forms/FieldForm'
import Loader from 'components/internal/Loader'
import Spacer from 'components/Spacer'
import withConfirmation from 'components/internal/decorators/withConfirmation'
import { DialogTitle } from 'components/internal/typography'
import { Field } from 'models'
import { MutationResponseModes, withMutation, withQuery } from 'lib/data'
import { showAlertSuccess } from 'client/methods'
import FIELD_FRAGMENTS from 'fragments/fields'

function FieldsEditPage({
entities = [],
fields = [],
loading,
match,
updateField,
history
}) {
const handleFormSubmit = values => updateField(values, { onSuccess: () => {
const { teamId, projectId, entityId } = match.params
const path = `/teams/${teamId}/projects/${projectId}/entities/${entityId}/fields`
showAlertSuccess({ message: 'Successfully updated field' })
history.push(path)
} })

if (loading) {
return <Loader record={{ loading: true }} />
}

const processedFields = Field.process(fields)
const field = processedFields.find(f => f.id === match.params.fieldId)
const title = `Edit Field #${field.label}`

return (
<Fragment>
<DialogTitle>{title}</DialogTitle>
<Spacer height={30} />
<FieldForm
initialValues={field}
onSubmit={handleFormSubmit}
entities={entities}
/>
<Spacer height={30} />
</Fragment>
)
}

FieldsEditPage = injectSheet(({ colors, typography }) => ({
entityName: {
...typography.semibold,

alignItems: 'center',
color: colors.text_dark,
display: 'flex',
lineHeight: 1
}
}))(FieldsEditPage)

FieldsEditPage = withMutation(gql`
mutation UpdateFieldMutation($id: ID!, $input: UpdateFieldInput!) {
updateField(id: $id, input: $input) {
...Field_fields
}
}
${FIELD_FRAGMENTS.fields}
`, {
inputFilter: gql`
fragment UpdateFieldInput on UpdateFieldInput {
id
children
dataType
validations
settings
defaultValue
elementType
hint
label
name
position
referencedEntityId
}
`,
mode: MutationResponseModes.CUSTOM,
updateData: ({ cachedData, responseRecords }) => {
const currentRecords = cachedData.fields
cachedData.fields = _.unionWith(currentRecords, responseRecords, _.isEqual)
}
})(FieldsEditPage)

FieldsEditPage = withQuery(gql`
query GetFieldsAndEntities($entityId: ID!, $projectId: ID!) {
fields(entityId: $entityId) {
...Field_fields
}
entities(projectId: $projectId) {
id
label
name
}
}
${FIELD_FRAGMENTS.fields}
`, {
options: ({ match }) => ({
variables: {
entityId: match.params.entityId,
projectId: match.params.projectId
}
})
})(FieldsEditPage)

export default withConfirmation()(FieldsEditPage)
Loading

0 comments on commit 1d57011

Please sign in to comment.