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

Save draft #1340

Merged
merged 59 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d974f43
fix: Add optional access to navigator.vendor deprecated property
ptbrowne Feb 12, 2024
0294863
docs: Add table of contents
ptbrowne Feb 12, 2024
af8a102
refactor: Extract keys early
ptbrowne Feb 13, 2024
56929a9
refactor: Use query cache & invalidation to facilitate mutation
ptbrowne Feb 13, 2024
aea33b7
feat: Add useMutate helper
ptbrowne Feb 13, 2024
38dbb98
refactor: Remove config through useMutate
ptbrowne Feb 13, 2024
42d977a
feat: Add draft published state
ptbrowne Feb 13, 2024
f81c149
feat: Ability to turn config into draft/publish it from chart list
ptbrowne Feb 13, 2024
c995497
refactor: Extract saveState from component
ptbrowne Feb 13, 2024
3844279
fix: Config for existing chart is correctly fetched
ptbrowne Feb 13, 2024
23dae00
refactor: Rename for clarity
ptbrowne Feb 13, 2024
1d5792c
feat: Use menu instead of tooltip
ptbrowne Feb 13, 2024
64b6359
fix fetch data
ptbrowne Feb 15, 2024
9663353
refactor: Rename method
ptbrowne Feb 15, 2024
4303529
refactor: Decompose effect in both parts
ptbrowne Feb 15, 2024
9d77f58
feat: Show warning if viewing chart still in draft
ptbrowne Feb 15, 2024
ba80167
feat: Add save draft button
ptbrowne Feb 15, 2024
867311c
fix: Add required chartId prop
ptbrowne Feb 15, 2024
af92a08
feat: Add translations for chart features
ptbrowne Feb 15, 2024
22ad15a
refactor: Add docs and explicit the version
ptbrowne Feb 15, 2024
e6f5479
fix: Inverted message
ptbrowne Feb 15, 2024
98d73f7
feat: Add published state as non optional
ptbrowne Feb 15, 2024
dded7a7
fix: button in button
ptbrowne Feb 16, 2024
79786d3
feat: Make create and update config simpler, they take a config and w…
ptbrowne Feb 16, 2024
f32fb11
Show SaveAsDraft button inside layout options
ptbrowne Feb 16, 2024
e51afff
feat: Hide save as draft button if not logged in
ptbrowne Feb 16, 2024
5134a9c
refactor: Rename SaveDraft button
ptbrowne Feb 16, 2024
5000dde
feat: Ability to dismiss snack
ptbrowne Feb 16, 2024
fd452c9
fix: Can save in draft a new chart
ptbrowne Feb 16, 2024
ec1baed
refactor: Reduce case
ptbrowne Feb 16, 2024
a9c297c
refactor: Remove unnnecessary type
ptbrowne Feb 16, 2024
1aa90d2
fix: Remove target blank
ptbrowne Feb 16, 2024
fc0d75b
feat: Show arrow for menu
ptbrowne Feb 16, 2024
6c2842e
fix: Remove config call does not check incoming data from userId, che…
ptbrowne Feb 16, 2024
424bbf2
fix: Gap
ptbrowne Feb 16, 2024
431703f
feat: Show edit button on chart page if it's a draft
ptbrowne Feb 16, 2024
07aded3
refactor: Extract Confirmation Dialog from actions
ptbrowne Feb 16, 2024
ac7621a
feat: Refactor actions to be able to show the primary one as button
ptbrowne Feb 16, 2024
31897ca
fix: Typo
ptbrowne Feb 16, 2024
2097c22
refactor: Split component and make title of visualisation table dynamic
ptbrowne Feb 16, 2024
a0fd5e0
feat: Show drafts and published configs separately
ptbrowne Feb 16, 2024
394ea79
feat: Show actions differently for drafts & published
ptbrowne Feb 16, 2024
2981ece
feat: Show only small check, less things to translate
ptbrowne Feb 16, 2024
8dfdbf2
feat: Make button a bit more lively
ptbrowne Feb 16, 2024
b54f3fd
feat: Add translations
ptbrowne Feb 16, 2024
18a823f
fix: Menu item takes the whole width
ptbrowne Feb 16, 2024
8f5e588
fix: Correct import
ptbrowne Feb 16, 2024
e11e4b9
fix: Missing published state
ptbrowne Feb 16, 2024
d1ebad4
fix: Unused
ptbrowne Feb 16, 2024
6b5846c
refactor: Extract row actions and confirmation dialog from profile-ta…
ptbrowne Feb 19, 2024
e9710a7
feat: Add xsmall variant to button sizes
ptbrowne Feb 19, 2024
4b541df
feat: Show delete in red
ptbrowne Feb 19, 2024
822381f
fix: Make all tables look the same
ptbrowne Feb 19, 2024
dd8bca6
refactor: Extract rename dialog
ptbrowne Feb 20, 2024
ce7c21b
feat: Wrap th cells into table row and extract styling to table
ptbrowne Feb 20, 2024
47a83d6
fix: Delete should correctly work
ptbrowne Feb 20, 2024
75ad1c9
fix: Correctly call confirmation if required
ptbrowne Feb 20, 2024
76b02cf
fix: Typo
ptbrowne Feb 20, 2024
076b30b
fix: Width exceeded 100%
ptbrowne Feb 20, 2024
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
107 changes: 101 additions & 6 deletions app/components/chart-selection-tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import {
Tabs,
Theme,
Tooltip,
useEventCallback,
} from "@mui/material";
import { makeStyles } from "@mui/styles";
import { PUBLISHED_STATE } from "@prisma/client";
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import React from "react";
Expand Down Expand Up @@ -37,9 +39,13 @@ import { useUserConfig } from "@/domain/user-configs";
import { useDataCubesComponentsQuery } from "@/graphql/hooks";
import { Icon, IconName } from "@/icons";
import { useLocale } from "@/src";
import { createConfig, updateConfig } 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 { useMutate } from "@/utils/use-fetch-data";

import { useLocalSnack } from "./use-local-snack";

type TabsState = {
popoverOpen: boolean;
Expand Down Expand Up @@ -313,15 +319,97 @@ export const LayoutChartButton = () => {
);
};

export const PublishChartButton = () => {
const { asPath } = useRouter();
const SaveAsDraftButton = ({ chartId }: { chartId: string | undefined }) => {
const { data: config, invalidate: invalidateConfig } = useUserConfig(chartId);
console.log("config", config?.user_id);
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
const session = useSession();

const [state] = useConfiguratorState();

const [snack, enqueueSnackbar] = useLocalSnack();
const { asPath, replace } = useRouter();

const createConfigMut = useMutate(createConfig);
const updatePublishedStateMut = useMutate(updateConfig);
const loggedInId = session.data?.user.id;

const handleClick = useEventCallback(async () => {
try {
if (config?.user_id && loggedInId) {
const updated = await updatePublishedStateMut.mutate(state, {
userId: loggedInId,
published_state: PUBLISHED_STATE.DRAFT,
key: config.key,
});

if (updated) {
if (asPath !== `/create/${updated.key}`) {
replace(`/create/new?edit=${updated.key}`);
}
enqueueSnackbar({
message: "Draft updated !",
variant: "success",
});
} else {
throw new Error("Could not update draft");
}
} else if (state) {
const saved = await createConfigMut.mutate(
state,
PUBLISHED_STATE.DRAFT
);
if (saved) {
enqueueSnackbar({
message: "Draft saved !",
variant: "success",
});
replace(`/create/${saved.key}`);
} else {
throw new Error("Could not save draft");
}
}
invalidateConfig();
} catch (e) {
enqueueSnackbar({
message: `Error while saving draft: ${
e instanceof Error ? e.message : e
}`,
variant: "error",
});
}
});

return (
<Tooltip
arrow
title={snack?.message ?? ""}
open={!!snack}
disableFocusListener
disableHoverListener
disableTouchListener
>
<Button variant="outlined" onClick={handleClick}>
<Trans id="button.save-draft">Save draft</Trans>
</Button>
</Tooltip>
);
};

export const PublishChartButton = ({
chartId,
}: {
chartId: string | undefined;
}) => {
const session = useSession();
const chartId = getRouterChartId(asPath);
const { data: config, status } = useUserConfig(chartId);
const editingPublishedChart =
session.data?.user.id && config?.user_id === session.data.user.id;
session.data?.user.id &&
config?.user_id === session.data.user.id &&
config.published_state === "PUBLISHED";

return status === "fetching" ? null : (
return status === "fetching" && !config ? (
<>{status}</>
) : (
<NextStepButton>
{editingPublishedChart ? (
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
Expand Down Expand Up @@ -367,6 +455,9 @@ const TabsInner = (props: TabsInnerProps) => {
} = props;
const [state, dispatch] = useConfiguratorState(hasChartConfigs);

const { asPath } = useRouter();
const chartId = getRouterChartId(asPath);

return (
<Box
sx={{
Expand Down Expand Up @@ -414,6 +505,7 @@ const TabsInner = (props: TabsInnerProps) => {
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{ ...style, transform, opacity: 1 }}
component="div"
key={d.key}
sx={{
mr: 2,
Expand Down Expand Up @@ -475,7 +567,10 @@ const TabsInner = (props: TabsInnerProps) => {
(enableLayouting(state) ? (
<LayoutChartButton />
) : (
<PublishChartButton />
<Box gap={"1rem"} display="flex">
<SaveAsDraftButton chartId={chartId} />
<PublishChartButton chartId={chartId} />
</Box>
))}
</Box>
);
Expand Down
34 changes: 34 additions & 0 deletions app/components/use-local-snack.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { useCallback, useEffect, useRef, useState } from "react";

/**
* Holds a temporary snack state
*/
export const useLocalSnack = () => {
type Snack = {
message: string;
variant: "success" | "error";
duration?: number;
};
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const [snack, setSnack] = useState(undefined as Snack | undefined);

useEffect(() => {
return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, []);

return [
snack,
useCallback((snack: Snack | undefined) => {
setSnack(snack);
if (snack) {
timeoutRef.current = setTimeout(() => {
setSnack(undefined);
}, snack.duration || 5000);
}
}, []),
] as const;
};
26 changes: 11 additions & 15 deletions app/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1127,21 +1127,17 @@ const Layout = t.intersection([
export type Layout = t.TypeOf<typeof Layout>;
export type LayoutType = Layout["type"];

const Config = t.intersection([
t.type(
{
version: t.string,
dataSource: DataSource,
layout: Layout,
chartConfigs: t.array(ChartConfig),
activeChartKey: t.string,
},
"Config"
),
t.partial({
publishedState: t.string,
}),
]);
const Config = t.type(
{
version: t.string,
dataSource: DataSource,
layout: Layout,
chartConfigs: t.array(ChartConfig),
activeChartKey: t.string,
},
"Config"
);
[];
ptbrowne marked this conversation as resolved.
Show resolved Hide resolved
export type Config = t.TypeOf<typeof Config>;

export const isValidConfig = (config: unknown): config is Config => {
Expand Down
9 changes: 7 additions & 2 deletions app/configurator/configurator-state.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { PUBLISHED_STATE } from "@prisma/client";
import produce, { current } from "immer";
import get from "lodash/get";
import pickBy from "lodash/pickBy";
Expand Down Expand Up @@ -1875,7 +1876,10 @@ async function publishState(
chartKey
);

const result = await createConfig(preparedConfig);
const result = await createConfig(
preparedConfig,
PUBLISHED_STATE.PUBLISHED
);

onSaveConfig(result, i, reversedChartKeys.length);
return result;
Expand All @@ -1897,8 +1901,9 @@ async function publishState(
? updateConfig(preparedConfig, {
key: dbConfig.key,
userId: user.id,
published_state: PUBLISHED_STATE.PUBLISHED,
})
: createConfig(preparedConfig));
: createConfig(preparedConfig, PUBLISHED_STATE.PUBLISHED));

onSaveConfig(result, 0, 1);
return result;
Expand Down
5 changes: 4 additions & 1 deletion app/db/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Server side methods to connect to the database
*/

import { Config, Prisma, User } from "@prisma/client";
import { Config, Prisma, PUBLISHED_STATE, User } from "@prisma/client";

import { ChartConfig, ConfiguratorStatePublished } from "@/configurator";
import { migrateConfiguratorState } from "@/utils/chart-config/versioning";
Expand All @@ -20,16 +20,19 @@ export const createConfig = async ({
key,
data,
userId,
publishedState,
}: {
key: string;
data: Prisma.ConfigCreateInput["data"];
userId?: User["id"] | undefined;
publishedState: PUBLISHED_STATE;
}): Promise<{ key: string }> => {
return await prisma.config.create({
data: {
key,
data,
user_id: userId,
published_state: publishedState,
},
});
};
Expand Down
21 changes: 20 additions & 1 deletion app/login/components/profile-tables.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import { PUBLISHED_STATE } from "@prisma/client";
import NextLink from "next/link";
import React from "react";
import { ObjectInspector } from "react-inspector";

Check failure on line 26 in app/login/components/profile-tables.tsx

View workflow job for this annotation

GitHub Actions / lint

'ObjectInspector' is declared but its value is never read.

import useDisclosure from "@/components/use-disclosure";
import { ParsedConfig } from "@/db/config";
Expand Down Expand Up @@ -122,6 +123,11 @@
Published
</Trans>
</TableCell>
<TableCell>
<Trans id="login.profile.my-visualizations.chart-updated-date">
Updated
</Trans>
</TableCell>
<TableCell align="right">
<Trans id="login.profile.my-visualizations.chart-actions">
Actions
Expand Down Expand Up @@ -182,6 +188,12 @@
const removeMut = useMutate(removeConfig);
const actions = React.useMemo(() => {
const actions: ActionProps[] = [
{
type: "link",
href: `/v/${config.key}`,
label: t({ id: "login.chart.view", message: "View" }),
iconName: "eye",
},
{
type: "link",
href: `/create/new?copy=${config.key}`,
Expand Down Expand Up @@ -332,7 +344,14 @@
</TableCell>
<TableCell width={120}>
<Typography variant="body2">
{config.created_at.toLocaleDateString("de")}
{config.published_state === PUBLISHED_STATE.DRAFT
? "Draft"
: config.created_at.toLocaleDateString("de")}
</Typography>
</TableCell>
<TableCell width={120}>
<Typography variant="body2">
{config.updated_at.toLocaleDateString("de")}
</Typography>
</TableCell>
<TableCell width="auto" align="right">
Expand Down
3 changes: 2 additions & 1 deletion app/pages/api/config-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ const route = api({
POST: async ({ req, res }) => {
const session = await getServerSession(req, res, nextAuthOptions);
const userId = session?.user?.id;
const { data } = req.body;
const { data, publishedState } = req.body;

return await createConfig({
key: data.key,
data,
userId,
publishedState: publishedState,
});
},
});
Expand Down
2 changes: 1 addition & 1 deletion app/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ model Config {
user User? @relation(fields: [user_id], references: [id])
user_id Int?

published_state PUBLISHED_STATE @default(DRAFT)
published_state PUBLISHED_STATE @default(PUBLISHED)

@@map("config")
}
Expand Down
6 changes: 4 additions & 2 deletions app/server/nextkit.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import createAPI from "nextkit";

export const api = createAPI({
async onError() {
async onError(req, res, error) {

Check failure on line 4 in app/server/nextkit.ts

View workflow job for this annotation

GitHub Actions / lint

'req' is declared but its value is never read.

Check failure on line 4 in app/server/nextkit.ts

View workflow job for this annotation

GitHub Actions / lint

'res' is declared but its value is never read.
return {
status: 500,
message: "Something went wrong.",
message: `Something went wrong: ${
error instanceof Error ? error.message : error
}`,
};
},
});
Loading
Loading