Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Csstudio 2421 advanced search #151

Merged
merged 4 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions src/api/ologApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
*/

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { withoutCacheBust } from "hooks/useSanitizedSearchParams";
import customization from "config/customization";
import { useCallback } from "react";

Expand Down Expand Up @@ -90,19 +89,14 @@ export const ologApi = createApi({
keepUnusedDataFor: 0, // Don't cache anything
endpoints: builder => ({
searchLogs: builder.query({
query: ({searchParams, searchPageParams}) => {
return {
url: '/logs/search',
params: {
// remove cache bust so we don't send it to the server
// we must do it *here* rather than where useSearchLogsQuery
// is called because we want repeated searches of the same
// search to trigger calling the api (e.g. polling)
...withoutCacheBust(searchParams),
...searchPageParams
}
}
query: ({query, title, desc, start, end, level, logbooks, tags, owner, attachments, from, size, sort}) => {
return {
url: '/logs/search',
params: {
query, title, desc, start, end, level, logbooks, tags, owner, attachments, from, size, sort
}
}
}
}),
getTags: builder.query({
query: () => ({
Expand Down
6 changes: 3 additions & 3 deletions src/beta/components/log/EntryTypeChip.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Chip } from "@mui/material";
import React from "react";

export const EntryTypeChip = ({name, ...props}) => {
export const EntryTypeChip = ({value, ...props}) => {

return (
<Chip
label={name}
aria-label={`has logbook ${name}`}
label={`type: ${value}`}
aria-label={`has level ${value}`}
size="small"
variant="outlined"
color="ologBlack"
Expand Down
9 changes: 6 additions & 3 deletions src/beta/components/log/LogDetails/LogDetails.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,13 @@ const LogDetails = styled(({log, className}) => {
}
<Divider />
<MetadataTable
ValueProps={{
component: "div"
}}
data={{
Logbooks: <ChipList>{log?.logbooks?.map(it => <LogbookChip key={it.name} name={it.name} />)}</ChipList>,
Tags: <ChipList>{log?.tags?.map(it => <TagChip key={it.name} name={it.name} />)}</ChipList>,
"Entry Type": <EntryTypeChip name={log?.level} />
Logbooks: <ChipList>{log?.logbooks?.map(it => <LogbookChip key={it.name} value={it.name} />)}</ChipList>,
Tags: <ChipList>{log?.tags?.map(it => <TagChip key={it.name} value={it.name} />)}</ChipList>,
"Entry Type": <EntryTypeChip value={log?.level} />
}}
/>
</Stack>
Expand Down
6 changes: 3 additions & 3 deletions src/beta/components/log/LogDetails/MetadataTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Value = styled(Typography)({
gridColumn: "span / span"
});

const MetadataTable = styled(({data, className}) => {
const MetadataTable = styled(({data, KeyProps, ValueProps, className}) => {

return (
<Box
Expand All @@ -22,8 +22,8 @@ const MetadataTable = styled(({data, className}) => {
>
{Object.entries(data).map(entry =>
<React.Fragment key={entry[0]}>
<Key>{entry[0]}</Key>
<Value>{entry[1]}</Value>
<Key {...KeyProps}>{entry[0]}</Key>
<Value {...ValueProps}>{entry[1]}</Value>
</React.Fragment>
)}
</Box>
Expand Down
6 changes: 3 additions & 3 deletions src/beta/components/log/LogbookChip.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from "react";
import { Chip } from "@mui/material";

export const LogbookChip = ({name, ...props}) => {
export const LogbookChip = ({value, ...props}) => {
return (
<Chip
label={name}
aria-label={`has logbook ${name}`}
label={value}
aria-label={`has logbook ${value}`}
size="small"
variant="outlined"
color="ologPurple"
Expand Down
6 changes: 3 additions & 3 deletions src/beta/components/log/TagChip.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React from "react";
import LocalOfferIcon from '@mui/icons-material/LocalOffer';
import { Chip } from "@mui/material";

export const TagChip = ({name, ...props}) => {
export const TagChip = ({value, ...props}) => {
return (
<Chip
label={name}
aria-label={`has tag ${name}`}
label={value}
aria-label={`has tag ${value}`}
icon={<LocalOfferIcon />}
size="small"
variant="outlined"
Expand Down
14 changes: 14 additions & 0 deletions src/beta/components/log/TextChip.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from "react";
import { Chip } from "@mui/material";

export const TextChip = ({name, value, ...props}) => {
return (
<Chip
label={`${name}: ${value}`}
aria-label={`has ${name}: ${value}`}
size="small"
variant="outlined"
{...props}
/>
);
};
142 changes: 142 additions & 0 deletions src/beta/components/search/AdvancedSearch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import React, { useEffect } from "react";
import { Button, Stack, Typography } from "@mui/material";
import TextInput from "components/shared/input/TextInput";
import WizardDateInput from "components/shared/input/WizardDateInput";
import EntryTypeSelect from "components/shared/input/managed/EntryTypeSelect";
import LogbooksMultiSelect from "components/shared/input/managed/LogbooksMultiSelect";
import TagsMultiSelect from "components/shared/input/managed/TagsMultiSelect";
import { useForm, useWatch } from "react-hook-form";
import { defaultSearchParams } from "features/searchParamsReducer";
import FilterAltIcon from '@mui/icons-material/FilterAlt';


const isDate = (obj) => {
return obj instanceof Date && !isNaN(obj);
}
const toDate = (dateString) => {
if(isDate(dateString)) {
return new Date(dateString);
} else {
return null;
}
}

export const AdvancedSearch = ({search, onSearchChange, onSearchSubmit}) => {

const values = {...defaultSearchParams, ...search};

const form = useForm({
defaultValues: defaultSearchParams,
values: values
});

const { control, handleSubmit, getValues } = form;

const liveValues = useWatch({control, defaultValue: values});

// send updated form values on any form change
useEffect(() => {
onSearchChange(liveValues);
}, [liveValues, onSearchChange]);

// invoke submit callback when submitted
const onSubmit = () => {
if (onSearchSubmit) {
onSearchSubmit();
}
}

return (
<Stack>
<Typography component="h2" variant="h4" id="advanced-search">Filters</Typography>
<Stack
component="form"
onSubmit={handleSubmit(onSubmit)}
aria-labelledby='advanced-search' role='search'
gap={1}
>
<TextInput
name='title'
label='Title'
control={control}
defaultValue=''
/>
<TextInput
name='desc'
label='Text'
control={control}
defaultValue=''
/>
<EntryTypeSelect
control={control}
/>
<LogbooksMultiSelect
control={control}
/>
<TagsMultiSelect
control={control}
/>
<TextInput
name='owner'
label='Author'
control={control}
defaultValue=''
/>
<WizardDateInput
name='start'
label='Start Time'
form={form}
defaultValue={getValues('start')}
DatePickerProps={{
disableFuture: true
}}
rules={{
validate: {
timeParadox: val => {
const startDate = toDate(val);
const endDate = toDate(getValues('end'));
if(startDate && endDate) {
return startDate <= endDate || 'Start date cannot come after end date'
} else {
return true;
}
}
}
}}
/>
<WizardDateInput
name='end'
label='End Time'
form={form}
defaultValue={getValues('end')}
DatePickerProps={{
disableFuture: true
}}
rules={{
validate: {
timeParadox: val => {
const startDate = toDate(getValues('start'));
const endDate = toDate(val);
if(startDate && endDate) {
return endDate > startDate || 'End date cannot come before start date'
} else {
return true;
}
}
}
}}
/>
<TextInput
name='attachments'
label='Attachments'
control={control}
defaultValue=''
/>
<Stack flexDirection="row" justifyContent="flex-end">
<Button type="submit" variant="contained" startIcon={<FilterAltIcon />} >Apply</Button>
</Stack>
</Stack>
</Stack>
);

};
55 changes: 55 additions & 0 deletions src/beta/components/search/SearchParamsBadges.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from "react";
import { Stack } from "@mui/material";
import { EntryTypeChip } from "../log/EntryTypeChip";
import { LogbookChip } from "../log/LogbookChip";
import { TagChip } from "../log/TagChip";
import { TextChip } from "../log/TextChip";
import { defaultSearchParams } from "features/searchParamsReducer";

const LogbooksList = ({logbooks, onChange}) => {

const onDelete = (logbook) => {
const updated = logbooks.filter(it => it.name !== logbook.name);
onChange(updated);
}

return (
<>
{logbooks.map(logbook => <LogbookChip key={logbook?.name} value={logbook?.name} onDelete={() => onDelete(logbook)} />)}
</>
)
}

const TagsList = ({tags, onChange}) => {

const onDelete = (tag) => {
const updated = tags.filter(it => it.name !== tag.name);
onChange(updated);
}

return (
<>
{tags.map(tag => <TagChip key={tag?.name} value={tag?.name} onDelete={() => onDelete(tag)} />)}
</>
)
}

export const SearchParamsBadges = ({search, onSearch}) => {

const { title, level, desc, owner, attachments, start, end, tags, logbooks } = search;

return (
<Stack flexDirection="row" gap={0.5} flexWrap="wrap" padding={0.5}>
{title ? <TextChip name="title" value={title} onDelete={() => onSearch({...search, title: defaultSearchParams.title})} /> : null}
{level ? <EntryTypeChip value={level} onDelete={() => onSearch({...search, level: defaultSearchParams.level})} /> : null}
{desc ? <TextChip name="desc" value={desc} onDelete={() => onSearch({...search, desc: defaultSearchParams.desc})} /> : null}
{owner ? <TextChip name="author" value={owner} onDelete={() => onSearch({...search, owner: defaultSearchParams.owner})} /> : null}
{attachments ? <TextChip name="attach" value={attachments} onDelete={() => onSearch({...search, attachments: defaultSearchParams.attachments})} /> : null}
{start ? <TextChip name="from" value={start} onDelete={() => onSearch({...search, start: defaultSearchParams.start})} /> : null}
{end ? <TextChip name="to" value={end} onDelete={() => onSearch({...search, end: defaultSearchParams.end})} /> : null}
{tags ? <TagsList tags={tags} onChange={(val) => onSearch({...search, tags: val})} /> : null}
{logbooks ? <LogbooksList logbooks={logbooks} onChange={(val) => onSearch({...search, logbooks: val})} /> : null}
</Stack>
);

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Box, Drawer } from "@mui/material";
import { defaultSearchParams, updateSearchParams } from "features/searchParamsReducer";
import React, { useEffect, useState } from "react";
import { useDispatch } from "react-redux";
import { AdvancedSearch } from "../AdvancedSearch";

export const AdvancedSearchDrawer = ({searchParams, advancedSearchOpen, setAdvancedSearchOpen}) => {

const dispatch = useDispatch();

// Keep a separate local state so we can decide when to submit it
const [localSearchParams, setLocalSearchParams] = useState(defaultSearchParams);

// sync changes to external search params with local state
useEffect(() => {
setLocalSearchParams(searchParams);
}, [searchParams])

// When search changes, update local state
const onSearchChange = (values) => {
setLocalSearchParams(values);
}

// When closed:
// - close the drawer (obviously)
// - update the external search params state
const onAdvancedSearchClose = () => {
setAdvancedSearchOpen(false);
dispatch(updateSearchParams(localSearchParams));
}

return (
<Drawer
open={advancedSearchOpen}
onClose={onAdvancedSearchClose}
>
<Box padding={1}>
<AdvancedSearch
search={localSearchParams}
onSearchChange={onSearchChange}
onSearchSubmit={onAdvancedSearchClose}
/>
</Box>
</Drawer>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export const SearchResultSingleItem = ({log, selected, onClick}) => {
/>
</Stack>
<Stack flexDirection="row" gap={0.5} flexWrap="wrap" gridColumn="span 2">
{log?.logbooks?.map(it => <LogbookChip key={it.name} name={it.name} />)}
{log?.tags?.map(it => <TagChip key={it.name} name={it.name} />)}
{log?.logbooks?.map(it => <LogbookChip key={it.name} value={it.name} />)}
{log?.tags?.map(it => <TagChip key={it.name} value={it.name} />)}
</Stack>
</Stack>
);
Expand Down
Loading
Loading