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

COM-957: Add basic warning entity #2503

Merged
merged 6 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
5 changes: 5 additions & 0 deletions demo/admin/crud-generator-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CrudGeneratorConfig } from "@comet/cms-admin";

export default [
{
target: "src/products/generated",
Expand All @@ -8,4 +9,8 @@ export default [
target: "src/news/generated",
entityName: "News",
},
{
target: "src/warnings/generated",
entityName: "Warning",
},
] satisfies CrudGeneratorConfig[];
10 changes: 10 additions & 0 deletions demo/admin/src/common/MasterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ProductsPage } from "@src/products/generated/ProductsPage";
import { ManufacturersPage as ManufacturersHandmadePage } from "@src/products/ManufacturersPage";
import ProductsHandmadePage from "@src/products/ProductsPage";
import ProductTagsPage from "@src/products/tags/ProductTagsPage";
import { WarningsPage } from "@src/warnings/WarningsPage";
import { FormattedMessage } from "react-intl";
import { Redirect, RouteComponentProps } from "react-router-dom";

Expand Down Expand Up @@ -175,6 +176,15 @@ export const masterMenuData: MasterMenuData = [
},
requiredPermission: "pageTree",
},
{
type: "route",
primary: <FormattedMessage id="menu.warnings" defaultMessage="Warnings" />,
route: {
path: "/system/warnings",
component: WarningsPage,
},
requiredPermission: "pageTree",
},
],
requiredPermission: "pageTree",
},
Expand Down
152 changes: 152 additions & 0 deletions demo/admin/src/warnings/WarningsGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { gql, useQuery } from "@apollo/client";
import {
DataGridToolbar,
GridColDef,
GridFilterButton,
MainContent,
muiGridFilterToGql,
muiGridSortToGql,
ToolbarItem,
useBufferedRowCount,
useDataGridRemote,
usePersistentColumnState,
} from "@comet/admin";
import { WarningSolid } from "@comet/admin-icons";
import { Chip } from "@mui/material";
import { DataGrid, GridToolbarQuickFilter } from "@mui/x-data-grid";
import { GQLWarningLevel } from "@src/graphql.generated";
import * as React from "react";
import { FormattedDate, FormattedTime, useIntl } from "react-intl";

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

const warningsFragment = gql`
fragment WarningsList on Warning {
id
createdAt
updatedAt
type
level
state
}
`;

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(): React.ReactElement {
const intl = useIntl();
const dataGridProps = {
...useDataGridRemote({ initialFilter: { items: [{ columnField: "state", operatorValue: "is", value: "open" }] } }),
...usePersistentColumnState("WarningsGrid"),
};

const columns: GridColDef<GQLWarningsListFragment>[] = [
{
field: "createdAt",
headerName: intl.formatMessage({ id: "warning.dateTime", defaultMessage: "Date / Time" }),
type: "dateTime",
renderCell: (params) => (
<>
<FormattedDate value={params.value} /> <FormattedTime value={params.value} />
</>
),
width: 200,
},
{
field: "level",
headerName: intl.formatMessage({ id: "warning.level", defaultMessage: "Level" }),
type: "singleSelect",
valueOptions: [
{ value: "critical", label: intl.formatMessage({ id: "warning.level.critical", defaultMessage: "Critical" }) },
{ value: "high", label: intl.formatMessage({ id: "warning.level.high", defaultMessage: "High" }) },
{ value: "low", label: intl.formatMessage({ id: "warning.level.low", defaultMessage: "Low" }) },
],
width: 150,
renderCell: (params) => {
const colorMapping: Record<GQLWarningLevel, "error" | "warning" | "default"> = {
critical: "error",
high: "warning",
low: "default",
};
return (
<Chip
icon={params.value === "critical" ? <WarningSolid /> : undefined}
color={colorMapping[params.value as GQLWarningLevel]}
label={params.value}
/>
);
},
},
{
field: "type",
headerName: intl.formatMessage({ id: "warning.type", defaultMessage: "Type" }),
width: 150,
renderCell: (params) => <Chip label={params.value} />,
},
{
field: "state",
headerName: intl.formatMessage({ id: "warning.state", defaultMessage: "State" }),
type: "singleSelect",
valueOptions: [
{ value: "open", label: intl.formatMessage({ id: "warning.state.open", defaultMessage: "Open" }) },
{ value: "resolved", label: intl.formatMessage({ id: "warning.state.resolved", defaultMessage: "Resolved" }) },
{ value: "ignored", label: intl.formatMessage({ id: "warning.state.ignored", defaultMessage: "Ignored" }) },
],
width: 150,
},
];

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

const { data, loading, error } = useQuery<GQLWarningsGridQuery, GQLWarningsGridQueryVariables>(warningsQuery, {
variables: {
filter: gqlFilter,
search: gqlSearch,
offset: dataGridProps.page * dataGridProps.pageSize,
limit: dataGridProps.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}
disableSelectionOnClick
rows={rows}
rowCount={rowCount}
columns={columns}
loading={loading}
components={{
Toolbar: WarningsGridToolbar,
}}
/>
</MainContent>
);
}
14 changes: 14 additions & 0 deletions demo/admin/src/warnings/WarningsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Stack } from "@comet/admin";
import * as React from "react";
import { useIntl } from "react-intl";

import { WarningsGrid } from "./WarningsGrid";

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

type Warning {
id: ID!
createdAt: DateTime!
updatedAt: DateTime!
type: String!
level: WarningLevel!
state: WarningState!
}

enum WarningLevel {
critical
high
low
}

enum WarningState {
open
resolved
ignored
}

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

type PaginatedPageTreeNodes {
nodes: [PageTreeNode!]!
totalCount: Int!
Expand Down Expand Up @@ -790,6 +816,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, search: String, filter: WarningFilter, sort: [WarningSort!]): PaginatedWarnings!
}

input UserFilter {
Expand Down Expand Up @@ -1149,6 +1177,41 @@ input ManufacturerCountryFilter {
or: [ManufacturerCountryFilter!]
}

input WarningFilter {
createdAt: DateTimeFilter
updatedAt: DateTimeFilter
type: StringFilter
level: WarningLevelEnumFilter
state: WarningStateEnumFilter
and: [WarningFilter!]
or: [WarningFilter!]
}

input WarningLevelEnumFilter {
isAnyOf: [WarningLevel!]
equal: WarningLevel
notEqual: WarningLevel
}

input WarningStateEnumFilter {
isAnyOf: [WarningState!]
equal: WarningState
notEqual: WarningState
}

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

enum WarningSortField {
createdAt
updatedAt
type
level
state
}

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

input UserPermissionInput {
Expand Down Expand Up @@ -1498,3 +1564,15 @@ input ManufacturerUpdateInput {
address: AddressInput
addressAsEmbeddable: AddressAsEmbeddableInput
}

input WarningInput {
type: String!
level: WarningLevel!
state: WarningState!
}

input WarningUpdateInput {
type: String
level: WarningLevel
state: WarningState
}
2 changes: 2 additions & 0 deletions demo/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import { Page } from "./pages/entities/page.entity";
import { PredefinedPageModule } from "./predefined-page/predefined-page.module";
import { ProductsModule } from "./products/products.module";
import { RedirectScope } from "./redirects/dto/redirect-scope";
import { WarningsModule } from "./warnings/warning.module";

@Module({})
export class AppModule {
Expand Down Expand Up @@ -170,6 +171,7 @@ export class AppModule {
},
}),
...(config.sentry ? [SentryModule.forRootAsync(config.sentry)] : []),
WarningsModule,
],
};
}
Expand Down
13 changes: 13 additions & 0 deletions demo/api/src/db/migrations/Migration20240725161414.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Migration } from '@mikro-orm/migrations';

export class Migration20240725161414 extends Migration {

async up(): Promise<void> {
this.addSql('create table "Warning" ("id" uuid not null, "createdAt" timestamptz(0) not null, "updatedAt" timestamptz(0) not null, "type" varchar(255) not null, "level" text check ("level" in (\'critical\', \'high\', \'low\')) not null, "state" text check ("state" in (\'open\', \'resolved\', \'ignored\')) not null, constraint "Warning_pkey" primary key ("id"));');
}

async down(): Promise<void> {
this.addSql('drop table if exists "Warning" cascade;');
}

}
10 changes: 10 additions & 0 deletions demo/api/src/warnings/entities/warning-level.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { registerEnumType } from "@nestjs/graphql";

export enum WarningLevel {
critical = "critical",
high = "high",
low = "low",
}
registerEnumType(WarningLevel, {
name: "WarningLevel",
});
10 changes: 10 additions & 0 deletions demo/api/src/warnings/entities/warning-state.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { registerEnumType } from "@nestjs/graphql";

export enum WarningState {
open = "open",
resolved = "resolved",
ignored = "ignored",
}
registerEnumType(WarningState, {
name: "WarningState",
});
Loading
Loading