Skip to content

Commit

Permalink
Merge pull request #1208 from visualize-admin/feat/allow-editing-char…
Browse files Browse the repository at this point in the history
…ts-when-logged-in

feat: Allow edition of published charts
  • Loading branch information
bprusinowski authored Oct 11, 2023
2 parents 3f5fb3d + 8318ef1 commit ecb7add
Show file tree
Hide file tree
Showing 17 changed files with 358 additions and 72 deletions.
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

1 comment on commit ecb7add

@vercel
Copy link

@vercel vercel bot commented on ecb7add Oct 11, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

visualization-tool – ./

visualization-tool-alpha.vercel.app
visualization-tool-ixt1.vercel.app
visualization-tool-git-main-ixt1.vercel.app

Please sign in to comment.