diff --git a/App/FeatureSet/BaseAPI/Index.ts b/App/FeatureSet/BaseAPI/Index.ts index 20eb5d1015..402dc1f951 100644 --- a/App/FeatureSet/BaseAPI/Index.ts +++ b/App/FeatureSet/BaseAPI/Index.ts @@ -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 => { @@ -776,6 +780,14 @@ const BaseAPIFeatureSet: FeatureSet = { ).getRouter(), ); + app.use( + `/${APP_NAME.toLocaleLowerCase()}`, + new BaseAPI( + TableView, + TableViewService, + ).getRouter(), + ); + app.use( `/${APP_NAME.toLocaleLowerCase()}`, new BaseAPI( diff --git a/Common/Models/DatabaseModels/Index.ts b/Common/Models/DatabaseModels/Index.ts index a7c7fb3bbc..9b69d35400 100644 --- a/Common/Models/DatabaseModels/Index.ts +++ b/Common/Models/DatabaseModels/Index.ts @@ -307,7 +307,7 @@ const AllModelTypes: Array<{ TelemetryException, - TableView + TableView, ]; const modelTypeMap: { [key: string]: { new (): BaseModel } } = {}; diff --git a/Common/Models/DatabaseModels/TableView.ts b/Common/Models/DatabaseModels/TableView.ts index 5340797619..f60b29cb80 100644 --- a/Common/Models/DatabaseModels/TableView.ts +++ b/Common/Models/DatabaseModels/TableView.ts @@ -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 = undefined; - @ColumnAccessControl({ create: [ Permission.ProjectOwner, @@ -417,7 +415,6 @@ export default class TableView extends BaseModel { }) public sort?: Sort = 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; - } diff --git a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1727877987596-MigrationName.ts b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1727877987596-MigrationName.ts index b00e7d1c49..f062ce79a8 100644 --- a/Common/Server/Infrastructure/Postgres/SchemaMigrations/1727877987596-MigrationName.ts +++ b/Common/Server/Infrastructure/Postgres/SchemaMigrations/1727877987596-MigrationName.ts @@ -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 { - 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 { - 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 { + 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 { + 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"`); + } } diff --git a/Common/Server/Services/Index.ts b/Common/Server/Services/Index.ts index e745e4ce01..20d983fdcc 100644 --- a/Common/Server/Services/Index.ts +++ b/Common/Server/Services/Index.ts @@ -137,6 +137,8 @@ import ScheduledMaintenanceTemplateService from "./ScheduledMaintenanceTemplateS import ScheduledMaintenanceTemplateOwnerTeamService from "./ScheduledMaintenanceTemplateOwnerTeamService"; import ScheduledMaintenanceTemplateOwnerUserService from "./ScheduledMaintenanceTemplateOwnerUserService"; +import TableViewService from "./TableViewService"; + const services: Array = [ AcmeCertificateService, PromoCodeService, @@ -285,6 +287,8 @@ const services: Array = [ ScheduledMaintenanceTemplateService, ScheduledMaintenanceTemplateOwnerTeamService, ScheduledMaintenanceTemplateOwnerUserService, + + TableViewService, ]; export const AnalyticsServices: Array< diff --git a/Common/Server/Services/TableViewService.ts b/Common/Server/Services/TableViewService.ts new file mode 100644 index 0000000000..7748afa8ab --- /dev/null +++ b/Common/Server/Services/TableViewService.ts @@ -0,0 +1,10 @@ +import DatabaseService from "./DatabaseService"; +import TableView from "Common/Models/DatabaseModels/TableView"; + +export class Service extends DatabaseService { + public constructor() { + super(TableView); + } +} + +export default new Service(); diff --git a/Common/UI/Components/Card/Card.tsx b/Common/UI/Components/Card/Card.tsx index 16619ba18f..d4cb2888e9 100644 --- a/Common/UI/Components/Card/Card.tsx +++ b/Common/UI/Components/Card/Card.tsx @@ -57,42 +57,46 @@ const Card: FunctionComponent = (
{props.rightElement} - {props.buttons?.map((button: CardButtonSchema | ReactElement, i: number) => { - return ( -
0 - ? { - marginLeft: "10px", - } - : {} - } - key={i} - > - - {React.isValidElement(button) ? button : null} - {React.isValidElement(button) ? null : ( -
- ); - })} + 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} + /> + )} +
+ ); + }, + )} diff --git a/Common/UI/Components/ModelDetail/CardModelDetail.tsx b/Common/UI/Components/ModelDetail/CardModelDetail.tsx index 34c1408304..4a82dd6197 100644 --- a/Common/UI/Components/ModelDetail/CardModelDetail.tsx +++ b/Common/UI/Components/ModelDetail/CardModelDetail.tsx @@ -39,7 +39,9 @@ const CardModelDetail: ( ) => ReactElement = ( props: ComponentProps, ): ReactElement => { - const [cardButtons, setCardButtons] = useState>([]); + const [cardButtons, setCardButtons] = useState< + Array + >([]); const [showModel, setShowModal] = useState(false); const [item, setItem] = useState(null); const [refresher, setRefresher] = useState(false); diff --git a/Common/UI/Components/ModelTable/BaseModelTable.tsx b/Common/UI/Components/ModelTable/BaseModelTable.tsx index d645a85737..851313263d 100644 --- a/Common/UI/Components/ModelTable/BaseModelTable.tsx +++ b/Common/UI/Components/ModelTable/BaseModelTable.tsx @@ -93,7 +93,6 @@ export enum ShowAs { OrderedStatesList, } - export interface SaveFilterProps { tableId: string; } @@ -263,7 +262,9 @@ const BaseModelTable: ( Array> >([]); - const [cardButtons, setCardButtons] = useState>([]); + const [cardButtons, setCardButtons] = useState< + Array + >([]); const [actionButtonSchema, setActionButtonSchema] = useState< Array> @@ -793,21 +794,21 @@ const BaseModelTable: ( return selectFields; }; - const getSaveFilterDropdown: GetReactElementFunction = (): ReactElement => { - if(!props.saveFilterProps){ - return <> + if (!props.saveFilterProps) { + return <>; } - if(props.saveFilterProps && props.saveFilterProps.tableId){ - return ( - { - }}> - ) + if (props.saveFilterProps && props.saveFilterProps.tableId) { + return ( + + {}}> + + ); } - return <> - } + return <>; + }; const setHeaderButtons: VoidFunction = (): void => { // add header buttons. @@ -863,7 +864,6 @@ const BaseModelTable: ( }); } - if (showFilterButton) { headerbuttons.push({ title: "", @@ -880,7 +880,7 @@ const BaseModelTable: ( }); } - if(props.saveFilterProps){ + if (props.saveFilterProps) { headerbuttons.push(getSaveFilterDropdown()); } @@ -1641,8 +1641,6 @@ const BaseModelTable: ( ); }; - - const getCardComponent: GetReactElementFunction = (): ReactElement => { if (showAs === ShowAs.Table || showAs === ShowAs.List) { return ( diff --git a/Common/UI/Components/MoreMenu/Divider.tsx b/Common/UI/Components/MoreMenu/Divider.tsx index fdaddad4ca..fc884f5540 100644 --- a/Common/UI/Components/MoreMenu/Divider.tsx +++ b/Common/UI/Components/MoreMenu/Divider.tsx @@ -1,12 +1,7 @@ import React, { FunctionComponent, ReactElement } from "react"; -const MoreMenuItem: FunctionComponent = ( - -): ReactElement => { - return ( -
-
- ); +const MoreMenuItem: FunctionComponent = (): ReactElement => { + return
; }; export default MoreMenuItem; diff --git a/Common/UI/Components/MoreMenu/MoreMenu.tsx b/Common/UI/Components/MoreMenu/MoreMenu.tsx index 452dfc8cbd..5d56b1d4bf 100644 --- a/Common/UI/Components/MoreMenu/MoreMenu.tsx +++ b/Common/UI/Components/MoreMenu/MoreMenu.tsx @@ -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; + children: Array | ReactElement; } const MoreMenu: FunctionComponent = ( - props: ComponentProps, + props: ComponentProps, ): ReactElement => { - - const { ref, isComponentVisible, setIsComponentVisible } = + const { ref, isComponentVisible, setIsComponentVisible } = useComponentOutsideClick(false); - + const [isDropdownVisible, setDropdownVisible] = useState(false); - const [isDropdownVisible, setDropdownVisible] = useState(false); + useEffect(() => { + setDropdownVisible(isComponentVisible); + }, [isComponentVisible]); - useEffect(() => { - setDropdownVisible(isComponentVisible); - }, [isComponentVisible]); + return ( +
+
+
- return ( -
-
-
- - {isComponentVisible &&
- {props.children} -
} + {isComponentVisible && ( +
+ {props.children}
- - ); + )} +
+ ); }; export default MoreMenu; diff --git a/Common/UI/Components/MoreMenu/MoreMenuItem.tsx b/Common/UI/Components/MoreMenu/MoreMenuItem.tsx index 9babc158e1..6ea07caa6c 100644 --- a/Common/UI/Components/MoreMenu/MoreMenuItem.tsx +++ b/Common/UI/Components/MoreMenu/MoreMenuItem.tsx @@ -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 = ( - props: ComponentProps, + props: ComponentProps, ): ReactElement => { - return ( - { - props.onClick(); - }}> - {props.icon && } - {props.text} - - ); + return ( + { + props.onClick(); + }} + > + {props.icon && ( + + )} + {props.text} + + ); }; export default MoreMenuItem;