diff --git a/Common/Models/DatabaseModels/IncidentEpisode.ts b/Common/Models/DatabaseModels/IncidentEpisode.ts index 7758b57e23..7baf045a48 100644 --- a/Common/Models/DatabaseModels/IncidentEpisode.ts +++ b/Common/Models/DatabaseModels/IncidentEpisode.ts @@ -1,3 +1,4 @@ +import IncidentGroupingRule from "./IncidentGroupingRule"; import IncidentSeverity from "./IncidentSeverity"; import IncidentState from "./IncidentState"; import Label from "./Label"; @@ -1052,6 +1053,73 @@ export default class IncidentEpisode extends BaseModel { }) public groupingKey?: string = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentEpisode, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentEpisode, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "incidentGroupingRuleId", + type: TableColumnType.Entity, + modelType: IncidentGroupingRule, + title: "Incident Grouping Rule", + description: + "Relation to the Incident Grouping Rule that created this episode (if applicable)", + }) + @ManyToOne( + () => { + return IncidentGroupingRule; + }, + { + eager: false, + nullable: true, + onDelete: "SET NULL", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "incidentGroupingRuleId" }) + public incidentGroupingRule?: IncidentGroupingRule = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentEpisode, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentEpisode, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: false, + title: "Incident Grouping Rule ID", + description: + "ID of the Incident Grouping Rule that created this episode (if applicable)", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: true, + transformer: ObjectID.getDatabaseTransformer(), + }) + public incidentGroupingRuleId?: ObjectID = undefined; + @ColumnAccessControl({ create: [ Permission.ProjectOwner, diff --git a/Common/Models/DatabaseModels/IncidentEpisodeMember.ts b/Common/Models/DatabaseModels/IncidentEpisodeMember.ts index ecc3ff8246..4d7a422d0d 100644 --- a/Common/Models/DatabaseModels/IncidentEpisodeMember.ts +++ b/Common/Models/DatabaseModels/IncidentEpisodeMember.ts @@ -1,5 +1,6 @@ import Incident from "./Incident"; import IncidentEpisode from "./IncidentEpisode"; +import IncidentGroupingRule from "./IncidentGroupingRule"; import Project from "./Project"; import User from "./User"; import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel"; @@ -397,6 +398,43 @@ export default class IncidentEpisodeMember extends BaseModel { }) public addedByUserId?: ObjectID = undefined; + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.CreateIncidentEpisodeMember, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentEpisodeMember, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "matchedRuleId", + type: TableColumnType.Entity, + modelType: IncidentGroupingRule, + title: "Matched Grouping Rule", + description: + "Relation to the Incident Grouping Rule that matched this incident (if applicable)", + }) + @ManyToOne( + () => { + return IncidentGroupingRule; + }, + { + eager: false, + nullable: true, + onDelete: "SET NULL", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "matchedRuleId" }) + public matchedRule?: IncidentGroupingRule = undefined; + @ColumnAccessControl({ create: [ Permission.ProjectOwner, diff --git a/Common/Models/DatabaseModels/IncidentGroupingRule.ts b/Common/Models/DatabaseModels/IncidentGroupingRule.ts new file mode 100644 index 0000000000..9777686327 --- /dev/null +++ b/Common/Models/DatabaseModels/IncidentGroupingRule.ts @@ -0,0 +1,1430 @@ +import IncidentSeverity from "./IncidentSeverity"; +import Label from "./Label"; +import Monitor from "./Monitor"; +import OnCallDutyPolicy from "./OnCallDutyPolicy"; +import Project from "./Project"; +import Team from "./Team"; +import User from "./User"; +import BaseModel from "./DatabaseBaseModel/DatabaseBaseModel"; +import Route from "../../Types/API/Route"; +import ColumnAccessControl from "../../Types/Database/AccessControl/ColumnAccessControl"; +import TableAccessControl from "../../Types/Database/AccessControl/TableAccessControl"; +import ColumnLength from "../../Types/Database/ColumnLength"; +import ColumnType from "../../Types/Database/ColumnType"; +import CrudApiEndpoint from "../../Types/Database/CrudApiEndpoint"; +import EnableDocumentation from "../../Types/Database/EnableDocumentation"; +import EnableWorkflow from "../../Types/Database/EnableWorkflow"; +import TableColumn from "../../Types/Database/TableColumn"; +import TableColumnType from "../../Types/Database/TableColumnType"; +import TableMetadata from "../../Types/Database/TableMetadata"; +import TenantColumn from "../../Types/Database/TenantColumn"; +import IconProp from "../../Types/Icon/IconProp"; +import ObjectID from "../../Types/ObjectID"; +import Permission from "../../Types/Permission"; +import { + Column, + Entity, + Index, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, +} from "typeorm"; + +export interface IncidentGroupingRuleMatchCriteria { + monitorIds?: Array; + monitorCustomFields?: Record; + incidentSeverityIds?: Array; + labelIds?: Array; + incidentTitlePattern?: string; + incidentDescriptionPattern?: string; +} + +export interface IncidentGroupingRuleGroupByFields { + monitorId?: boolean; + incidentSeverityId?: boolean; + incidentTitle?: boolean; + customFieldValues?: Array; +} + +@EnableDocumentation() +@TenantColumn("projectId") +@TableAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + delete: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.DeleteIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], +}) +@CrudApiEndpoint(new Route("/incident-grouping-rule")) +@Entity({ + name: "IncidentGroupingRule", +}) +@EnableWorkflow({ + create: true, + delete: true, + update: true, + read: true, +}) +@TableMetadata({ + tableName: "IncidentGroupingRule", + singularName: "Incident Grouping Rule", + pluralName: "Incident Grouping Rules", + icon: IconProp.Layers, + tableDescription: + "Configure rules for automatically grouping related incidents into episodes", +}) +export default class IncidentGroupingRule extends BaseModel { + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [], + }) + @TableColumn({ + manyToOneRelationColumn: "projectId", + type: TableColumnType.Entity, + modelType: Project, + title: "Project", + description: "Relation to Project Resource in which this object belongs", + }) + @ManyToOne( + () => { + return Project; + }, + { + eager: false, + nullable: true, + onDelete: "CASCADE", + orphanedRowAction: "nullify", + }, + ) + @JoinColumn({ name: "projectId" }) + public project?: Project = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [], + }) + @Index() + @TableColumn({ + type: TableColumnType.ObjectID, + required: true, + canReadOnRelationQuery: true, + title: "Project ID", + description: "ID of your OneUptime Project in which this object belongs", + }) + @Column({ + type: ColumnType.ObjectID, + nullable: false, + transformer: ObjectID.getDatabaseTransformer(), + }) + public projectId?: ObjectID = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.ShortText, + canReadOnRelationQuery: true, + title: "Name", + description: "Name of this incident grouping rule", + }) + @Column({ + nullable: false, + type: ColumnType.ShortText, + length: ColumnLength.ShortText, + }) + public name?: string = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.LongText, + title: "Description", + description: "Description of this incident grouping rule", + }) + @Column({ + nullable: true, + type: ColumnType.LongText, + length: ColumnLength.LongText, + }) + public description?: string = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.Number, + title: "Priority", + description: + "Priority of this rule. Lower number = higher priority. Rules are evaluated in priority order.", + defaultValue: 1, + isDefaultValueColumn: true, + }) + @Column({ + type: ColumnType.Number, + nullable: false, + default: 1, + }) + public priority?: number = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @Index() + @TableColumn({ + required: true, + type: TableColumnType.Boolean, + title: "Is Enabled", + description: "Whether this rule is enabled", + defaultValue: true, + isDefaultValueColumn: true, + }) + @Column({ + type: ColumnType.Boolean, + nullable: false, + default: true, + }) + public isEnabled?: boolean = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.JSON, + title: "Match Criteria", + description: + "JSON object defining the criteria for matching incidents to this rule", + }) + @Column({ + type: ColumnType.JSON, + nullable: true, + }) + public matchCriteria?: IncidentGroupingRuleMatchCriteria = undefined; + + // Match Criteria Fields (individual columns for form support) + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Monitor, + title: "Monitors", + description: + "Only group incidents from these monitors. Leave empty to match incidents from any monitor.", + }) + @ManyToMany( + () => { + return Monitor; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentGroupingRuleMonitor", + inverseJoinColumn: { + name: "monitorId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentGroupingRuleId", + referencedColumnName: "_id", + }, + }) + public monitors?: Array = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: IncidentSeverity, + title: "Incident Severities", + description: + "Only group incidents with these severities. Leave empty to match incidents of any severity.", + }) + @ManyToMany( + () => { + return IncidentSeverity; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentGroupingRuleIncidentSeverity", + inverseJoinColumn: { + name: "incidentSeverityId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentGroupingRuleId", + referencedColumnName: "_id", + }, + }) + public incidentSeverities?: Array = undefined; + + @ColumnAccessControl({ + create: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.CreateIncidentGroupingRule, + ], + read: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.ProjectMember, + Permission.ReadIncidentGroupingRule, + ], + update: [ + Permission.ProjectOwner, + Permission.ProjectAdmin, + Permission.EditIncidentGroupingRule, + ], + }) + @TableColumn({ + required: false, + type: TableColumnType.EntityArray, + modelType: Label, + title: "Incident Labels", + description: + "Only group incidents that have at least one of these labels. Leave empty to match incidents regardless of incident labels.", + }) + @ManyToMany( + () => { + return Label; + }, + { eager: false }, + ) + @JoinTable({ + name: "IncidentGroupingRuleIncidentLabel", + inverseJoinColumn: { + name: "labelId", + referencedColumnName: "_id", + }, + joinColumn: { + name: "incidentGroupingRuleId", + referencedColumnName: "_id", + }, + }) + public incidentLabels?: Array