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

feat: Allow edition of published charts #1208

Merged
merged 7 commits into from
Oct 11, 2023
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
45 changes: 41 additions & 4 deletions app/components/chart-selection-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Trans } from "@lingui/macro";
import { Box, Button, Popover, Tab, Tabs, Theme } from "@mui/material";
import { Trans, t } from "@lingui/macro";
import { Box, Button, Popover, Tab, Tabs, Theme, Tooltip } from "@mui/material";
import { makeStyles } from "@mui/styles";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React from "react";
import { DragDropContext, Draggable, Droppable } from "react-beautiful-dnd";

Expand All @@ -23,8 +25,11 @@ import {
} from "@/graphql/query-hooks";
import { Icon, IconName } from "@/icons";
import { useLocale } from "@/src";
import { fetchChartConfig } from "@/utils/chart-config/api";
import { createChartId } from "@/utils/create-chart-id";
import { getRouterChartId } from "@/utils/router/helpers";
import useEvent from "@/utils/use-event";
import { useFetchData } from "@/utils/use-fetch-data";

type TabsState = {
popoverOpen: boolean;
Expand Down Expand Up @@ -277,14 +282,46 @@ const PublishChartButton = () => {
}
});

return (
const { asPath } = useRouter();
const session = useSession();
const chartId = getRouterChartId(asPath);
const queryFn = React.useCallback(
() => fetchChartConfig(chartId ?? ""),
[chartId]
);
const { data: config, status } = useFetchData(queryFn, {
enable: !!(session.data?.user && chartId),
initialStatus: "fetching",
});

const editingPublishedChart =
session.data?.user.id && config?.user_id === session.data.user.id;

return status === "fetching" ? null : (
<Button
color="primary"
variant="contained"
onClick={metadata && components ? goNext : undefined}
sx={{ minWidth: "fit-content" }}
>
<Trans id="button.publish">Publish this visualization</Trans>
{editingPublishedChart ? (
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Tooltip
title={t({
id: "button.update.warning",
message:
"Keep in mind that updating this visualization will affect all the places where it might be already embedded!",
})}
>
<div>
<Icon name="hintWarning" />
</div>
</Tooltip>
<Trans id="button.update">Update this visualization</Trans>
</Box>
) : (
<Trans id="button.publish">Publish this visualization</Trans>
)}
</Button>
);
};
Expand Down
87 changes: 74 additions & 13 deletions app/configurator/configurator-state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
import { mapValueIrisToColor } from "@/configurator/components/ui-helpers";
import { FIELD_VALUE_NONE } from "@/configurator/constants";
import { toggleInteractiveFilterDataDimension } from "@/configurator/interactive-filters/interactive-filters-config-state";
import { ParsedConfig } from "@/db/config";
import { DimensionValue, isGeoDimension } from "@/domain/data";
import { DEFAULT_DATA_SOURCE } from "@/domain/datasource";
import { client } from "@/graphql/client";
Expand All @@ -79,17 +80,23 @@ import {
} from "@/graphql/types";
import { Locale } from "@/locales/locales";
import { useLocale } from "@/locales/use-locale";
import { useUser } from "@/login/utils";
import { findInHierarchy } from "@/rdf/tree-utils";
import {
getDataSourceFromLocalStorage,
useDataSourceStore,
} from "@/stores/data-source";
import { createConfig, fetchChartConfig } from "@/utils/chart-config/api";
import {
createConfig,
fetchChartConfig,
updateConfig,
} from "@/utils/chart-config/api";
import {
CONFIGURATOR_STATE_VERSION,
migrateConfiguratorState,
} from "@/utils/chart-config/versioning";
import { createChartId } from "@/utils/create-chart-id";
import { getRouterChartId } from "@/utils/router/helpers";
import { unreachableError } from "@/utils/unreachable";

export type ConfiguratorStateAction =
Expand Down Expand Up @@ -340,7 +347,8 @@ const INITIAL_STATE: ConfiguratorState = {
dataSource: DEFAULT_DATA_SOURCE,
};

const emptyState: ConfiguratorStateSelectingDataSet = {
const EMPTY_STATE: ConfiguratorStateSelectingDataSet = {
...INITIAL_STATE,
version: CONFIGURATOR_STATE_VERSION,
state: "SELECTING_DATASET",
dataSet: undefined,
Expand Down Expand Up @@ -917,7 +925,7 @@ const reducer: Reducer<ConfiguratorState, ConfiguratorStateAction> = (
case "INITIALIZED":
// Never restore from an UNINITIALIZED state
return action.value.state === "INITIAL"
? getStateWithCurrentDataSource(emptyState)
? getStateWithCurrentDataSource(EMPTY_STATE)
: action.value;
case "DATASET_SELECTED":
if (draft.state === "SELECTING_DATASET") {
Expand Down Expand Up @@ -1330,6 +1338,19 @@ export const initChartStateFromChart = async (
}
};

export const initChartStateFromChartEdit = async (
from: ChartId
): Promise<ConfiguratorStateConfiguringChart | undefined> => {
const config = await fetchChartConfig(from);

if (config?.data) {
return migrateConfiguratorState({
...config.data,
state: "CONFIGURING_CHART",
});
}
};

export const initChartStateFromCube = async (
client: Client,
datasetIri: DatasetIri,
Expand Down Expand Up @@ -1361,7 +1382,7 @@ export const initChartStateFromCube = async (

if (metadata?.dataCubeByIri && components?.dataCubeByIri) {
return transitionStepNext(
getStateWithCurrentDataSource({ ...emptyState, dataSet: datasetIri }),
getStateWithCurrentDataSource({ ...EMPTY_STATE, dataSet: datasetIri }),
{ ...metadata.dataCubeByIri, ...components.dataCubeByIri }
);
}
Expand Down Expand Up @@ -1421,6 +1442,7 @@ const ConfiguratorStateProviderInternal = ({
const [state, dispatch] = stateAndDispatch;
const { asPath, push, replace, query } = useRouter();
const client = useClient();
const user = useUser();

// Initialize state on page load.
useEffect(() => {
Expand All @@ -1433,6 +1455,8 @@ const ConfiguratorStateProviderInternal = ({
if (chartId === "new") {
if (query.from && typeof query.from === "string") {
newChartState = await initChartStateFromChart(query.from);
} else if (query.edit && typeof query.edit === "string") {
newChartState = await initChartStateFromChartEdit(query.edit);
} else if (query.cube && typeof query.cube === "string") {
newChartState = await initChartStateFromCube(
client,
Expand Down Expand Up @@ -1477,12 +1501,20 @@ const ConfiguratorStateProviderInternal = ({
switch (state.state) {
case "CONFIGURING_CHART":
if (chartId === "new") {
const newChartId = createChartId();
window.localStorage.setItem(
getLocalStorageKey(newChartId),
JSON.stringify(state)
);
replace(`/create/${newChartId}`);
if (query.edit && typeof query.edit === "string") {
replace(`/create/${query.edit}`);
window.localStorage.setItem(
getLocalStorageKey(query.edit),
JSON.stringify(state)
);
} else {
const newChartId = createChartId();
window.localStorage.setItem(
getLocalStorageKey(newChartId),
JSON.stringify(state)
);
replace(`/create/${newChartId}`);
}
} else {
// Store current state in localstorage
window.localStorage.setItem(
Expand All @@ -1495,7 +1527,18 @@ const ConfiguratorStateProviderInternal = ({
case "PUBLISHING":
(async () => {
try {
const result = await createConfig({
let dbConfig: ParsedConfig | undefined;
const key = getRouterChartId(asPath);

if (key && user) {
const config = await fetchChartConfig(key);

if (config && config.user_id === user.id) {
dbConfig = config;
}
}

const preparedConfig: ConfiguratorStatePublishing = {
...state,
chartConfigs: [
...state.chartConfigs.map((d) => {
Expand All @@ -1520,7 +1563,14 @@ const ConfiguratorStateProviderInternal = ({
// the story from a specific point and e.g. toggle back and forth between
// the different charts).
activeChartKey: state.chartConfigs[0].key,
});
};

const result = await (dbConfig && user
? updateConfig(preparedConfig, {
key: dbConfig.key,
userId: user.id,
})
: createConfig(preparedConfig));

/**
* EXPERIMENTAL: Post back created chart ID to opener and close window.
Expand Down Expand Up @@ -1551,7 +1601,18 @@ const ConfiguratorStateProviderInternal = ({
} catch (e) {
console.error(e);
}
}, [state, dispatch, chartId, push, asPath, locale, query.from, replace]);
}, [
state,
dispatch,
chartId,
push,
asPath,
locale,
query.from,
replace,
user,
query.edit,
]);

return (
<ConfiguratorStateContext.Provider value={stateAndDispatch}>
Expand Down
35 changes: 32 additions & 3 deletions app/db/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,55 @@ import { Config, Prisma, User } from "@prisma/client";
import { ChartConfig, ConfiguratorStatePublished } from "@/configurator";
import { migrateConfiguratorState } from "@/utils/chart-config/versioning";

import { createChartId } from "../utils/create-chart-id";

import prisma from "./client";

/**
* Store data in the DB.
* If the user is logged, the chart is linked to the user.
*
* @param key Key of the config to be stored
* @param data Data to be stored as configuration
*/
export const createConfig = async ({
key,
data,
userId,
}: {
key: string;
data: Prisma.ConfigCreateInput["data"];
userId?: User["id"] | undefined;
}): Promise<{ key: string }> => {
return await prisma.config.create({
data: {
key: createChartId(),
key,
data,
user_id: userId,
},
});
};

/**
* Update config in the DB.
* Only valid for logged in users.
*
* @param key Key of the config to be updated
* @param data Data to be stored as configuration
*/
export const updateConfig = async ({
key,
data,
userId,
}: {
key: string;
data: Prisma.ConfigCreateInput["data"];
userId: User["id"];
}): Promise<{ key: string }> => {
return await prisma.config.update({
where: {
key,
},
data: {
key,
data,
user_id: userId,
},
Expand Down
8 changes: 8 additions & 0 deletions app/locales/de/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ msgstr "Diese Visualisierung veröffentlichen"
msgid "button.share"
msgstr "Teilen"

#: app/components/chart-selection-tabs.tsx
msgid "button.update"
msgstr "Diese Visualisierung aktualisieren"

#: app/components/chart-selection-tabs.tsx
msgid "button.update.warning"
msgstr "Denken Sie daran, dass sich die Aktualisierung dieser Visualisierung auf alle Stellen auswirkt, an denen sie bereits eingebettet ist!"

#: app/configurator/components/field-i18n.ts
msgid "chart.map.layers.area"
msgstr "Flächen"
Expand Down
8 changes: 8 additions & 0 deletions app/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ msgstr "Publish this visualization"
msgid "button.share"
msgstr "Share"

#: app/components/chart-selection-tabs.tsx
msgid "button.update"
msgstr "Update this visualization"

#: app/components/chart-selection-tabs.tsx
msgid "button.update.warning"
msgstr "Keep in mind that updating this visualization will affect all the places where it might be already embedded!"

#: app/configurator/components/field-i18n.ts
msgid "chart.map.layers.area"
msgstr "Areas"
Expand Down
8 changes: 8 additions & 0 deletions app/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ msgstr "Publier cette visualisation"
msgid "button.share"
msgstr "Partager"

#: app/components/chart-selection-tabs.tsx
msgid "button.update"
msgstr "Actualiser cette visualisation"

#: app/components/chart-selection-tabs.tsx
msgid "button.update.warning"
msgstr "Gardez à l'esprit que la mise à jour de cette visualisation affectera tous les endroits où elle est déjà intégrée !"

#: app/configurator/components/field-i18n.ts
msgid "chart.map.layers.area"
msgstr "Zones"
Expand Down
8 changes: 8 additions & 0 deletions app/locales/it/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ msgstr "Pubblica questa visualizzazione"
msgid "button.share"
msgstr "Condividi"

#: app/components/chart-selection-tabs.tsx
msgid "button.update"
msgstr "Aggiornare questa visualizzazione"

#: app/components/chart-selection-tabs.tsx
msgid "button.update.warning"
msgstr "Tenete presente che l'aggiornamento di questa visualizzazione avrà effetto su tutti i luoghi in cui potrebbe essere già incorporata!"

#: app/configurator/components/field-i18n.ts
msgid "chart.map.layers.area"
msgstr "Aree"
Expand Down
Loading
Loading