Files
calendar/packages/features/booking-audit/lib/actions/ReassignmentAuditActionService.ts
T
Hariom BalharaGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
f1011ddd08 chore: Improves the core booking audit architecture by adding new capabilities and simplifying the existing interfaces. (#25872)
* feat(booking-audit): extract core audit system changes from PR 25125

This PR extracts core audit infrastructure changes without integration changes:

1. New action services introduced:
   - SeatBookedAuditActionService
   - SeatRescheduledAuditActionService

2. Simplification of ActionService interface:
   - Streamlined IAuditActionService interface
   - Reduced TypeScript burden with cleaner type definitions

3. ActionSource support:
   - Added BookingAuditSource enum (API_V1, API_V2, WEBAPP, WEBHOOK, UNKNOWN)
   - Added source and operationId fields to BookingAudit model

4. New AuditAction types:
   - SEAT_BOOKED
   - SEAT_RESCHEDULED
   - APP actor type

5. New BookingAuditAccessService:
   - Permission-based access control for audit logs
   - Added readTeamAuditLogs and readOrgAuditLogs permissions

6. Fixes in the logs viewer flow:
   - Enhanced BookingAuditViewerService with improved filtering
   - Local AttendeeRepository for actor enrichment

Changes are contained within packages/features/booking-audit with minimal
outside changes (permission registry only).

Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com>

* refactor(booking-audit): streamline action handling and enhance localization

- Replaced action icon retrieval with a mapping object for improved clarity and performance.
- Introduced constants for actor role labels to simplify role retrieval.
- Added new localization strings for audit log permission errors and organization requirements.
- Updated various service and repository interfaces to enhance type safety and clarity.
- Removed deprecated architecture documentation and adjusted related imports for consistency.

These changes aim to improve code maintainability and user experience in the booking audit system.

* fix(booking-audit): enhance actor role localization and operation ID tracking

- Updated actor role labels in the booking logs view to use lowercase for consistency.
- Improved localization by wrapping actor role display in a translation function.
- Added operationId field to audit logs for better correlation of actions across multiple bookings.
- Enhanced BookingAuditViewerService to include operationId in enriched audit logs.
- Updated integration tests to verify consistent operationId across related audit logs.

These changes aim to improve localization accuracy and facilitate better tracking of user actions in the booking audit system.

* feat: integrate credential repository and enhance app actor handling

- Added CredentialRepository to manage app credentials, including a method to find credentials by ID.
- Updated BookingAudit system to support app actors identified by credential ID, improving actor attribution and audit clarity.
- Introduced a new utility function to map app slugs to display names, enhancing the user experience in audit logs.
- Modified relevant interfaces and types to accommodate the new credential handling and app actor structure.
- Enhanced BookingAuditViewerService to display app names based on credentials, ensuring accurate representation in audit logs.

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-12-18 12:46:24 +00:00

109 lines
3.9 KiB
TypeScript

import { z } from "zod";
import type { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import { StringChangeSchema, NumberChangeSchema } from "../common/changeSchemas";
import { AuditActionServiceHelper } from "./AuditActionServiceHelper";
import type { IAuditActionService, TranslationWithParams, GetDisplayTitleParams, GetDisplayJsonParams, BaseStoredAuditData } from "./IAuditActionService";
/**
* Reassignment Audit Action Service
* Handles REASSIGNMENT action with per-action versioning
*/
// Module-level because it is passed to IAuditActionService type outside the class scope
const fieldsSchemaV1 = z.object({
assignedToId: NumberChangeSchema,
assignedById: NumberChangeSchema,
reassignmentReason: StringChangeSchema,
reassignmentType: z.enum(["manual", "roundRobin"]),
userPrimaryEmail: StringChangeSchema.optional(),
title: StringChangeSchema.optional(),
});
export class ReassignmentAuditActionService implements IAuditActionService {
readonly VERSION = 1;
public static readonly TYPE = "REASSIGNMENT" as const;
private static dataSchemaV1 = z.object({
version: z.literal(1),
fields: fieldsSchemaV1,
});
private static fieldsSchemaV1 = fieldsSchemaV1;
public static readonly latestFieldsSchema = fieldsSchemaV1;
// Union of all versions
public static readonly storedDataSchema = ReassignmentAuditActionService.dataSchemaV1;
// Union of all versions
public static readonly storedFieldsSchema = ReassignmentAuditActionService.fieldsSchemaV1;
private helper: AuditActionServiceHelper<
typeof ReassignmentAuditActionService.latestFieldsSchema,
typeof ReassignmentAuditActionService.storedDataSchema
>;
constructor(private userRepository: UserRepository) {
this.helper = new AuditActionServiceHelper({
latestVersion: this.VERSION,
latestFieldsSchema: ReassignmentAuditActionService.latestFieldsSchema,
storedDataSchema: ReassignmentAuditActionService.storedDataSchema,
});
}
getVersionedData(fields: unknown) {
return this.helper.getVersionedData(fields);
}
parseStored(data: unknown) {
return this.helper.parseStored(data);
}
getVersion(data: unknown): number {
return this.helper.getVersion(data);
}
migrateToLatest(data: unknown) {
// V1-only: validate and return as-is (no migration needed)
const validated = fieldsSchemaV1.parse(data);
return { isMigrated: false, latestData: validated };
}
async getDisplayTitle({ storedData }: GetDisplayTitleParams): Promise<TranslationWithParams> {
const { fields } = this.parseStored(storedData);
const user = await this.userRepository.findById({ id: fields.assignedToId.new });
const reassignedToName = user?.name || "Unknown";
return {
key: "booking_audit_action.booking_reassigned_to_host",
params: { host: reassignedToName },
};
}
getDisplayJson({
storedData,
}: GetDisplayJsonParams): ReassignmentAuditDisplayData {
const { fields } = this.parseStored(storedData);
return {
newAssignedToId: fields.assignedToId.new,
reassignmentReason: fields.reassignmentReason.new ?? null,
};
}
getDisplayFields(storedData: BaseStoredAuditData): Array<{
labelKey: string;
valueKey: string;
}> {
const { fields } = storedData;
const typeTranslationKey = `booking_audit_action.assignmentType_${fields.reassignmentType}`;
return [
{
labelKey: "booking_audit_action.type",
valueKey: typeTranslationKey,
}
];
}
}
export type ReassignmentAuditData = z.infer<typeof fieldsSchemaV1>;
export type ReassignmentAuditDisplayData = {
newAssignedToId: number;
reassignmentReason: string | null;
};