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

Admin link to warning #3542

Draft
wants to merge 10 commits into
base: next
Choose a base branch
from
10 changes: 10 additions & 0 deletions demo/admin/src/common/MasterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/Ma
import ProductsHandmadePage from "@src/products/ProductsPage";
import ProductTagsPage from "@src/products/tags/ProductTagsPage";
import { type ContentScope } from "@src/site-configs";
import { WarningsPage } from "@src/warnings/WarningsPage";
import { FormattedMessage } from "react-intl";
import { Redirect, type RouteComponentProps } from "react-router";

Expand Down Expand Up @@ -184,6 +185,15 @@ export const masterMenuData: MasterMenuData = [
},
requiredPermission: "pageTree",
},
{
type: "route",
primary: <FormattedMessage id="menu.warnings" defaultMessage="Warnings" />,
route: {
path: "/system/warnings",
component: WarningsPage,
},
requiredPermission: "pageTree",
},
],
},
{
Expand Down
215 changes: 215 additions & 0 deletions demo/admin/src/warnings/WarningsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import { gql, useApolloClient, useQuery } from "@apollo/client";
import {
dataGridDateTimeColumn,
DataGridToolbar,
type GridColDef,
GridFilterButton,
MainContent,
muiGridFilterToGql,
muiGridSortToGql,
ToolbarItem,
useBufferedRowCount,
useDataGridRemote,
usePersistentColumnState,
} from "@comet/admin";
import { ArrowRight, OpenNewTab, WarningSolid } from "@comet/admin-icons";
import { type DependencyInterface, useDependenciesConfig } from "@comet/cms-admin";
import { Chip, IconButton } from "@mui/material";
import { DataGrid, GridToolbarQuickFilter } from "@mui/x-data-grid";
import { useContentScope } from "@src/common/ContentScopeProvider";
import { type GQLWarningSeverity } from "@src/graphql.generated";
import { FormattedMessage, useIntl } from "react-intl";
import { useHistory } from "react-router";

import { type GQLWarningsGridQuery, type GQLWarningsGridQueryVariables, type GQLWarningsListFragment } from "./WarningsGrid.generated";

const warningsFragment = gql`
fragment WarningsList on Warning {
id
createdAt
updatedAt
message
type
severity
status
dependencyInfo {
rootColumnName
targetId
graphqlObjectType
jsonPath
}
}
`;

const warningsQuery = gql`
query WarningsGrid($offset: Int, $limit: Int, $sort: [WarningSort!], $search: String, $filter: WarningFilter) {
warnings(offset: $offset, limit: $limit, sort: $sort, search: $search, filter: $filter) {
nodes {
...WarningsList
}
totalCount
}
}
${warningsFragment}
`;

function WarningsGridToolbar() {
return (
<DataGridToolbar>
<ToolbarItem>
<GridToolbarQuickFilter />
</ToolbarItem>
<ToolbarItem>
<GridFilterButton />
</ToolbarItem>
</DataGridToolbar>
);
}

export function WarningsGrid() {
const intl = useIntl();
const dataGridProps = {
...useDataGridRemote({ initialFilter: { items: [{ field: "state", operator: "is", value: "open" }] } }),
...usePersistentColumnState("WarningsGrid"),
};
const history = useHistory();
const entityDependencyMap = useDependenciesConfig();
const apolloClient = useApolloClient();
const contentScope = useContentScope();

const columns: GridColDef<GQLWarningsListFragment>[] = [
{
...dataGridDateTimeColumn,
field: "createdAt",
headerName: intl.formatMessage({ id: "warning.dateTime", defaultMessage: "Date / Time" }),
width: 200,
},
{
field: "severity",
headerName: intl.formatMessage({ id: "warning.severity", defaultMessage: "Severity" }),
type: "singleSelect",
valueOptions: [
{ value: "critical", label: intl.formatMessage({ id: "warning.severity.critical", defaultMessage: "Critical" }) },
{ value: "high", label: intl.formatMessage({ id: "warning.severity.high", defaultMessage: "High" }) },
{ value: "low", label: intl.formatMessage({ id: "warning.severity.low", defaultMessage: "Low" }) },
],
width: 150,
renderCell: (params) => {
const colorMapping: Record<GQLWarningSeverity, "error" | "warning" | "default"> = {
critical: "error",
high: "warning",
low: "default",
};
return (
<Chip
icon={params.value === "critical" ? <WarningSolid /> : undefined}
color={colorMapping[params.value as GQLWarningSeverity]}
label={params.value}
/>
);
},
},
{
field: "type",
headerName: intl.formatMessage({ id: "warning.type", defaultMessage: "Type" }),
width: 150,
renderCell: (params) => <Chip label={params.value} />,
},
{
field: "message",
headerName: intl.formatMessage({ id: "warning.message", defaultMessage: "Message" }),
flex: 1,
},
{
field: "status",
headerName: intl.formatMessage({ id: "warning.status", defaultMessage: "Status" }),
type: "singleSelect",
valueOptions: [
{ value: "open", label: intl.formatMessage({ id: "warning.status.open", defaultMessage: "Open" }) },
{ value: "resolved", label: intl.formatMessage({ id: "warning.status.resolved", defaultMessage: "Resolved" }) },
{ value: "ignored", label: intl.formatMessage({ id: "warning.status.ignored", defaultMessage: "Ignored" }) },
],
width: 150,
},
{
field: "actions",
headerName: "",
sortable: false,
renderCell: ({ row }) => {
const dependencyObject = entityDependencyMap[row.dependencyInfo.graphqlObjectType] as DependencyInterface | undefined;

if (dependencyObject === undefined) {
if (process.env.NODE_ENV === "development") {
console.warn(
`Cannot load URL because no implementation of DependencyInterface for ${row.dependencyInfo.graphqlObjectType} was provided via the DependenciesConfig`,
);
}
return <FormattedMessage id="comet.dependencies.dataGrid.cannotLoadUrl" defaultMessage="Cannot determine URL" />;
}

const loadUrl = async () => {
const path = await dependencyObject.resolvePath({
rootColumnName: row.dependencyInfo.rootColumnName,
jsonPath: row.dependencyInfo.jsonPath,
apolloClient,
id: row.dependencyInfo.targetId,
});
return contentScope.match.url + path;
};

return (
<div style={{ display: "flex" }}>
<IconButton
onClick={async () => {
const url = await loadUrl();
window.open(url, "_blank");
}}
>
<OpenNewTab />
</IconButton>
<IconButton
onClick={async () => {
const url = await loadUrl();

history.push(url);
}}
>
<ArrowRight />
</IconButton>
</div>
);
},
},
];

const { filter: gqlFilter, search: gqlSearch } = muiGridFilterToGql(columns, dataGridProps.filterModel);

const { data, loading, error } = useQuery<GQLWarningsGridQuery, GQLWarningsGridQueryVariables>(warningsQuery, {
variables: {
filter: gqlFilter,
search: gqlSearch,
offset: dataGridProps.paginationModel.page * dataGridProps.paginationModel.pageSize,
limit: dataGridProps.paginationModel.pageSize,
sort: muiGridSortToGql(dataGridProps.sortModel),
},
});
const rowCount = useBufferedRowCount(data?.warnings.totalCount);
if (error) throw error;
const rows = data?.warnings.nodes ?? [];

return (
<MainContent fullHeight>
<DataGrid
{...dataGridProps}
disableRowSelectionOnClick
rows={rows}
rowCount={rowCount}
columns={columns}
loading={loading}
slots={{
toolbar: WarningsGridToolbar,
}}
/>
</MainContent>
);
}
13 changes: 13 additions & 0 deletions demo/admin/src/warnings/WarningsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Stack } from "@comet/admin";
import { useIntl } from "react-intl";

import { WarningsGrid } from "./WarningsGrid";

export function WarningsPage() {
const intl = useIntl();
return (
<Stack topLevelTitle={intl.formatMessage({ id: "warnings.warnings", defaultMessage: "Warnings" })}>
<WarningsGrid />
</Stack>
);
}
100 changes: 100 additions & 0 deletions demo/api/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,43 @@ type RedirectScope {
domain: String!
}

type WarningDependencyInfo {
rootEntityName: String!
rootColumnName: String!
rootPrimaryKey: String!
targetId: String!
jsonPath: String!
graphqlObjectType: String!
}

type Warning {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
message: String!
type: String!
severity: WarningSeverity!
dependencyInfo: WarningDependencyInfo!
status: WarningStatus!
}

enum WarningSeverity {
critical
high
low
}

enum WarningStatus {
open
resolved
ignored
}

type PaginatedWarnings {
nodes: [Warning!]!
totalCount: Int!
}

type PaginatedPageTreeNodes {
nodes: [PageTreeNode!]!
totalCount: Int!
Expand Down Expand Up @@ -755,6 +792,15 @@ input RedirectScopeInput {
domain: String!
}

input WarningDependencyInfoInput {
rootEntityName: String!
rootColumnName: String!
rootPrimaryKey: String!
targetId: String!
jsonPath: String!
graphqlObjectType: String!
}

type Query {
currentUser: CurrentUser!
userPermissionsUserById(id: String!): UserPermissionsUser!
Expand Down Expand Up @@ -819,6 +865,8 @@ type Query {
manufacturers(offset: Int! = 0, limit: Int! = 25, search: String, filter: ManufacturerFilter, sort: [ManufacturerSort!]): PaginatedManufacturers!
manufacturerCountry(id: ID!): ManufacturerCountry!
manufacturerCountries(offset: Int! = 0, limit: Int! = 25, search: String, filter: ManufacturerCountryFilter): PaginatedManufacturerCountries!
warning(id: ID!): Warning!
warnings(offset: Int! = 0, limit: Int! = 25, status: [WarningStatus!]! = [open], search: String, filter: WarningFilter, sort: [WarningSort!]): PaginatedWarnings!
}

input UserPermissionsUserFilter {
Expand Down Expand Up @@ -1180,6 +1228,39 @@ input ManufacturerCountryFilter {
or: [ManufacturerCountryFilter!]
}

input WarningFilter {
message: StringFilter
type: StringFilter
severity: WarningSeverityEnumFilter
status: WarningStatusEnumFilter
and: [WarningFilter!]
or: [WarningFilter!]
}

input WarningSeverityEnumFilter {
isAnyOf: [WarningSeverity!]
equal: WarningSeverity
notEqual: WarningSeverity
}

input WarningStatusEnumFilter {
isAnyOf: [WarningStatus!]
equal: WarningStatus
notEqual: WarningStatus
}

input WarningSort {
field: WarningSortField!
direction: SortDirection! = ASC
}

enum WarningSortField {
message
type
severity
status
}

type Mutation {
currentUserSignOut: String!
userPermissionsCreatePermission(userId: String!, input: UserPermissionInput!): UserPermission!
Expand Down Expand Up @@ -1241,6 +1322,9 @@ type Mutation {
updateManufacturer(id: ID!, input: ManufacturerUpdateInput!): Manufacturer!
deleteManufacturer(id: ID!): Boolean!
publishAllProducts: Boolean!
createWarning(input: WarningInput!): Warning!
updateWarning(id: ID!, input: WarningUpdateInput!): Warning!
deleteWarning(id: ID!): Boolean!
}

input UserPermissionInput {
Expand Down Expand Up @@ -1541,4 +1625,20 @@ input ManufacturerUpdateInput {
name: String
address: AddressInput
addressAsEmbeddable: AddressAsEmbeddableInput
}

input WarningInput {
message: String!
type: String!
severity: WarningSeverity!
dependencyInfo: WarningDependencyInfoInput!
status: WarningStatus! = open
}

input WarningUpdateInput {
message: String
type: String
severity: WarningSeverity
dependencyInfo: WarningDependencyInfoInput
status: WarningStatus
}
Loading