Skip to content

Commit

Permalink
feat: Search cube by termset instead of searching directly by compati…
Browse files Browse the repository at this point in the history
…ble cube

Instead of directly searching for a compatible cube, search by termset.
This provides flexibility as we do not need a cube for search, and we
will be able to integrate termset searching directly in the search page

- Added Termset specific type to Termset scalar
- Added story to search for cube termsets
- When searching by temporal dimension and termset, two queries are made
- Extracted URQL decorator
  • Loading branch information
ptbrowne committed Apr 12, 2024
1 parent c9f6234 commit c538d59
Show file tree
Hide file tree
Showing 18 changed files with 519 additions and 192 deletions.
19 changes: 18 additions & 1 deletion .storybook/decorators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import { i18n } from "../app/locales/locales";
import { theme } from "../app/themes/federal";
import AsyncLocalizationProvider from "../app/utils/l10n-provider";

import { Decorator } from "@storybook/react";
import { Client, Provider } from "urql";

export const AppContextDecorator = (Story: NextPage) => (
<SessionProvider>
<SessionProvider refetchOnWindowFocus={false} refetchWhenOffline={false}>
<AsyncLocalizationProvider locale="en">
<I18nProvider i18n={i18n}>
<CssBaseline />
Expand All @@ -36,3 +39,17 @@ export const RouterDecorator = (Story: NextPage) => {
</>
);
};

const graphqlURL =
process.env.NODE_ENV === "production"
? "/api/graphql"
: "http:https://localhost:3000/api/graphql";

export const UrqlDecorator: Decorator = (Story) => {
const client = new Client({ url: graphqlURL });
return (
<Provider value={client}>
<Story />
</Provider>
);
};
8 changes: 6 additions & 2 deletions .storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { Preview } from "@storybook/react";
import type { IndexEntry } from "@storybook/types";
import { AppContextDecorator, RouterDecorator } from "./decorators";
import {
AppContextDecorator,
RouterDecorator,
UrqlDecorator,
} from "./decorators";

const preview: Preview = {
// @ts-ignore
Expand All @@ -9,7 +13,7 @@ const preview: Preview = {
basePath: "/profile",
},
},
decorators: [AppContextDecorator, RouterDecorator],
decorators: [AppContextDecorator, RouterDecorator, UrqlDecorator],
options: {
storySort: (a: IndexEntry, b: IndexEntry) =>
a.id === b.id
Expand Down
232 changes: 232 additions & 0 deletions app/components/graphql-search.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import {
Box,
Checkbox,
CircularProgress,
FormControlLabel,
ListItemText,
MenuItem,
OutlinedInput,
Select,
SelectChangeEvent,
Stack,
TextField,
} from "@mui/material";
import { Meta } from "@storybook/react";
import keyBy from "lodash/keyBy";
import { useEffect, useState } from "react";
import { ObjectInspector } from "react-inspector";

import { DatasetResult } from "@/browser/dataset-browse";
import { Error } from "@/components/hint";
import Tag from "@/components/tag";
import { Termset } from "@/domain/data";
import { truthy } from "@/domain/types";
import {
SearchCubeFilterType,
useDataCubeTermsetsQuery,
useSearchCubesQuery,
} from "@/graphql/query-hooks";

export const Search = () => {
const [inputValue, setInputValue] = useState<string>("");
const [query, setQuery] = useState<string>("");
const [cube, setCube] = useState<string>(
"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9"
);
const [temporalDimension, setTemporalDimension] = useState("-");
const [sharedDimensions, setSharedDimensions] = useState<
Termset["iri"][] | undefined
>(undefined);

const [cubeTermsetsResults] = useDataCubeTermsetsQuery({
variables: {
locale: "en",
sourceType: "sparql",
sourceUrl: "https://lindas.admin.ch/query",
cubeFilter: {
iri: cube,
},
},
});

const [searchCubesResult] = useSearchCubesQuery({
pause: !sharedDimensions || sharedDimensions.length === 0,
variables: {
locale: "en",
sourceType: "sparql",
sourceUrl: "https://lindas.admin.ch/query",
filters: [
sharedDimensions
? {
type: SearchCubeFilterType.SharedDimensions,
value: sharedDimensions?.join(";"),
}
: null,
temporalDimension !== "-"
? {
type: SearchCubeFilterType.TemporalDimension,
value: temporalDimension,
}
: null,
].filter(truthy),
includeDrafts: false,
query,
},
});

const handleChangeSharedDimensions = (
ev: SelectChangeEvent<typeof sharedDimensions>
) => {
const {
target: { value },
} = ev;
setSharedDimensions(
// On autofill we get a stringified value.
typeof value === "string" ? value.split(",") : value
);
};

useEffect(() => {
if (
sharedDimensions === undefined &&
cubeTermsetsResults?.data?.dataCubeTermsets
) {
setSharedDimensions(
cubeTermsetsResults?.data?.dataCubeTermsets.map((sd) => sd.iri)
);
}
}, [cubeTermsetsResults, sharedDimensions]);

const cubeSharedDimensionsByIri = keyBy(
cubeTermsetsResults.data?.dataCubeTermsets ?? [],
(x) => x.iri
);

return (
<div>
<h2>Search results</h2>
<Stack gap={1} direction="column" alignItems="start">
<FormControlLabel
label="Query"
labelPlacement="start"
control={
<TextField
label="Search"
size="small"
placeholder="Search"
value={inputValue}
onChange={(ev) => {
const value = (ev.target as HTMLInputElement).value;
setInputValue(value);
}}
onKeyUp={(ev) => {
if (ev.key === "Enter") {
setQuery(inputValue);
}
}}
/>
}
/>

<FormControlLabel
label="Compatible cube"
labelPlacement="start"
control={
<Select
size="small"
native
onChange={(ev) => setCube(ev.target.value)}
value={cube}
>
<option value="https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9">
Einmalvergütung für Photovoltaikanlagen
</option>
<option value="https://environment.ld.admin.ch/foen/nfi/nfi_C-1029/cube/2023-1">
NFI Topics by stage of stand development
</option>
<option value="https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_auszahlungen/8">
Gebäudeprogramm - Auszahlungen nach Massnahmenbereich und
Berichtsjahr
</option>
</Select>
}
/>

<FormControlLabel
label="Termsets"
labelPlacement="start"
control={
<Stack gap={1} alignItems="center" direction="row">
<Select
labelId="demo-multiple-chip-label"
id="demo-multiple-chip"
multiple
value={sharedDimensions ?? []}
onChange={handleChangeSharedDimensions}
input={<OutlinedInput id="select-multiple-chip" label="Chip" />}
renderValue={(selected) =>
selected.map((value) => (
<Tag key={value} type="termset" sx={{ mr: 1 }}>
{cubeSharedDimensionsByIri?.[value]?.label}
</Tag>
))
}
>
{(
cubeTermsetsResults?.data?.dataCubeTermsets as Termset[]
)?.map((sd) => (
<MenuItem
key={sd.label}
value={sd.iri}
sx={{ gap: 2, alignItems: "start" }}
>
<Checkbox
checked={
sharedDimensions && sharedDimensions.includes(sd.iri)
}
/>
<ListItemText primary={sd.label} secondary={sd.iri} />
</MenuItem>
))}
</Select>
{cubeTermsetsResults.fetching ? (
<CircularProgress size={12} />
) : null}
</Stack>
}
/>

<FormControlLabel
label={"Temporal dimension"}
labelPlacement="start"
control={
<Select
value={temporalDimension}
onChange={(ev) => setTemporalDimension(ev.target.value as string)}
>
<MenuItem value="-">-</MenuItem>
<MenuItem value="Year">Year</MenuItem>
<MenuItem value="Month">Month</MenuItem>
</Select>
}
/>
</Stack>

<Box my={2}>
<ObjectInspector data={searchCubesResult.data} />
</Box>
{searchCubesResult.fetching ? <CircularProgress size={12} /> : null}
{searchCubesResult.error ? (
<Error>{searchCubesResult.error.message}</Error>
) : null}

{searchCubesResult?.data?.searchCubes.map((item) => (
<DatasetResult key={item.cube.iri} dataCube={item.cube} showTermsets />
))}
</div>
);
};

export default {
title: "Data / Search",
} as Meta;
78 changes: 78 additions & 0 deletions app/components/graphql-termsets.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
CircularProgress,
FormControlLabel,
Select,
Stack,
} from "@mui/material";
import { Meta } from "@storybook/react";
import { useState } from "react";

import Tag from "@/components/tag";
import { useDataCubeTermsetsQuery } from "@/graphql/query-hooks";

export const Termsets = () => {
const [cube, setCube] = useState<string>(
"https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9"
);
const [result] = useDataCubeTermsetsQuery({
variables: {
locale: "en",
sourceType: "sparql",
sourceUrl: "https://lindas.admin.ch/query",
cubeFilter: {
iri: cube,
latest: true,
},
},
});

const { data } = result;

return (
<div>
<h2>Termsets</h2>
<Stack gap={1} direction="column" alignItems="start">
<FormControlLabel
label="Compatible cube"
labelPlacement="start"
control={
<Stack gap={1} direction="row" alignItems="center">
<Select
size="small"
native
onChange={(ev) => setCube(ev.target.value)}
value={cube}
>
<option value="https://energy.ld.admin.ch/sfoe/bfe_ogd84_einmalverguetung_fuer_photovoltaikanlagen/9">
Einmalvergütung für Photovoltaikanlagen
</option>
<option value="https://environment.ld.admin.ch/foen/nfi/nfi_C-1029/cube/2023-1">
NFI Topics by stage of stand development
</option>
<option value="https://energy.ld.admin.ch/sfoe/bfe_ogd18_gebaeudeprogramm_auszahlungen/8">
Gebäudeprogramm - Auszahlungen nach Massnahmenbereich und
Berichtsjahr
</option>
</Select>
{result.fetching ? <CircularProgress size={12} /> : null}
</Stack>
}
/>
</Stack>

{result.error ? <div>Error: {result.error.message}</div> : null}

<Stack mt={2} gap={2} direction="row">
{data?.dataCubeTermsets?.map((termset) => (
<Tag key={termset.iri} type="termset">
{termset.label}
</Tag>
))}
</Stack>
</div>
);
};

export default {
title: "Data / Termsets",
} as Meta;

0 comments on commit c538d59

Please sign in to comment.