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:
parent
f6ff907cfd
commit
3bd7e3f05d
24 changed files with 267 additions and 526 deletions
5
.changeset/twenty-apricots-own.md
Normal file
5
.changeset/twenty-apricots-own.md
Normal 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.
|
1
apps/monitoring/.env.example
Normal file
1
apps/monitoring/.env.example
Normal file
|
@ -0,0 +1 @@
|
||||||
|
MONITORING_APP_API_URL=
|
|
@ -55,6 +55,8 @@ To use Graphql Playground, `Monitoring` app needs to be installed in Saleor, and
|
||||||
|
|
||||||
### Testing DataDog integration
|
### Testing DataDog integration
|
||||||
|
|
||||||
|
Set `MOCK_DATADOG_CLIENT` env to `True`
|
||||||
|
|
||||||
Use these credentials sets to test DataDog integration:
|
Use these credentials sets to test DataDog integration:
|
||||||
|
|
||||||
Working credentials:
|
Working credentials:
|
||||||
|
|
|
@ -12,5 +12,7 @@ services:
|
||||||
- "5001:80"
|
- "5001:80"
|
||||||
environment:
|
environment:
|
||||||
- DEBUG=True
|
- DEBUG=True
|
||||||
|
# Uncomment to enable test credentials mode
|
||||||
|
# - MOCK_DATADOG_CLIENT=True
|
||||||
volumes:
|
volumes:
|
||||||
- ./backend/monitoring/:/app/monitoring
|
- ./backend/monitoring/:/app/monitoring
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transpilePackages: ["@saleor/apps-shared"],
|
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw"],
|
||||||
rewrites() {
|
rewrites() {
|
||||||
/**
|
/**
|
||||||
* For dev/preview Next.js can work as a proxy and redirect unknown paths to provided backend address
|
* 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
|
* 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 {
|
return {
|
||||||
fallback: [
|
fallback: [
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
"@material-ui/lab": "4.0.0-alpha.61",
|
"@material-ui/lab": "4.0.0-alpha.61",
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@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",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"@vitejs/plugin-react": "4.0.0",
|
"@vitejs/plugin-react": "4.0.0",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
@ -28,7 +30,7 @@
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-hook-form": "^7.42.1",
|
"react-hook-form": "^7.43.9",
|
||||||
"urql": "^4.0.4",
|
"urql": "^4.0.4",
|
||||||
"vite": "4.3.9",
|
"vite": "4.3.9",
|
||||||
"vitest": "0.31.3"
|
"vitest": "0.31.3"
|
||||||
|
@ -50,8 +52,5 @@
|
||||||
"eslint-config-saleor": "workspace:*",
|
"eslint-config-saleor": "workspace:*",
|
||||||
"typescript": "5.1.3"
|
"typescript": "5.1.3"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true
|
||||||
"saleor": {
|
|
||||||
"schemaVersion": "3.10"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,9 @@ export const API_KEYS_LINKS: { [key in DatadogSite]: string } = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DATADOG_SITES_LINKS: { [key in DatadogSite]: string } = {
|
export const DATADOG_SITES_LINKS: { [key in DatadogSite]: string } = {
|
||||||
[DatadogSite.Us1]: "datadoghq.com",
|
[DatadogSite.Us1]: "https://datadoghq.com",
|
||||||
[DatadogSite.Us3]: "us3.datadoghq.com",
|
[DatadogSite.Us3]: "https://us3.datadoghq.com",
|
||||||
[DatadogSite.Us5]: "us5.datadoghq.com",
|
[DatadogSite.Us5]: "https://us5.datadoghq.com",
|
||||||
[DatadogSite.Eu1]: "datadoghq.eu",
|
[DatadogSite.Eu1]: "https://datadoghq.eu",
|
||||||
[DatadogSite.Us1Fed]: "ddog-gov.com",
|
[DatadogSite.Us1Fed]: "https://ddog-gov.com",
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,49 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { createGraphQLClient } from "@saleor/apps-shared";
|
|
||||||
import { PropsWithChildren } from "react";
|
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<{}>) {
|
export function GraphQLProvider(props: PropsWithChildren<{}>) {
|
||||||
const { appBridgeState } = useAppBridge();
|
const { appBridgeState } = useAppBridge();
|
||||||
|
@ -11,6 +53,7 @@ export function GraphQLProvider(props: PropsWithChildren<{}>) {
|
||||||
const client = createGraphQLClient({
|
const client = createGraphQLClient({
|
||||||
saleorApiUrl,
|
saleorApiUrl,
|
||||||
token,
|
token,
|
||||||
|
graphql: "/graphql/",
|
||||||
});
|
});
|
||||||
|
|
||||||
return <Provider value={client} {...props} />;
|
return <Provider value={client} {...props} />;
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
export function isInIframe() {
|
|
||||||
try {
|
|
||||||
return window.self !== window.top;
|
|
||||||
} catch (e) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
});
|
|
|
@ -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");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,33 +1,25 @@
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { useTheme } from "@saleor/macaw-ui";
|
import { useTheme } from "@saleor/macaw-ui/next";
|
||||||
import { memo, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
/**
|
// todo move to shared
|
||||||
* Macaw-ui stores its theme mode in memory and local storage. To synchronize App with Dashboard,
|
export function ThemeSynchronizer() {
|
||||||
* Macaw must be informed about this change from AppBridge.
|
|
||||||
*
|
|
||||||
* If you are not using Macaw, you can remove this.
|
|
||||||
*/
|
|
||||||
function _ThemeSynchronizer() {
|
|
||||||
const { appBridgeState } = useAppBridge();
|
const { appBridgeState } = useAppBridge();
|
||||||
const { setTheme, themeType } = useTheme();
|
const { setTheme } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!setTheme || !appBridgeState?.theme) {
|
if (!setTheme || !appBridgeState?.theme) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (themeType !== appBridgeState?.theme) {
|
if (appBridgeState.theme === "light") {
|
||||||
setTheme(appBridgeState.theme);
|
setTheme("defaultLight");
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
}
|
||||||
}, [appBridgeState?.theme, setTheme, themeType]);
|
|
||||||
|
if (appBridgeState.theme === "dark") {
|
||||||
|
setTheme("defaultDark");
|
||||||
|
}
|
||||||
|
}, [appBridgeState?.theme, setTheme]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ThemeSynchronizer = memo(_ThemeSynchronizer);
|
|
||||||
|
|
|
@ -1,88 +1,30 @@
|
||||||
import "../styles/globals.css";
|
import "@saleor/macaw-ui/next/style";
|
||||||
|
import "../style.css";
|
||||||
import { Theme } from "@material-ui/core/styles";
|
|
||||||
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
|
||||||
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
import React from "react";
|
||||||
import {
|
|
||||||
dark,
|
|
||||||
light,
|
|
||||||
SaleorThemeColors,
|
|
||||||
ThemeProvider as MacawUIThemeProvider,
|
|
||||||
} from "@saleor/macaw-ui";
|
|
||||||
import React, { PropsWithChildren, useEffect } from "react";
|
|
||||||
import { AppProps } from "next/app";
|
import { AppProps } from "next/app";
|
||||||
|
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
|
||||||
import { ThemeSynchronizer } from "../lib/theme-synchronizer";
|
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";
|
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.
|
* Ensure instance is a singleton.
|
||||||
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
|
|
||||||
*/
|
*/
|
||||||
const appBridgeInstance =
|
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
|
||||||
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 }>
|
|
||||||
>;
|
|
||||||
|
|
||||||
function NextApp({ Component, pageProps }: AppProps) {
|
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 (
|
return (
|
||||||
<NoSSRWrapper>
|
<NoSSRWrapper>
|
||||||
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
|
||||||
<GraphQLProvider>
|
<GraphQLProvider>
|
||||||
<ThemeProvider overrides={themeOverrides} ssr palettes={palettes}>
|
<ThemeProvider>
|
||||||
<ThemeSynchronizer />
|
<ThemeSynchronizer />
|
||||||
<RoutePropagator />
|
<RoutePropagator />
|
||||||
|
<Box padding={4}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</GraphQLProvider>
|
</GraphQLProvider>
|
||||||
</AppBridgeProvider>
|
</AppBridgeProvider>
|
||||||
|
|
|
@ -1,16 +1,13 @@
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { AppColumnsLayout } from "../../ui/app-columns-layout";
|
|
||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { IntegrationsList } from "../../ui/providers-list";
|
|
||||||
import { NoProvidersConfigured } from "../../ui/no-providers-configured";
|
import { NoProvidersConfigured } from "../../ui/no-providers-configured";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { DatadogConfig } from "../../ui/datadog/datadog-config";
|
import { DatadogConfig } from "../../ui/datadog/datadog-config";
|
||||||
import { DatadogSite, useConfigQuery } from "../../../generated/graphql";
|
import { DatadogSite, useConfigQuery } from "../../../generated/graphql";
|
||||||
import { LinearProgress, Link, Typography } from "@material-ui/core";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { Section } from "../../ui/sections";
|
|
||||||
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
|
||||||
import { Done, Error } from "@material-ui/icons";
|
|
||||||
import { DATADOG_SITES_LINKS } from "../../datadog-urls";
|
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 useActiveProvider = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -20,13 +17,15 @@ const useActiveProvider = () => {
|
||||||
return selectedProvider ?? null;
|
return selectedProvider ?? null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Content = () => {
|
const ConfigurationPage = () => {
|
||||||
const [configuration, fetchConfiguration] = useConfigQuery();
|
const [configuration, fetchConfiguration] = useConfigQuery();
|
||||||
const { appBridge } = useAppBridge();
|
const { appBridge } = useAppBridge();
|
||||||
|
|
||||||
const datadogCredentials = configuration.data?.integrations.datadog?.credentials;
|
const datadogCredentials = configuration.data?.integrations.datadog?.credentials;
|
||||||
const datadogError = configuration.data?.integrations.datadog?.error;
|
const datadogError = configuration.data?.integrations.datadog?.error;
|
||||||
|
|
||||||
|
const { push } = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchConfiguration();
|
fetchConfiguration();
|
||||||
}, [fetchConfiguration]);
|
}, [fetchConfiguration]);
|
||||||
|
@ -34,7 +33,7 @@ const Content = () => {
|
||||||
const selectedProvider = useActiveProvider();
|
const selectedProvider = useActiveProvider();
|
||||||
|
|
||||||
if (configuration.fetching && !configuration.data) {
|
if (configuration.fetching && !configuration.data) {
|
||||||
return <LinearProgress />;
|
return <Text>Loading...</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedProvider === "datadog") {
|
if (selectedProvider === "datadog") {
|
||||||
|
@ -50,57 +49,48 @@ const Content = () => {
|
||||||
const site = configuration.data?.integrations.datadog?.credentials.site ?? DatadogSite.Us1;
|
const site = configuration.data?.integrations.datadog?.credentials.site ?? DatadogSite.Us1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Box display={"flex"} gap={4} flexDirection={"column"}>
|
||||||
<Typography paragraph variant="h3">
|
<Text as={"h1"} variant="heading">
|
||||||
<Done style={{ verticalAlign: "middle", marginRight: 10 }} />
|
|
||||||
App configured
|
App configured
|
||||||
</Typography>
|
</Text>
|
||||||
<Typography paragraph>
|
<Text as={"p"}>
|
||||||
Visit{" "}
|
Visit{" "}
|
||||||
<Link
|
<TextLink newTab href={DATADOG_SITES_LINKS[site] ?? "https://app.datadoghq.com/"}>
|
||||||
href="https://app.datadoghq.com/"
|
Datadog
|
||||||
onClick={(e) => {
|
</TextLink>{" "}
|
||||||
e.preventDefault();
|
to access your logs
|
||||||
appBridge?.dispatch(
|
</Text>
|
||||||
actions.Redirect({
|
<Button
|
||||||
to: DATADOG_SITES_LINKS[site],
|
onClick={() => {
|
||||||
newContext: true,
|
push("/configuration/datadog");
|
||||||
})
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Datadog
|
Edit configuration
|
||||||
</Link>{" "}
|
</Button>
|
||||||
to access your logs
|
</Box>
|
||||||
</Typography>
|
|
||||||
</Section>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datadogError) {
|
if (datadogError) {
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Box>
|
||||||
<Typography paragraph variant="h3">
|
<Text variant="heading" as={"h1"}>
|
||||||
<Error style={{ verticalAlign: "middle", marginRight: 10 }} />
|
|
||||||
Configuration Error
|
Configuration Error
|
||||||
</Typography>
|
</Text>
|
||||||
<Typography>{datadogError}</Typography>
|
<Text color={"textCriticalDefault"}>{datadogError}</Text>
|
||||||
</Section>
|
<Button
|
||||||
|
marginTop={8}
|
||||||
|
onClick={() => {
|
||||||
|
push("/configuration/datadog");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Edit configuration
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ConfigurationPage: NextPage = () => {
|
|
||||||
const selectedProvider = useActiveProvider();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AppColumnsLayout>
|
|
||||||
<IntegrationsList activeProvider={selectedProvider} />
|
|
||||||
<Content />
|
|
||||||
</AppColumnsLayout>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ConfigurationPage;
|
export default ConfigurationPage;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { NextPage } from "next";
|
import { NextPage } from "next";
|
||||||
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { MouseEventHandler, useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
import { LinearProgress, Link } from "@material-ui/core";
|
import { isInIframe } from "@saleor/apps-shared";
|
||||||
import { isInIframe } from "../lib/is-in-iframe";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is page publicly accessible from your app.
|
* This is page publicly accessible from your app.
|
||||||
* You should probably remove it.
|
* You should probably remove it.
|
||||||
*/
|
*/
|
||||||
const IndexPage: NextPage = () => {
|
const IndexPage: NextPage = () => {
|
||||||
const { appBridgeState, appBridge } = useAppBridge();
|
const { appBridgeState } = useAppBridge();
|
||||||
const { replace } = useRouter();
|
const { replace } = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -20,14 +20,16 @@ const IndexPage: NextPage = () => {
|
||||||
}, [appBridgeState?.ready, replace]);
|
}, [appBridgeState?.ready, replace]);
|
||||||
|
|
||||||
if (isInIframe()) {
|
if (isInIframe()) {
|
||||||
return <LinearProgress />;
|
return <Text>Loading...</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
<h1>Saleor Monitoring</h1>
|
<Text variant="heading" as="h1">
|
||||||
<p>Install App in Saleor to use it</p>
|
Saleor Monitoring
|
||||||
</div>
|
</Text>
|
||||||
|
<Text>Install App in Saleor Dashboard to use it</Text>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
3
apps/monitoring/src/style.css
Normal file
3
apps/monitoring/src/style.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
|
@ -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;
|
|
||||||
}
|
|
|
@ -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>;
|
|
||||||
}
|
|
|
@ -1,54 +1,24 @@
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
DataDogCredentialsInput,
|
DataDogCredentialsInput,
|
||||||
DatadogSite,
|
DatadogSite,
|
||||||
useConfigQuery,
|
useConfigQuery,
|
||||||
Mutation,
|
|
||||||
useUpdateCredentialsMutation,
|
|
||||||
useDeleteDatadogCredentialsMutation,
|
useDeleteDatadogCredentialsMutation,
|
||||||
|
useUpdateCredentialsMutation,
|
||||||
} from "../../../generated/graphql";
|
} from "../../../generated/graphql";
|
||||||
import { Section } from "../sections";
|
import { ArrowLeftIcon, Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import {
|
|
||||||
InputLabel,
|
import React, { useEffect } from "react";
|
||||||
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 Image from "next/image";
|
import Image from "next/image";
|
||||||
import DatadogLogo from "../../assets/datadog/dd_logo_h_rgb.svg";
|
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 { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
|
||||||
import { ArrowBack } from "@material-ui/icons";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { API_KEYS_LINKS } from "../../datadog-urls";
|
import { API_KEYS_LINKS } from "../../datadog-urls";
|
||||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||||
|
import { Input, Select, Toggle } from "@saleor/react-hook-form-macaw";
|
||||||
const useStyles = makeStyles({
|
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||||
form: {
|
|
||||||
marginTop: 50,
|
|
||||||
display: "grid",
|
|
||||||
gridAutoFlow: "row",
|
|
||||||
gap: 30,
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center",
|
|
||||||
},
|
|
||||||
headline: {
|
|
||||||
marginRight: "auto",
|
|
||||||
marginLeft: 10,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
gql`
|
gql`
|
||||||
query Config {
|
query Config {
|
||||||
|
@ -110,7 +80,7 @@ const ApiKeyHelperText = ({ site }: { site: DatadogSite }) => {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
Get one{" "}
|
Get one{" "}
|
||||||
<Link
|
<a
|
||||||
href={url}
|
href={url}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -123,13 +93,12 @@ const ApiKeyHelperText = ({ site }: { site: DatadogSite }) => {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
here
|
here
|
||||||
</Link>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DatadogConfig = () => {
|
export const DatadogConfig = () => {
|
||||||
const styles = useStyles();
|
|
||||||
const [queryData, fetchConfig] = useConfigQuery();
|
const [queryData, fetchConfig] = useConfigQuery();
|
||||||
const [, mutateCredentials] = useUpdateCredentialsMutation();
|
const [, mutateCredentials] = useUpdateCredentialsMutation();
|
||||||
const [, deleteCredentials] = useDeleteDatadogCredentialsMutation();
|
const [, deleteCredentials] = useDeleteDatadogCredentialsMutation();
|
||||||
|
@ -175,27 +144,36 @@ export const DatadogConfig = () => {
|
||||||
}, [queryData.data, setValue]);
|
}, [queryData.data, setValue]);
|
||||||
|
|
||||||
if (queryData.fetching && !queryData.data) {
|
if (queryData.fetching && !queryData.data) {
|
||||||
return <LinearProgress />;
|
return <Text>Loading</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Section>
|
<Box>
|
||||||
<div className={styles.header}>
|
<Breadcrumbs>
|
||||||
<IconButton
|
<Breadcrumbs.Item href={"/configuration"}>Configuration</Breadcrumbs.Item>
|
||||||
onClick={() => {
|
<Breadcrumbs.Item>DataDog</Breadcrumbs.Item>
|
||||||
router.push("/configuration");
|
</Breadcrumbs>
|
||||||
}}
|
|
||||||
variant="secondary"
|
<Box marginTop={8} display={"grid"} __gridTemplateColumns={"400px auto"} gap={8}>
|
||||||
>
|
<Box display={"flex"} gap={4} flexDirection={"column"}>
|
||||||
<ArrowBack />
|
<Text variant={"heading"} as={"h1"}>
|
||||||
</IconButton>
|
|
||||||
<Typography className={styles.headline} variant="h3">
|
|
||||||
Configuration
|
Configuration
|
||||||
</Typography>
|
</Text>
|
||||||
<Image width={100} src={DatadogLogo} alt="DataDog" />
|
<Image width={100} src={DatadogLogo} alt="DataDog" />
|
||||||
</div>
|
<Text as={"p"}>
|
||||||
<form
|
Configure your Datadog integration to send your Saleor metrics to Datadog.
|
||||||
className={styles.form}
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
display={"flex"}
|
||||||
|
gap={4}
|
||||||
|
flexDirection={"column"}
|
||||||
|
as={"form"}
|
||||||
|
borderColor={"neutralHighlight"}
|
||||||
|
borderWidth={1}
|
||||||
|
borderStyle={"solid"}
|
||||||
|
borderRadius={4}
|
||||||
|
padding={8}
|
||||||
onSubmit={handleSubmit((values) => {
|
onSubmit={handleSubmit((values) => {
|
||||||
return mutateCredentials({
|
return mutateCredentials({
|
||||||
input: {
|
input: {
|
||||||
|
@ -223,53 +201,34 @@ export const DatadogConfig = () => {
|
||||||
});
|
});
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<div>
|
<Box as={"label"} display={"flex"} gap={2}>
|
||||||
<Controller
|
<Toggle control={control} name={"active"} />
|
||||||
render={({ field }) => {
|
<Text variant={"bodyEmp"}>Active</Text>
|
||||||
return (
|
</Box>
|
||||||
<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
|
<Select
|
||||||
defaultValue={DatadogSite.Us1}
|
label={"Datadog Site"}
|
||||||
fullWidth
|
options={Object.values(DatadogSite).map((v) => ({
|
||||||
labelId="datadog-site-label"
|
label: v,
|
||||||
{...register("site")}
|
value: v,
|
||||||
>
|
}))}
|
||||||
{Object.values(DatadogSite).map((v) => (
|
control={control}
|
||||||
<MenuItem value={v} key={v}>
|
name={"site"}
|
||||||
{v}
|
/>
|
||||||
</MenuItem>
|
<Input
|
||||||
))}
|
label="API Key"
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
variant="standard"
|
|
||||||
label="Api Key"
|
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
helperText={<ApiKeyHelperText site={activeSite} />}
|
helperText={<ApiKeyHelperText site={activeSite} />}
|
||||||
{...register("apiKey")}
|
control={control}
|
||||||
|
name={"apiKey"}
|
||||||
/>
|
/>
|
||||||
{queryData.data?.integrations.datadog?.error && (
|
{queryData.data?.integrations.datadog?.error && (
|
||||||
<Typography color="error">{queryData.data?.integrations.datadog?.error}</Typography>
|
<Text color={"textCriticalDefault"}>{queryData.data?.integrations.datadog?.error}</Text>
|
||||||
)}
|
)}
|
||||||
<Button type="submit" variant="primary" fullWidth>
|
<Box display={"flex"} gap={2} marginTop={8} justifyContent={"flex-end"}>
|
||||||
Save configuration
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
|
variant={"tertiary"}
|
||||||
type="reset"
|
type="reset"
|
||||||
variant="secondary"
|
|
||||||
fullWidth
|
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
@ -277,12 +236,16 @@ export const DatadogConfig = () => {
|
||||||
fetchConfig();
|
fetchConfig();
|
||||||
reset();
|
reset();
|
||||||
notifySuccess("Configuration updated", "Successfully deleted Datadog settings");
|
notifySuccess("Configuration updated", "Successfully deleted Datadog settings");
|
||||||
|
router.push("/configuration");
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete configuration
|
<Text color={"textCriticalDefault"}>Delete configuration</Text>
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
<Button type="submit">Save configuration</Button>
|
||||||
</Section>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { Section } from "./sections";
|
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||||
import { Typography } from "@material-ui/core";
|
import Link from "next/link";
|
||||||
|
|
||||||
export const NoProvidersConfigured = () => (
|
export const NoProvidersConfigured = () => (
|
||||||
<Section>
|
<Box display={"flex"} gap={4} flexDirection={"column"}>
|
||||||
<Typography paragraph variant="h3">
|
<Text as={"h1"} variant="heading">
|
||||||
No providers configured
|
No providers configured
|
||||||
</Typography>
|
</Text>
|
||||||
<Typography paragraph>
|
<Text as={"p"}>You need to configure Datadog to enable the app</Text>
|
||||||
Chose one of providers on the left and configure it to use the app
|
<Link href={"/configuration/datadog"}>
|
||||||
</Typography>
|
<Button>Configure Datadog</Button>
|
||||||
</Section>
|
</Link>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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 }} />
|
|
||||||
);
|
|
|
@ -17,8 +17,8 @@
|
||||||
"@saleor/app-sdk": "0.40.1",
|
"@saleor/app-sdk": "0.40.1",
|
||||||
"@saleor/apps-shared": "workspace:*",
|
"@saleor/apps-shared": "workspace:*",
|
||||||
"@saleor/apps-ui": "workspace:*",
|
"@saleor/apps-ui": "workspace:*",
|
||||||
"@saleor/react-hook-form-macaw": "workspace:*",
|
|
||||||
"@saleor/macaw-ui": "0.8.0-pre.95",
|
"@saleor/macaw-ui": "0.8.0-pre.95",
|
||||||
|
"@saleor/react-hook-form-macaw": "workspace:*",
|
||||||
"@sentry/nextjs": "7.55.2",
|
"@sentry/nextjs": "7.55.2",
|
||||||
"@urql/exchange-auth": "^2.1.4",
|
"@urql/exchange-auth": "^2.1.4",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
|
|
@ -886,9 +886,15 @@ importers:
|
||||||
'@saleor/apps-shared':
|
'@saleor/apps-shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared
|
version: link:../../packages/shared
|
||||||
|
'@saleor/apps-ui':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/ui
|
||||||
'@saleor/macaw-ui':
|
'@saleor/macaw-ui':
|
||||||
specifier: ^0.7.2
|
specifier: 0.8.0-pre.95
|
||||||
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)
|
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':
|
'@urql/exchange-auth':
|
||||||
specifier: ^2.1.4
|
specifier: ^2.1.4
|
||||||
version: 2.1.4(graphql@16.6.0)
|
version: 2.1.4(graphql@16.6.0)
|
||||||
|
@ -923,7 +929,7 @@ importers:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0(react@18.2.0)
|
version: 18.2.0(react@18.2.0)
|
||||||
react-hook-form:
|
react-hook-form:
|
||||||
specifier: ^7.42.1
|
specifier: ^7.43.9
|
||||||
version: 7.44.3(react@18.2.0)
|
version: 7.44.3(react@18.2.0)
|
||||||
urql:
|
urql:
|
||||||
specifier: ^4.0.4
|
specifier: ^4.0.4
|
||||||
|
|
Loading…
Reference in a new issue