Monitoring app UI (#677)

* Replace macaw to next

* UI WIP

* ui wip

* fix graphql calls

* Fix ui

* Changsets

* Apply CR review

* fix spacing
This commit is contained in:
Lukasz Ostrowski 2023-06-27 19:20:58 +02:00 committed by GitHub
parent f6ff907cfd
commit 3bd7e3f05d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 267 additions and 526 deletions

View file

@ -0,0 +1,5 @@
---
"saleor-app-monitoring": major
---
Updated App's UI to the new Macaw. Simplified the view and removed unnecessary not-implemented providers.

View file

@ -0,0 +1 @@
MONITORING_APP_API_URL=

View file

@ -55,6 +55,8 @@ To use Graphql Playground, `Monitoring` app needs to be installed in Saleor, and
### Testing DataDog integration
Set `MOCK_DATADOG_CLIENT` env to `True`
Use these credentials sets to test DataDog integration:
Working credentials:

View file

@ -12,5 +12,7 @@ services:
- "5001:80"
environment:
- DEBUG=True
# Uncomment to enable test credentials mode
# - MOCK_DATADOG_CLIENT=True
volumes:
- ./backend/monitoring/:/app/monitoring

View file

@ -1,14 +1,18 @@
/** @type {import('next').NextConfig} */
module.exports = {
transpilePackages: ["@saleor/apps-shared"],
reactStrictMode: true,
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
rewrites() {
/**
* For dev/preview Next.js can work as a proxy and redirect unknown paths to provided backend address
*
* In production, when env is not provided, frontend will call its relative path and reverse proxy will do the rest
*/
const backendPath = process.env.MONITORING_APP_API_URL ?? "";
const backendPath = process.env.MONITORING_APP_API_URL;
if(!backendPath) {
throw new Error('Please set MONITORING_APP_API_URL variable')
}
return {
fallback: [

View file

@ -16,7 +16,9 @@
"@material-ui/lab": "4.0.0-alpha.61",
"@saleor/app-sdk": "0.40.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/macaw-ui": "^0.7.2",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.95",
"@saleor/react-hook-form-macaw": "workspace:*",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.0",
"clsx": "^1.2.1",
@ -28,7 +30,7 @@
"pino-pretty": "^10.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.42.1",
"react-hook-form": "^7.43.9",
"urql": "^4.0.4",
"vite": "4.3.9",
"vitest": "0.31.3"
@ -50,8 +52,5 @@
"eslint-config-saleor": "workspace:*",
"typescript": "5.1.3"
},
"private": true,
"saleor": {
"schemaVersion": "3.10"
}
"private": true
}

View file

@ -9,9 +9,9 @@ export const API_KEYS_LINKS: { [key in DatadogSite]: string } = {
};
export const DATADOG_SITES_LINKS: { [key in DatadogSite]: string } = {
[DatadogSite.Us1]: "datadoghq.com",
[DatadogSite.Us3]: "us3.datadoghq.com",
[DatadogSite.Us5]: "us5.datadoghq.com",
[DatadogSite.Eu1]: "datadoghq.eu",
[DatadogSite.Us1Fed]: "ddog-gov.com",
[DatadogSite.Us1]: "https://datadoghq.com",
[DatadogSite.Us3]: "https://us3.datadoghq.com",
[DatadogSite.Us5]: "https://us5.datadoghq.com",
[DatadogSite.Eu1]: "https://datadoghq.eu",
[DatadogSite.Us1Fed]: "https://ddog-gov.com",
};

View file

@ -1,7 +1,49 @@
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { createGraphQLClient } from "@saleor/apps-shared";
import { PropsWithChildren } from "react";
import { Provider } from "urql";
import { cacheExchange, createClient as urqlCreateClient, fetchExchange, Provider } from "urql";
import { authExchange } from "@urql/exchange-auth";
/**
* Local client creation. Contrary to other apps, Monitoring frontend doesnt contact Saleor directly,
* but calls Python-based service which also provides graphQL endpoint.
*
* App calls /graphql/ which is rewritten MONITORING_APP_API_URL. See next.config.js
*/
const createGraphQLClient = ({
graphql,
saleorApiUrl,
token,
}: {
graphql: string;
saleorApiUrl: string;
token: string;
}) => {
return urqlCreateClient({
url: graphql,
exchanges: [
cacheExchange,
authExchange(async (utils) => {
return {
addAuthToOperation(operation) {
const headers: Record<string, string> = token
? {
"Authorization-Bearer": token,
"Saleor-Api-Url": saleorApiUrl,
}
: {};
return utils.appendHeaders(operation, headers);
},
didAuthError(error) {
return error.graphQLErrors.some((e) => e.extensions?.code === "FORBIDDEN");
},
async refreshAuth() {},
};
}),
fetchExchange,
],
});
};
export function GraphQLProvider(props: PropsWithChildren<{}>) {
const { appBridgeState } = useAppBridge();
@ -11,6 +53,7 @@ export function GraphQLProvider(props: PropsWithChildren<{}>) {
const client = createGraphQLClient({
saleorApiUrl,
token,
graphql: "/graphql/",
});
return <Provider value={client} {...props} />;

View file

@ -1,7 +0,0 @@
export function isInIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}

View file

@ -1,19 +0,0 @@
import React, { PropsWithChildren } from "react";
import dynamic from "next/dynamic";
const Wrapper = (props: PropsWithChildren<{}>) => <React.Fragment>{props.children}</React.Fragment>;
/**
* Saleor App can be rendered only as a Saleor Dashboard iframe.
* All content is rendered after Dashboard exchanges auth with the app.
* Hence, there is no reason to render app server side.
*
* This component forces app to work in SPA-mode. It simplifies browser-only code and reduces need
* of using dynamic() calls
*
* You can use this wrapper selectively for some pages or remove it completely.
* It doesn't affect Saleor communication, but may cause problems with some client-only code.
*/
export const NoSSRWrapper = dynamic(() => Promise.resolve(Wrapper), {
ssr: false,
});

View file

@ -1,48 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { AppBridgeState } from "@saleor/app-sdk/app-bridge";
import { render, waitFor } from "@testing-library/react";
import { ThemeSynchronizer } from "./theme-synchronizer";
const appBridgeState: AppBridgeState = {
ready: true,
token: "token",
domain: "some-domain.saleor.cloud",
theme: "dark",
path: "/",
locale: "en",
id: "app-id",
saleorApiUrl: "https://some-domain.saleor.cloud/graphql/",
};
const mockThemeChange = vi.fn();
vi.mock("@saleor/app-sdk/app-bridge", () => {
return {
useAppBridge() {
return {
appBridgeState: appBridgeState,
};
},
};
});
vi.mock("@saleor/macaw-ui", () => {
return {
useTheme() {
return {
setTheme: mockThemeChange,
themeType: "light",
};
},
};
});
describe("ThemeSynchronizer", () => {
it("Updates MacawUI theme when AppBridgeState theme changes", () => {
render(<ThemeSynchronizer />);
return waitFor(() => {
expect(mockThemeChange).toHaveBeenCalledWith("dark");
});
});
});

View file

@ -1,33 +1,25 @@
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { useTheme } from "@saleor/macaw-ui";
import { memo, useEffect } from "react";
import { useTheme } from "@saleor/macaw-ui/next";
import { useEffect } from "react";
/**
* Macaw-ui stores its theme mode in memory and local storage. To synchronize App with Dashboard,
* Macaw must be informed about this change from AppBridge.
*
* If you are not using Macaw, you can remove this.
*/
function _ThemeSynchronizer() {
// todo move to shared
export function ThemeSynchronizer() {
const { appBridgeState } = useAppBridge();
const { setTheme, themeType } = useTheme();
const { setTheme } = useTheme();
useEffect(() => {
if (!setTheme || !appBridgeState?.theme) {
return;
}
if (themeType !== appBridgeState?.theme) {
setTheme(appBridgeState.theme);
/**
* Hack to fix macaw, which is going into infinite loop on light mode (probably de-sync local storage with react state)
* TODO Fix me when Macaw 2.0 is shipped
*/
window.localStorage.setItem("macaw-ui-theme", appBridgeState.theme);
if (appBridgeState.theme === "light") {
setTheme("defaultLight");
}
}, [appBridgeState?.theme, setTheme, themeType]);
if (appBridgeState.theme === "dark") {
setTheme("defaultDark");
}
}, [appBridgeState?.theme, setTheme]);
return null;
}
export const ThemeSynchronizer = memo(_ThemeSynchronizer);

View file

@ -1,88 +1,30 @@
import "../styles/globals.css";
import { Theme } from "@material-ui/core/styles";
import "@saleor/macaw-ui/next/style";
import "../style.css";
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import {
dark,
light,
SaleorThemeColors,
ThemeProvider as MacawUIThemeProvider,
} from "@saleor/macaw-ui";
import React, { PropsWithChildren, useEffect } from "react";
import React from "react";
import { AppProps } from "next/app";
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
import { NoSSRWrapper } from "../lib/no-ssr-wrapper";
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
import { NoSSRWrapper } from "@saleor/apps-shared";
import { GraphQLProvider } from "../graphql-provider";
const themeOverrides: Partial<Theme> = {
/**
* You can override MacawUI theme here
*/
};
type PalettesOverride = Record<"light" | "dark", SaleorThemeColors>;
/**
* Temporary override of colors, to match new dashboard palette.
* Long term this will be replaced with Macaw UI 2.x with up to date design tokens
*/
const palettes: PalettesOverride = {
light: {
...light,
background: {
default: "#fff",
paper: "#fff",
},
},
dark: {
...dark,
background: {
default: "hsla(211, 42%, 14%, 1)",
paper: "hsla(211, 42%, 14%, 1)",
},
},
};
/**
* Ensure instance is a singleton.
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
*/
const appBridgeInstance =
typeof window !== "undefined"
? new AppBridge({
initialTheme: "light",
})
: undefined;
/**
* That's a hack required by Macaw-UI incompatibility with React@18
*/
const ThemeProvider = MacawUIThemeProvider as React.FC<
PropsWithChildren<{ overrides?: Partial<Theme>; ssr: boolean; palettes: PalettesOverride }>
>;
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
function NextApp({ Component, pageProps }: AppProps) {
/**
* Configure JSS (used by MacawUI) for SSR. If Macaw is not used, can be removed.
*/
useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles?.parentElement?.removeChild(jssStyles);
}
}, []);
return (
<NoSSRWrapper>
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
<GraphQLProvider>
<ThemeProvider overrides={themeOverrides} ssr palettes={palettes}>
<ThemeProvider>
<ThemeSynchronizer />
<RoutePropagator />
<Component {...pageProps} />
<Box padding={4}>
<Component {...pageProps} />
</Box>
</ThemeProvider>
</GraphQLProvider>
</AppBridgeProvider>

View file

@ -1,16 +1,13 @@
import { NextPage } from "next";
import { AppColumnsLayout } from "../../ui/app-columns-layout";
import React, { useEffect } from "react";
import { IntegrationsList } from "../../ui/providers-list";
import { NoProvidersConfigured } from "../../ui/no-providers-configured";
import { useRouter } from "next/router";
import { DatadogConfig } from "../../ui/datadog/datadog-config";
import { DatadogSite, useConfigQuery } from "../../../generated/graphql";
import { LinearProgress, Link, Typography } from "@material-ui/core";
import { Section } from "../../ui/sections";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import { Done, Error } from "@material-ui/icons";
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { DATADOG_SITES_LINKS } from "../../datadog-urls";
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import { Breadcrumbs, TextLink } from "@saleor/apps-ui";
const useActiveProvider = () => {
const router = useRouter();
@ -20,13 +17,15 @@ const useActiveProvider = () => {
return selectedProvider ?? null;
};
const Content = () => {
const ConfigurationPage = () => {
const [configuration, fetchConfiguration] = useConfigQuery();
const { appBridge } = useAppBridge();
const datadogCredentials = configuration.data?.integrations.datadog?.credentials;
const datadogError = configuration.data?.integrations.datadog?.error;
const { push } = useRouter();
useEffect(() => {
fetchConfiguration();
}, [fetchConfiguration]);
@ -34,7 +33,7 @@ const Content = () => {
const selectedProvider = useActiveProvider();
if (configuration.fetching && !configuration.data) {
return <LinearProgress />;
return <Text>Loading...</Text>;
}
if (selectedProvider === "datadog") {
@ -50,57 +49,48 @@ const Content = () => {
const site = configuration.data?.integrations.datadog?.credentials.site ?? DatadogSite.Us1;
return (
<Section>
<Typography paragraph variant="h3">
<Done style={{ verticalAlign: "middle", marginRight: 10 }} />
<Box display={"flex"} gap={4} flexDirection={"column"}>
<Text as={"h1"} variant="heading">
App configured
</Typography>
<Typography paragraph>
</Text>
<Text as={"p"}>
Visit{" "}
<Link
href="https://app.datadoghq.com/"
onClick={(e) => {
e.preventDefault();
appBridge?.dispatch(
actions.Redirect({
to: DATADOG_SITES_LINKS[site],
newContext: true,
})
);
}}
>
<TextLink newTab href={DATADOG_SITES_LINKS[site] ?? "https://app.datadoghq.com/"}>
Datadog
</Link>{" "}
</TextLink>{" "}
to access your logs
</Typography>
</Section>
</Text>
<Button
onClick={() => {
push("/configuration/datadog");
}}
>
Edit configuration
</Button>
</Box>
);
}
if (datadogError) {
return (
<Section>
<Typography paragraph variant="h3">
<Error style={{ verticalAlign: "middle", marginRight: 10 }} />
<Box>
<Text variant="heading" as={"h1"}>
Configuration Error
</Typography>
<Typography>{datadogError}</Typography>
</Section>
</Text>
<Text color={"textCriticalDefault"}>{datadogError}</Text>
<Button
marginTop={8}
onClick={() => {
push("/configuration/datadog");
}}
>
Edit configuration
</Button>
</Box>
);
}
return null;
};
const ConfigurationPage: NextPage = () => {
const selectedProvider = useActiveProvider();
return (
<AppColumnsLayout>
<IntegrationsList activeProvider={selectedProvider} />
<Content />
</AppColumnsLayout>
);
};
export default ConfigurationPage;

View file

@ -1,16 +1,16 @@
import { NextPage } from "next";
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { MouseEventHandler, useEffect, useState } from "react";
import { LinearProgress, Link } from "@material-ui/core";
import { isInIframe } from "../lib/is-in-iframe";
import { useEffect } from "react";
import { isInIframe } from "@saleor/apps-shared";
import { useRouter } from "next/router";
import { Box, Text } from "@saleor/macaw-ui/next";
/**
* This is page publicly accessible from your app.
* You should probably remove it.
*/
const IndexPage: NextPage = () => {
const { appBridgeState, appBridge } = useAppBridge();
const { appBridgeState } = useAppBridge();
const { replace } = useRouter();
useEffect(() => {
@ -20,14 +20,16 @@ const IndexPage: NextPage = () => {
}, [appBridgeState?.ready, replace]);
if (isInIframe()) {
return <LinearProgress />;
return <Text>Loading...</Text>;
}
return (
<div>
<h1>Saleor Monitoring</h1>
<p>Install App in Saleor to use it</p>
</div>
<Box>
<Text variant="heading" as="h1">
Saleor Monitoring
</Text>
<Text>Install App in Saleor Dashboard to use it</Text>
</Box>
);
};

View file

@ -0,0 +1,3 @@
a {
text-decoration: none;
}

View file

@ -1,14 +0,0 @@
body {
font-family: Inter, -apple-system, "system-ui", "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell,
"Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
padding: 32px;
}
code {
border-radius: 5px;
display: inline-block;
margin-top: 10px;
padding: 0.75rem;
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}

View file

@ -1,21 +0,0 @@
import { makeStyles } from "@saleor/macaw-ui";
import { PropsWithChildren } from "react";
const useStyles = makeStyles({
root: {
display: "grid",
gridTemplateColumns: "280px auto 280px",
alignItems: "start",
gap: 32,
maxWidth: 1180,
margin: "0 auto",
},
});
type Props = PropsWithChildren<{}>;
export function AppColumnsLayout({ children }: Props) {
const styles = useStyles();
return <div className={styles.root}>{children}</div>;
}

View file

@ -1,54 +1,24 @@
import { Controller, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import {
DataDogCredentialsInput,
DatadogSite,
useConfigQuery,
Mutation,
useUpdateCredentialsMutation,
useDeleteDatadogCredentialsMutation,
useUpdateCredentialsMutation,
} from "../../../generated/graphql";
import { Section } from "../sections";
import {
InputLabel,
LinearProgress,
MenuItem,
Select,
TextField,
Typography,
Checkbox,
FormGroup,
FormControlLabel,
Link,
} from "@material-ui/core";
import React, { useEffect, useState } from "react";
import { Button, makeStyles, Backlink, IconButton } from "@saleor/macaw-ui";
import { ArrowLeftIcon, Box, Button, Text } from "@saleor/macaw-ui/next";
import React, { useEffect } from "react";
import Image from "next/image";
import DatadogLogo from "../../assets/datadog/dd_logo_h_rgb.svg";
import { gql, useMutation } from "urql";
import { gql } from "urql";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import { ArrowBack } from "@material-ui/icons";
import { useRouter } from "next/router";
import { API_KEYS_LINKS } from "../../datadog-urls";
import { useDashboardNotification } from "@saleor/apps-shared";
const useStyles = makeStyles({
form: {
marginTop: 50,
display: "grid",
gridAutoFlow: "row",
gap: 30,
},
header: {
display: "flex",
justifyContent: "space-between",
alignItems: "center",
},
headline: {
marginRight: "auto",
marginLeft: 10,
},
});
import { Input, Select, Toggle } from "@saleor/react-hook-form-macaw";
import { Breadcrumbs } from "@saleor/apps-ui";
gql`
query Config {
@ -110,7 +80,7 @@ const ApiKeyHelperText = ({ site }: { site: DatadogSite }) => {
return (
<span>
Get one{" "}
<Link
<a
href={url}
onClick={(e) => {
e.preventDefault();
@ -123,13 +93,12 @@ const ApiKeyHelperText = ({ site }: { site: DatadogSite }) => {
}}
>
here
</Link>
</a>
</span>
);
};
export const DatadogConfig = () => {
const styles = useStyles();
const [queryData, fetchConfig] = useConfigQuery();
const [, mutateCredentials] = useUpdateCredentialsMutation();
const [, deleteCredentials] = useDeleteDatadogCredentialsMutation();
@ -175,114 +144,108 @@ export const DatadogConfig = () => {
}, [queryData.data, setValue]);
if (queryData.fetching && !queryData.data) {
return <LinearProgress />;
return <Text>Loading</Text>;
}
return (
<Section>
<div className={styles.header}>
<IconButton
onClick={() => {
router.push("/configuration");
}}
variant="secondary"
>
<ArrowBack />
</IconButton>
<Typography className={styles.headline} variant="h3">
Configuration
</Typography>
<Image width={100} src={DatadogLogo} alt="DataDog" />
</div>
<form
className={styles.form}
onSubmit={handleSubmit((values) => {
return mutateCredentials({
input: {
active: values.active,
credentials: {
apiKey: values.apiKey,
site: values.site,
<Box>
<Breadcrumbs>
<Breadcrumbs.Item href={"/configuration"}>Configuration</Breadcrumbs.Item>
<Breadcrumbs.Item>DataDog</Breadcrumbs.Item>
</Breadcrumbs>
<Box marginTop={8} display={"grid"} __gridTemplateColumns={"400px auto"} gap={8}>
<Box display={"flex"} gap={4} flexDirection={"column"}>
<Text variant={"heading"} as={"h1"}>
Configuration
</Text>
<Image width={100} src={DatadogLogo} alt="DataDog" />
<Text as={"p"}>
Configure your Datadog integration to send your Saleor metrics to Datadog.
</Text>
</Box>
<Box
display={"flex"}
gap={4}
flexDirection={"column"}
as={"form"}
borderColor={"neutralHighlight"}
borderWidth={1}
borderStyle={"solid"}
borderRadius={4}
padding={8}
onSubmit={handleSubmit((values) => {
return mutateCredentials({
input: {
active: values.active,
credentials: {
apiKey: values.apiKey,
site: values.site,
},
},
},
}).then((res) => {
const updatedConfig = res.data?.updateDatadogConfig.datadog;
const errors = res.data?.updateDatadogConfig.errors;
}).then((res) => {
const updatedConfig = res.data?.updateDatadogConfig.datadog;
const errors = res.data?.updateDatadogConfig.errors;
if (updatedConfig) {
setValue("active", updatedConfig.active);
setValue("apiKey", buildMaskedKey(updatedConfig.credentials.apiKeyLast4));
setValue("site", updatedConfig.credentials.site);
if (updatedConfig) {
setValue("active", updatedConfig.active);
setValue("apiKey", buildMaskedKey(updatedConfig.credentials.apiKeyLast4));
setValue("site", updatedConfig.credentials.site);
notifySuccess("Configuration updated", "Successfully updated Datadog settings");
}
notifySuccess("Configuration updated", "Successfully updated Datadog settings");
}
if (errors?.length) {
notifyError("Error configuring Datadog", errors[0].message);
}
});
})}
>
<div>
<Controller
render={({ field }) => {
return (
<FormGroup row>
<FormControlLabel
control={<Checkbox checked={field.value} {...field} />}
label="Enabled"
/>
</FormGroup>
);
}}
name="active"
control={control}
/>
<InputLabel id="datadog-site-label">Datadog Site</InputLabel>
<Select
defaultValue={DatadogSite.Us1}
fullWidth
labelId="datadog-site-label"
{...register("site")}
>
{Object.values(DatadogSite).map((v) => (
<MenuItem value={v} key={v}>
{v}
</MenuItem>
))}
</Select>
</div>
<TextField
fullWidth
variant="standard"
label="Api Key"
defaultValue=""
helperText={<ApiKeyHelperText site={activeSite} />}
{...register("apiKey")}
/>
{queryData.data?.integrations.datadog?.error && (
<Typography color="error">{queryData.data?.integrations.datadog?.error}</Typography>
)}
<Button type="submit" variant="primary" fullWidth>
Save configuration
</Button>
<Button
type="reset"
variant="secondary"
fullWidth
onClick={(e) => {
e.preventDefault();
deleteCredentials({}).then(() => {
fetchConfig();
reset();
notifySuccess("Configuration updated", "Successfully deleted Datadog settings");
if (errors?.length) {
notifyError("Error configuring Datadog", errors[0].message);
}
});
}}
})}
>
Delete configuration
</Button>
</form>
</Section>
<Box as={"label"} display={"flex"} gap={2}>
<Toggle control={control} name={"active"} />
<Text variant={"bodyEmp"}>Active</Text>
</Box>
<Select
label={"Datadog Site"}
options={Object.values(DatadogSite).map((v) => ({
label: v,
value: v,
}))}
control={control}
name={"site"}
/>
<Input
label="API Key"
defaultValue=""
helperText={<ApiKeyHelperText site={activeSite} />}
control={control}
name={"apiKey"}
/>
{queryData.data?.integrations.datadog?.error && (
<Text color={"textCriticalDefault"}>{queryData.data?.integrations.datadog?.error}</Text>
)}
<Box display={"flex"} gap={2} marginTop={8} justifyContent={"flex-end"}>
<Button
variant={"tertiary"}
type="reset"
onClick={(e) => {
e.preventDefault();
deleteCredentials({}).then(() => {
fetchConfig();
reset();
notifySuccess("Configuration updated", "Successfully deleted Datadog settings");
router.push("/configuration");
});
}}
>
<Text color={"textCriticalDefault"}>Delete configuration</Text>
</Button>
<Button type="submit">Save configuration</Button>
</Box>
</Box>
</Box>
</Box>
);
};

View file

@ -1,13 +1,14 @@
import { Section } from "./sections";
import { Typography } from "@material-ui/core";
import { Box, Button, Text } from "@saleor/macaw-ui/next";
import Link from "next/link";
export const NoProvidersConfigured = () => (
<Section>
<Typography paragraph variant="h3">
<Box display={"flex"} gap={4} flexDirection={"column"}>
<Text as={"h1"} variant="heading">
No providers configured
</Typography>
<Typography paragraph>
Chose one of providers on the left and configure it to use the app
</Typography>
</Section>
</Text>
<Text as={"p"}>You need to configure Datadog to enable the app</Text>
<Link href={"/configuration/datadog"}>
<Button>Configure Datadog</Button>
</Link>
</Box>
);

View file

@ -1,99 +0,0 @@
import { makeStyles } from "@saleor/macaw-ui";
import Image from "next/image";
import DatadogLogo from "../assets/datadog/dd_logo_h_rgb.svg";
import NewRelicLogo from "../assets/new-relic/new_relic_logo_horizontal.svg";
import LogzLogo from "../assets/logzio/1584985593-blue-horizontal.svg";
import React from "react";
import { Section } from "./sections";
import { Typography } from "@material-ui/core";
import clsx from "clsx";
import { useRouter } from "next/router";
import { Done, Error } from "@material-ui/icons";
import { useConfigQuery } from "../../generated/graphql";
const useStyles = makeStyles((theme) => {
return {
item: {
cursor: "pointer",
display: "flex",
marginBottom: 20,
padding: "10px",
justifyContent: "space-between",
border: "1px solid transparent",
},
disabledItem: {
filter: "grayscale(1)",
opacity: 0.7,
pointerEvents: "none",
marginBottom: 20,
padding: "10px",
},
selected: {
border: `1px solid ${theme.palette.divider} !important`,
borderRadius: 4,
},
list: {
margin: 0,
padding: 0,
listStyle: "none",
},
};
});
type Props = {
activeProvider: "datadog" | string | null;
};
export const IntegrationsList = ({ activeProvider }: Props) => {
const styles = useStyles();
const router = useRouter();
const [queryData] = useConfigQuery();
const isDatadogConfigured = queryData.data?.integrations.datadog?.credentials;
const isDatadogError = queryData.data?.integrations.datadog?.error;
return (
<Section>
<ul className={styles.list}>
<li
onClick={() => {
router.push("/configuration/datadog");
}}
className={clsx(styles.item, {
[styles.selected]: activeProvider === "datadog",
})}
>
<div>
<Image alt="Datadog" width={100} src={DatadogLogo} />
</div>
{isDatadogConfigured && !isDatadogError && (
<div>
<Done color="secondary" />
</div>
)}
{isDatadogError && (
<div>
<Error color="error" />
</div>
)}
</li>
<li className={styles.disabledItem}>
<div>
<Typography variant="caption">Coming Soon</Typography>
</div>
</li>
<li className={styles.disabledItem}>
<div>
<Image alt="New Relic" width={100} src={NewRelicLogo} />
</div>
</li>
<li className={styles.disabledItem}>
<div>
<Image alt="Logz.io" width={100} src={LogzLogo} />
</div>
</li>
</ul>
</Section>
);
};

View file

@ -1,6 +0,0 @@
import { Paper, PaperProps } from "@material-ui/core";
import React from "react";
export const Section = (props: PaperProps) => (
<Paper {...props} elevation={0} style={{ padding: 20 }} />
);

View file

@ -17,8 +17,8 @@
"@saleor/app-sdk": "0.40.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*",
"@saleor/react-hook-form-macaw": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.95",
"@saleor/react-hook-form-macaw": "workspace:*",
"@sentry/nextjs": "7.55.2",
"@urql/exchange-auth": "^2.1.4",
"clsx": "^1.2.1",

View file

@ -886,9 +886,15 @@ importers:
'@saleor/apps-shared':
specifier: workspace:*
version: link:../../packages/shared
'@saleor/apps-ui':
specifier: workspace:*
version: link:../../packages/ui
'@saleor/macaw-ui':
specifier: ^0.7.2
version: 0.7.3(@material-ui/core@4.12.4)(@material-ui/icons@4.11.3)(@material-ui/lab@4.0.0-alpha.61)(@types/react@18.2.5)(react-dom@18.2.0)(react-helmet@6.1.0)(react@18.2.0)
specifier: 0.8.0-pre.95
version: 0.8.0-pre.95(@types/react-dom@18.2.5)(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
'@saleor/react-hook-form-macaw':
specifier: workspace:*
version: link:../../packages/react-hook-form-macaw
'@urql/exchange-auth':
specifier: ^2.1.4
version: 2.1.4(graphql@16.6.0)
@ -923,7 +929,7 @@ importers:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-hook-form:
specifier: ^7.42.1
specifier: ^7.43.9
version: 7.44.3(react@18.2.0)
urql:
specifier: ^4.0.4