Skip to content

Commit

Permalink
chore: Merge main
Browse files Browse the repository at this point in the history
  • Loading branch information
bprusinowski committed Nov 9, 2022
2 parents a05059e + 8b60116 commit 8a45f7c
Show file tree
Hide file tree
Showing 70 changed files with 2,041 additions and 2,886 deletions.
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ENV PORT 3000

COPY ./ ./

RUN yarn prisma generate
RUN yarn build

# Install only prod dependencies and start app
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,42 @@ Those charts configurations are kept in the repository.
At the moment, the screenshots are made from charts using data from int.lindas.admin.ch as for some functionalities, we do not
yet have production data.
## Authentication
Authentication by eIAM through a Keycloak instance.
We use Next-auth to integrate our application with Keycloak.
See https://next-auth.js.org/providers/keycloak for documentation.
### Locally
The easiest way is to run Keycloak via Docker.
```
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:19.0.3 start-dev
```

⚠️ After creating the container via the above command, if you stop it, restart the container via the docker UI so that you re-use the
same storage, otherwise you'll have to reconfigure Keycloak.

To configure Keycloak:

- Access the [Keycloak admin][keycloak-admin] (login, password: "admin", "admin")
- Create client application
- Via import: [Keycloak][keycloak-admin] > Clients > Import client
- Use the exported client `keycloak-visualize-client-dev.json`
- Manually: [Keycloak][keycloak-admin] > Clients > Create client
- id: "visualize"
- Choose OpenIDConnect
- In next slide, toggle "Client Authentication" on
- Configure redirect URI on client
- Root URL: `http://localhost:3000`
- Redirect URI: `/api/auth/callback/keycloak`
- Create a user
- Set a password to the user (in Credentials tab)
- Set environment variables in `.env.local`
- KEYCLOAK_ID: "visualize"
- KEYCLOAK_SECRET: From [Keycloak][keycloak-admin] > Clients > visualize > Credentials > Client secret
- KEYCLOAK_ISSUER: http://localhost:8080/realms/master

[keycloak-admin]: http://localhost:8080/admin/master/console/#/
11 changes: 3 additions & 8 deletions app/components/chart-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Flex from "@/components/flex";
import {
ChartType,
ConfiguratorStateConfiguringChart,
ConfiguratorStateDescribingChart,
ConfiguratorStatePublishing,
} from "@/configurator";
import { useConfiguratorState } from "@/src";
Expand All @@ -18,9 +17,7 @@ type ChartPanelProps = { children: ReactNode } & BoxProps;
export const ChartPanelConfigurator = (props: ChartPanelProps) => {
// This type of chart panel can only appear for below steps.
const [state] = useConfiguratorState() as unknown as [
| ConfiguratorStateConfiguringChart
| ConfiguratorStateDescribingChart
| ConfiguratorStatePublishing
ConfiguratorStateConfiguringChart | ConfiguratorStatePublishing
];

return (
Expand Down Expand Up @@ -53,12 +50,10 @@ const useChartPanelInnerStyles = makeStyles<Theme, { showTabs: boolean }>(
root: {
flexDirection: "column",
backgroundColor: theme.palette.grey[100],
boxShadow: theme.shadows[6],
borderRadius: 12,
borderTopLeftRadius: ({ showTabs }) => (showTabs ? 0 : 12),
border: "1px solid",
borderColor: theme.palette.divider,
// TODO: Handle properly when chart composition is implemented (enable when
// ChartSelectionTabs becomes scrollable)
borderTopRightRadius: 12,
overflow: "hidden",
width: "auto",
},
Expand Down
41 changes: 35 additions & 6 deletions app/components/chart-preview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Trans } from "@lingui/macro";
import { Box, Typography } from "@mui/material";
import { Box, Theme, Typography } from "@mui/material";
import { makeStyles } from "@mui/styles";
import Head from "next/head";
import * as React from "react";

Expand Down Expand Up @@ -39,16 +40,33 @@ export const ChartPreview = ({
);
};

const useStyles = makeStyles<Theme>({
title: {
marginBottom: 2,
cursor: "pointer",
"&:hover": {
textDecoration: "underline",
},
},
description: {
marginBottom: 2,
cursor: "pointer",
"&:hover": {
textDecoration: "underline",
},
},
});

export const ChartPreviewInner = ({
dataSetIri,
dataSource,
}: {
dataSetIri: string;
dataSource: DataSource;
}) => {
const [state] = useConfiguratorState();
const [state, dispatch] = useConfiguratorState();
const locale = useLocale();

const classes = useStyles();
const [{ data: metaData }] = useDataCubeMetadataQuery({
variables: {
iri: dataSetIri,
Expand Down Expand Up @@ -91,16 +109,21 @@ export const ChartPreviewInner = ({
</Box>
)}
{(state.state === "CONFIGURING_CHART" ||
state.state === "DESCRIBING_CHART" ||
state.state === "PUBLISHING") && (
<>
<>
<Typography
variant="h2"
sx={{
mb: 2,
color: state.meta.title[locale] === "" ? "grey.500" : "text",
}}
className={classes.title}
onClick={() =>
dispatch({
type: "ACTIVE_FIELD_CHANGED",
value: "title",
})
}
>
{state.meta.title[locale] === "" ? (
<Trans id="annotation.add.title">[ Title ]</Trans>
Expand All @@ -118,11 +141,17 @@ export const ChartPreviewInner = ({
</Head>
<Typography
variant="body1"
className={classes.description}
sx={{
mb: 2,
color:
state.meta.description[locale] === "" ? "grey.500" : "text",
}}
onClick={() =>
dispatch({
type: "ACTIVE_FIELD_CHANGED",
value: "description",
})
}
>
{state.meta.description[locale] === "" ? (
<Trans id="annotation.add.description">[ Description ]</Trans>
Expand Down
68 changes: 53 additions & 15 deletions app/components/chart-selection-tabs.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Box, Popover, Tab, Tabs, Theme } from "@mui/material";
import { Trans } from "@lingui/macro";
import { Box, Popover, Tab, Tabs, Theme, Button } from "@mui/material";
import { makeStyles } from "@mui/styles";
import React, {
createContext,
Expand All @@ -12,13 +13,14 @@ import React, {
import {
ChartType,
ConfiguratorStateConfiguringChart,
ConfiguratorStateDescribingChart,
ConfiguratorStatePublishing,
useConfiguratorState,
} from "@/configurator";
import { ChartTypeSelector } from "@/configurator/components/chart-type-selector";
import { getIconName } from "@/configurator/components/ui-helpers";
import { useDataCubeMetadataWithComponentValuesQuery } from "@/graphql/query-hooks";
import { Icon, IconName } from "@/icons";
import { useLocale } from "@/src";
import useEvent from "@/utils/use-event";

import Flex from "./flex";
Expand Down Expand Up @@ -95,9 +97,7 @@ const useStyles = makeStyles<Theme, { editable: boolean }>((theme) => ({

const TabsEditable = ({ chartType }: { chartType: ChartType }) => {
const [configuratorState] = useConfiguratorState() as unknown as [
| ConfiguratorStateConfiguringChart
| ConfiguratorStateDescribingChart
| ConfiguratorStatePublishing
ConfiguratorStateConfiguringChart | ConfiguratorStatePublishing
];
const [tabsState, setTabsState] = useTabsState();
const [popoverAnchorEl, setPopoverAnchorEl] = useState<HTMLElement | null>(
Expand Down Expand Up @@ -149,6 +149,41 @@ const TabsFixed = ({ chartType }: { chartType: ChartType }) => {
return <TabsInner chartType={chartType} editable={false} />;
};

const PublishChartButton = () => {
const [state, dispatch] = useConfiguratorState();
const { dataSet: dataSetIri } = state as
| ConfiguratorStatePublishing
| ConfiguratorStateConfiguringChart;
const locale = useLocale();
const [{ data }] = useDataCubeMetadataWithComponentValuesQuery({
variables: {
iri: dataSetIri ?? "",
sourceType: state.dataSource.type,
sourceUrl: state.dataSource.url,
locale,
},
pause: !dataSetIri,
});
const goNext = useEvent(() => {
if (data?.dataCubeByIri) {
dispatch({
type: "STEP_NEXT",
dataSetMetadata: data?.dataCubeByIri,
});
}
});

return (
<Button
color="primary"
variant="contained"
onClick={data ? goNext : undefined}
>
<Trans id="button.publish">Publish the chart</Trans>
</Button>
);
};

const TabsInner = ({
chartType,
editable,
Expand All @@ -159,16 +194,19 @@ const TabsInner = ({
onActionButtonClick?: (e: React.MouseEvent<HTMLElement>) => void;
}) => {
return (
<Tabs value={0}>
{/* TODO: Generate dynamically when chart composition is implemented */}
<Tab
sx={{ p: 0 }}
onClick={onActionButtonClick}
label={
<TabContent iconName={getIconName(chartType)} editable={editable} />
}
/>
</Tabs>
<Box display="flex" sx={{ width: "100%", alignItems: "flex-start" }}>
<Tabs value={0} sx={{ position: "relative", top: 1, flexGrow: 1 }}>
{/* TODO: Generate dynamically when chart composition is implemented */}
<Tab
sx={{ p: 0, background: "white" }}
onClick={onActionButtonClick}
label={
<TabContent iconName={getIconName(chartType)} editable={editable} />
}
/>
</Tabs>
<PublishChartButton />
</Box>
);
};

Expand Down
3 changes: 3 additions & 0 deletions app/components/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import Flex from "@/components/flex";
import { LanguageMenu } from "@/components/language-menu";
import { SOURCE_OPTIONS } from "@/domain/datasource/constants";

import LoginMenu from "./login-menu";

const DEFAULT_HEADER_PROGRESS = 100;

export const useHeaderProgressContext = () => {
Expand Down Expand Up @@ -137,6 +139,7 @@ const MetadataMenu = ({ contentId }: { contentId?: string }) => {
}}
>
<LanguageMenu contentId={contentId} />
<LoginMenu />
</Flex>
);
};
Expand Down
1 change: 0 additions & 1 deletion app/components/language-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export const LanguageMenu = ({ contentId }: { contentId?: string }) => {
ml: [0, "auto"],
width: ["100%", "auto"],
backgroundColor: ["grey.300", "transparent"],
order: [1, 2],
justifyContent: "flex-end",
}}
>
Expand Down
71 changes: 71 additions & 0 deletions app/components/login-menu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { Typography, Button, Box } from "@mui/material";
import { getProviders, signIn, signOut, useSession } from "next-auth/react";
import Link from "next/link";
import { useEffect, useState } from "react";

import { Awaited } from "@/domain/types";

type Providers = Awaited<ReturnType<typeof getProviders>>;

const useProviders = () => {
const [state, setState] = useState({
status: "loading",
data: undefined as Providers | undefined,
});
useEffect(() => {
const run = async () => {
const providers = await getProviders();
setState({ status: "loaded", data: providers });
};
run();
}, []);
return state;
};

function LoginMenu() {
const { data: session, status: sessionStatus } = useSession();
const { data: providers, status: providersStatus } = useProviders();
if (sessionStatus === "loading" || providersStatus === "loading") {
return null;
}
if (!providers || !Object.keys(providers).length) {
return null;
}
return (
<Box sx={{ alignItems: "center", display: "flex" }}>
{session ? (
<>
<Typography variant="body2">
Signed in as <Link href="/profile">{session.user?.name}</Link>{" "}
{" - "}
</Typography>
<Button
variant="text"
color="primary"
size="small"
onClick={async () => await signOut()}
>
Sign out
</Button>
</>
) : (
<>
<Typography variant="body2">
Not signed in
{" - "}
</Typography>
<Button
variant="text"
color="primary"
size="small"
onClick={() => signIn("keycloak")}
>
Sign in
</Button>
</>
)}
</Box>
);
}

export default LoginMenu;
Loading

0 comments on commit 8a45f7c

Please sign in to comment.