add table filters model

This commit is contained in:
Simon Larsen 2024-10-02 15:13:37 +01:00
parent 7c941da7df
commit c833bcb37a
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
12 changed files with 176 additions and 116 deletions

View File

@ -55,6 +55,9 @@ import IncidentInternalNoteService, {
import IncidentNoteTemplateService, {
Service as IncidentNoteTemplateServiceType,
} from "Common/Server/Services/IncidentNoteTemplateService";
import TableViewService, {
Service as TableViewServiceType,
} from "Common/Server/Services/TableViewService";
import IncidentOwnerTeamService, {
Service as IncidentOwnerTeamServiceType,
} from "Common/Server/Services/IncidentOwnerTeamService";
@ -443,6 +446,7 @@ import ScheduledMaintenanceTemplateOwnerTeamService, {
import ScheduledMaintenanceTemplateOwnerUserService, {
Service as ScheduledMaintenanceTemplateOwnerUserServiceType,
} from "Common/Server/Services/ScheduledMaintenanceTemplateOwnerUserService";
import TableView from "Common/Models/DatabaseModels/TableView";
const BaseAPIFeatureSet: FeatureSet = {
init: async (): Promise<void> => {
@ -776,6 +780,14 @@ const BaseAPIFeatureSet: FeatureSet = {
).getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<TableView, TableViewServiceType>(
TableView,
TableViewService,
).getRouter(),
);
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAPI<IncidentState, IncidentStateServiceType>(

View File

@ -307,7 +307,7 @@ const AllModelTypes: Array<{
TelemetryException,
TableView
TableView,
];
const modelTypeMap: { [key: string]: { new (): BaseModel } } = {};

View File

@ -168,7 +168,6 @@ export default class TableView extends BaseModel {
})
public name?: string = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
@ -383,7 +382,6 @@ export default class TableView extends BaseModel {
})
public filters?: Query<BaseModel> = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
@ -417,7 +415,6 @@ export default class TableView extends BaseModel {
})
public sort?: Sort<BaseModel> = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
@ -448,8 +445,7 @@ export default class TableView extends BaseModel {
type: ColumnType.Number,
unique: false,
nullable: false,
default: 10
default: 10,
})
public itemsOnPage?: number = undefined;
}

View File

@ -1,22 +1,39 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class MigrationName1727877987596 implements MigrationInterface {
public name = 'MigrationName1727877987596'
public name = "MigrationName1727877987596";
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "TableView" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "tableId" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "filters" jsonb NOT NULL, "sort" jsonb NOT NULL, "itemsOnPage" integer NOT NULL DEFAULT '10', CONSTRAINT "PK_1e17a8834a65403cc87c4ead0cc" PRIMARY KEY ("_id"))`);
await queryRunner.query(`CREATE INDEX "IDX_a8691d4e023d907b5c73a43e42" ON "TableView" ("projectId") `);
await queryRunner.query(`ALTER TABLE "TableView" ADD CONSTRAINT "FK_a8691d4e023d907b5c73a43e424" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TableView" ADD CONSTRAINT "FK_2f2b7d1e199910951a0b5933d92" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "TableView" ADD CONSTRAINT "FK_b36d769f3d3d6fe4f9c35984551" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "TableView" DROP CONSTRAINT "FK_b36d769f3d3d6fe4f9c35984551"`);
await queryRunner.query(`ALTER TABLE "TableView" DROP CONSTRAINT "FK_2f2b7d1e199910951a0b5933d92"`);
await queryRunner.query(`ALTER TABLE "TableView" DROP CONSTRAINT "FK_a8691d4e023d907b5c73a43e424"`);
await queryRunner.query(`DROP INDEX "public"."IDX_a8691d4e023d907b5c73a43e42"`);
await queryRunner.query(`DROP TABLE "TableView"`);
}
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "TableView" ("_id" uuid NOT NULL DEFAULT uuid_generate_v4(), "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP WITH TIME ZONE, "version" integer NOT NULL, "projectId" uuid NOT NULL, "name" character varying(100) NOT NULL, "tableId" character varying(100) NOT NULL, "description" character varying(500), "createdByUserId" uuid, "deletedByUserId" uuid, "filters" jsonb NOT NULL, "sort" jsonb NOT NULL, "itemsOnPage" integer NOT NULL DEFAULT '10', CONSTRAINT "PK_1e17a8834a65403cc87c4ead0cc" PRIMARY KEY ("_id"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_a8691d4e023d907b5c73a43e42" ON "TableView" ("projectId") `,
);
await queryRunner.query(
`ALTER TABLE "TableView" ADD CONSTRAINT "FK_a8691d4e023d907b5c73a43e424" FOREIGN KEY ("projectId") REFERENCES "Project"("_id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TableView" ADD CONSTRAINT "FK_2f2b7d1e199910951a0b5933d92" FOREIGN KEY ("createdByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "TableView" ADD CONSTRAINT "FK_b36d769f3d3d6fe4f9c35984551" FOREIGN KEY ("deletedByUserId") REFERENCES "User"("_id") ON DELETE SET NULL ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "TableView" DROP CONSTRAINT "FK_b36d769f3d3d6fe4f9c35984551"`,
);
await queryRunner.query(
`ALTER TABLE "TableView" DROP CONSTRAINT "FK_2f2b7d1e199910951a0b5933d92"`,
);
await queryRunner.query(
`ALTER TABLE "TableView" DROP CONSTRAINT "FK_a8691d4e023d907b5c73a43e424"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_a8691d4e023d907b5c73a43e42"`,
);
await queryRunner.query(`DROP TABLE "TableView"`);
}
}

View File

@ -137,6 +137,8 @@ import ScheduledMaintenanceTemplateService from "./ScheduledMaintenanceTemplateS
import ScheduledMaintenanceTemplateOwnerTeamService from "./ScheduledMaintenanceTemplateOwnerTeamService";
import ScheduledMaintenanceTemplateOwnerUserService from "./ScheduledMaintenanceTemplateOwnerUserService";
import TableViewService from "./TableViewService";
const services: Array<BaseService> = [
AcmeCertificateService,
PromoCodeService,
@ -285,6 +287,8 @@ const services: Array<BaseService> = [
ScheduledMaintenanceTemplateService,
ScheduledMaintenanceTemplateOwnerTeamService,
ScheduledMaintenanceTemplateOwnerUserService,
TableViewService,
];
export const AnalyticsServices: Array<

View File

@ -0,0 +1,10 @@
import DatabaseService from "./DatabaseService";
import TableView from "Common/Models/DatabaseModels/TableView";
export class Service extends DatabaseService<TableView> {
public constructor() {
super(TableView);
}
}
export default new Service();

View File

@ -57,42 +57,46 @@ const Card: FunctionComponent<ComponentProps> = (
</div>
<div className="flex w-fit">
{props.rightElement}
{props.buttons?.map((button: CardButtonSchema | ReactElement, i: number) => {
return (
<div
style={
i > 0
? {
marginLeft: "10px",
}
: {}
}
key={i}
>
{React.isValidElement(button) ? button : null}
{React.isValidElement(button) ? null : (
<Button
key={i}
title={(button as CardButtonSchema).title}
buttonStyle={(button as CardButtonSchema).buttonStyle}
className={(button as CardButtonSchema).className}
onClick={() => {
if ((button as CardButtonSchema).onClick) {
(button as CardButtonSchema).onClick();
{props.buttons?.map(
(button: CardButtonSchema | ReactElement, i: number) => {
return (
<div
style={
i > 0
? {
marginLeft: "10px",
}
: {}
}
key={i}
>
{React.isValidElement(button) ? button : null}
{React.isValidElement(button) ? null : (
<Button
key={i}
title={(button as CardButtonSchema).title}
buttonStyle={
(button as CardButtonSchema).buttonStyle
}
}}
disabled={(button as CardButtonSchema).disabled}
icon={(button as CardButtonSchema).icon}
shortcutKey={(button as CardButtonSchema).shortcutKey}
dataTestId="card-button"
isLoading={(button as CardButtonSchema).isLoading}
/>
)}
</div>
);
})}
className={(button as CardButtonSchema).className}
onClick={() => {
if ((button as CardButtonSchema).onClick) {
(button as CardButtonSchema).onClick();
}
}}
disabled={(button as CardButtonSchema).disabled}
icon={(button as CardButtonSchema).icon}
shortcutKey={
(button as CardButtonSchema).shortcutKey
}
dataTestId="card-button"
isLoading={(button as CardButtonSchema).isLoading}
/>
)}
</div>
);
},
)}
</div>
</div>

View File

@ -39,7 +39,9 @@ const CardModelDetail: <TBaseModel extends BaseModel>(
) => ReactElement = <TBaseModel extends BaseModel>(
props: ComponentProps<TBaseModel>,
): ReactElement => {
const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema | ReactElement>>([]);
const [cardButtons, setCardButtons] = useState<
Array<CardButtonSchema | ReactElement>
>([]);
const [showModel, setShowModal] = useState<boolean>(false);
const [item, setItem] = useState<TBaseModel | null>(null);
const [refresher, setRefresher] = useState<boolean>(false);

View File

@ -93,7 +93,6 @@ export enum ShowAs {
OrderedStatesList,
}
export interface SaveFilterProps {
tableId: string;
}
@ -263,7 +262,9 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
Array<ClassicFilterType<TBaseModel>>
>([]);
const [cardButtons, setCardButtons] = useState<Array<CardButtonSchema | ReactElement>>([]);
const [cardButtons, setCardButtons] = useState<
Array<CardButtonSchema | ReactElement>
>([]);
const [actionButtonSchema, setActionButtonSchema] = useState<
Array<ActionButtonSchema<TBaseModel>>
@ -793,21 +794,21 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
return selectFields;
};
const getSaveFilterDropdown: GetReactElementFunction = (): ReactElement => {
if(!props.saveFilterProps){
return <></>
if (!props.saveFilterProps) {
return <></>;
}
if(props.saveFilterProps && props.saveFilterProps.tableId){
return (<MoreMenu>
<MoreMenuItem text="Save Filter" onClick={() => {
}}></MoreMenuItem>
</MoreMenu>)
if (props.saveFilterProps && props.saveFilterProps.tableId) {
return (
<MoreMenu>
<MoreMenuItem text="Save Filter" onClick={() => {}}></MoreMenuItem>
</MoreMenu>
);
}
return <></>
}
return <></>;
};
const setHeaderButtons: VoidFunction = (): void => {
// add header buttons.
@ -863,7 +864,6 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
});
}
if (showFilterButton) {
headerbuttons.push({
title: "",
@ -880,7 +880,7 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
});
}
if(props.saveFilterProps){
if (props.saveFilterProps) {
headerbuttons.push(getSaveFilterDropdown());
}
@ -1641,8 +1641,6 @@ const BaseModelTable: <TBaseModel extends BaseModel | AnalyticsBaseModel>(
);
};
const getCardComponent: GetReactElementFunction = (): ReactElement => {
if (showAs === ShowAs.Table || showAs === ShowAs.List) {
return (

View File

@ -1,12 +1,7 @@
import React, { FunctionComponent, ReactElement } from "react";
const MoreMenuItem: FunctionComponent = (
): ReactElement => {
return (
<div className="py-1" role="none">
</div>
);
const MoreMenuItem: FunctionComponent = (): ReactElement => {
return <div className="py-1" role="none"></div>;
};
export default MoreMenuItem;

View File

@ -1,41 +1,54 @@
import React, { FunctionComponent, ReactElement, useEffect, useState } from "react";
import React, {
FunctionComponent,
ReactElement,
useEffect,
useState,
} from "react";
import Button, { ButtonStyleType } from "../Button/Button";
import IconProp from "../../../Types/Icon/IconProp";
import useComponentOutsideClick from "../../Types/UseComponentOutsideClick";
export interface ComponentProps {
children: Array<ReactElement> | ReactElement;
children: Array<ReactElement> | ReactElement;
}
const MoreMenu: FunctionComponent<ComponentProps> = (
props: ComponentProps,
props: ComponentProps,
): ReactElement => {
const { ref, isComponentVisible, setIsComponentVisible } =
const { ref, isComponentVisible, setIsComponentVisible } =
useComponentOutsideClick(false);
const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false);
const [isDropdownVisible, setDropdownVisible] = useState<boolean>(false);
useEffect(() => {
setDropdownVisible(isComponentVisible);
}, [isComponentVisible]);
useEffect(() => {
setDropdownVisible(isComponentVisible);
}, [isComponentVisible]);
return (
<div className="relative inline-block text-left">
<div>
<Button
icon={IconProp.More}
buttonStyle={ButtonStyleType.ICON}
onClick={() => {
setIsComponentVisible(!isDropdownVisible);
}}
/>
</div>
return (
<div className="relative inline-block text-left">
<div>
<Button icon={IconProp.More} buttonStyle={ButtonStyleType.ICON} onClick={()=>{
setIsComponentVisible(!isDropdownVisible);
}} />
</div>
{isComponentVisible && <div ref={ref} className="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="menu-button">
{props.children}
</div>}
{isComponentVisible && (
<div
ref={ref}
className="absolute right-0 z-10 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
role="menu"
aria-orientation="vertical"
aria-labelledby="menu-button"
>
{props.children}
</div>
);
)}
</div>
);
};
export default MoreMenu;

View File

@ -3,22 +3,31 @@ import IconProp from "../../../Types/Icon/IconProp";
import Icon from "../Icon/Icon";
export interface ComponentProps {
icon?: IconProp | undefined;
text: string;
onClick: () => void;
icon?: IconProp | undefined;
text: string;
onClick: () => void;
}
const MoreMenuItem: FunctionComponent<ComponentProps> = (
props: ComponentProps,
props: ComponentProps,
): ReactElement => {
return (
<a className="cursor-pointer group flex items-center px-4 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50" role="menuitem" onClick={()=>{
props.onClick();
}}>
{props.icon && <Icon icon={props.icon} className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500" />}
{props.text}
</a>
);
return (
<a
className="cursor-pointer group flex items-center px-4 py-2 text-sm text-gray-700 hover:text-gray-900 hover:bg-gray-50"
role="menuitem"
onClick={() => {
props.onClick();
}}
>
{props.icon && (
<Icon
icon={props.icon}
className="mr-3 h-5 w-5 text-gray-400 group-hover:text-gray-500"
/>
)}
{props.text}
</a>
);
};
export default MoreMenuItem;