[PM-28746] Item transfer event logs (#18032)

* [PM-28746] Add item organization event types and i18n strings

* [PM-28746] Log event when transfer is accepted or declined
This commit is contained in:
Shane Melton
2025-12-18 10:20:59 -08:00
committed by GitHub
parent 524bd9a484
commit ff3582109c
5 changed files with 95 additions and 2 deletions

View File

@@ -452,6 +452,12 @@ export class EventService {
this.getShortId(ev.organizationId),
);
break;
case EventType.Organization_ItemOrganization_Accepted:
msg = humanReadableMsg = this.i18nService.t("userAcceptedTransfer");
break;
case EventType.Organization_ItemOrganization_Declined:
msg = humanReadableMsg = this.i18nService.t("userDeclinedTransfer");
break;
// Policies
case EventType.Policy_Updated: {

View File

@@ -4214,6 +4214,12 @@
}
}
},
"userAcceptedTransfer": {
"message": "Accepted transfer to organization ownership."
},
"userDeclinedTransfer": {
"message": "Revoked for declining transfer to organization ownership."
},
"invitedUserId": {
"message": "Invited user $ID$.",
"placeholders": {
@@ -12411,7 +12417,7 @@
"placeholders": {
"organization": {
"content": "$1",
"example": "My Org Name"
"example": "My Org Name"
}
}
},
@@ -12420,7 +12426,7 @@
"placeholders": {
"organization": {
"content": "$1",
"example": "My Org Name"
"example": "My Org Name"
}
}
},

View File

@@ -79,6 +79,8 @@ export enum EventType {
Organization_CollectionManagement_LimitItemDeletionDisabled = 1615,
Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsEnabled = 1616,
Organization_CollectionManagement_AllowAdminAccessToAllCollectionItemsDisabled = 1617,
Organization_ItemOrganization_Accepted = 1618,
Organization_ItemOrganization_Declined = 1619,
Policy_Updated = 1700,

View File

@@ -3,11 +3,13 @@ import { firstValueFrom, of, Subject } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { CollectionService, CollectionView } from "@bitwarden/admin-console/common";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -37,6 +39,7 @@ describe("DefaultVaultItemsTransferService", () => {
let mockI18nService: MockProxy<I18nService>;
let mockDialogService: MockProxy<DialogService>;
let mockToastService: MockProxy<ToastService>;
let mockEventCollectionService: MockProxy<EventCollectionService>;
let mockConfigService: MockProxy<ConfigService>;
const userId = "user-id" as UserId;
@@ -71,6 +74,7 @@ describe("DefaultVaultItemsTransferService", () => {
mockI18nService = mock<I18nService>();
mockDialogService = mock<DialogService>();
mockToastService = mock<ToastService>();
mockEventCollectionService = mock<EventCollectionService>();
mockConfigService = mock<ConfigService>();
mockI18nService.t.mockImplementation((key) => key);
@@ -85,6 +89,7 @@ describe("DefaultVaultItemsTransferService", () => {
mockI18nService,
mockDialogService,
mockToastService,
mockEventCollectionService,
mockConfigService,
);
});
@@ -774,6 +779,63 @@ describe("DefaultVaultItemsTransferService", () => {
expect(mockDialogService.open).toHaveBeenCalledTimes(4);
expect(mockCipherService.shareManyWithServer).not.toHaveBeenCalled();
});
describe("event logs", () => {
it("logs accepted event when user accepts transfer", async () => {
const personalCiphers = [{ id: "cipher-1" } as CipherView];
setupMocksForEnforcementScenario({
policies: [policy],
organizations: [organization],
ciphers: personalCiphers,
defaultCollection: {
id: collectionId,
organizationId: organizationId,
isDefaultCollection: true,
} as CollectionView,
});
mockDialogService.open.mockReturnValueOnce(
createMockDialogRef(TransferItemsDialogResult.Accepted),
);
mockCipherService.shareManyWithServer.mockResolvedValue(undefined);
await service.enforceOrganizationDataOwnership(userId);
expect(mockEventCollectionService.collect).toHaveBeenCalledWith(
EventType.Organization_ItemOrganization_Accepted,
undefined,
undefined,
organizationId,
);
});
it("logs declined event when user rejects transfer", async () => {
const personalCiphers = [{ id: "cipher-1" } as CipherView];
setupMocksForEnforcementScenario({
policies: [policy],
organizations: [organization],
ciphers: personalCiphers,
defaultCollection: {
id: collectionId,
organizationId: organizationId,
isDefaultCollection: true,
} as CollectionView,
});
mockDialogService.open
.mockReturnValueOnce(createMockDialogRef(TransferItemsDialogResult.Declined))
.mockReturnValueOnce(createMockDialogRef(LeaveConfirmationDialogResult.Confirmed));
await service.enforceOrganizationDataOwnership(userId);
expect(mockEventCollectionService.collect).toHaveBeenCalledWith(
EventType.Organization_ItemOrganization_Declined,
undefined,
undefined,
organizationId,
);
});
});
});
describe("transferInProgress$", () => {

View File

@@ -11,10 +11,12 @@ import {
// eslint-disable-next-line no-restricted-imports
import { CollectionService } from "@bitwarden/admin-console/common";
import { EventCollectionService } from "@bitwarden/common/abstractions/event/event-collection.service";
import { OrganizationService } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyType } from "@bitwarden/common/admin-console/enums";
import { Organization } from "@bitwarden/common/admin-console/models/domain/organization";
import { EventType } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -49,6 +51,7 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
private i18nService: I18nService,
private dialogService: DialogService,
private toastService: ToastService,
private eventCollectionService: EventCollectionService,
private configService: ConfigService,
) {}
@@ -160,6 +163,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
if (!userAcceptedTransfer) {
// TODO: Revoke user from organization if they decline migration and show toast PM-29465
await this.eventCollectionService.collect(
EventType.Organization_ItemOrganization_Declined,
undefined,
undefined,
migrationInfo.enforcingOrganization.id,
);
return;
}
@@ -175,6 +185,13 @@ export class DefaultVaultItemsTransferService implements VaultItemsTransferServi
variant: "success",
message: this.i18nService.t("itemsTransferred"),
});
await this.eventCollectionService.collect(
EventType.Organization_ItemOrganization_Accepted,
undefined,
undefined,
migrationInfo.enforcingOrganization.id,
);
} catch (error) {
this._transferInProgressSubject.next(false);
this.logService.error("Error transferring personal items to organization", error);