mirror of
https://github.com/bitwarden/browser
synced 2025-12-10 21:33:27 +00:00
[PM-24655] Delete an existing Integration (#16382)
This commit is contained in:
@@ -9670,6 +9670,9 @@
|
|||||||
"failedToSaveIntegration": {
|
"failedToSaveIntegration": {
|
||||||
"message": "Failed to save integration. Please try again later."
|
"message": "Failed to save integration. Please try again later."
|
||||||
},
|
},
|
||||||
|
"failedToDeleteIntegration": {
|
||||||
|
"message": "Failed to delete integration. Please try again later."
|
||||||
|
},
|
||||||
"deviceIdMissing": {
|
"deviceIdMissing": {
|
||||||
"message": "Device ID is missing"
|
"message": "Device ID is missing"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export type Integration = {
|
|||||||
*/
|
*/
|
||||||
newBadgeExpiration?: string;
|
newBadgeExpiration?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
isConnected?: boolean;
|
|
||||||
canSetupConnection?: boolean;
|
canSetupConnection?: boolean;
|
||||||
configuration?: string;
|
configuration?: string;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
|||||||
@@ -156,6 +156,34 @@ export class HecOrganizationIntegrationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async deleteHec(
|
||||||
|
organizationId: OrganizationId,
|
||||||
|
OrganizationIntegrationId: OrganizationIntegrationId,
|
||||||
|
OrganizationIntegrationConfigurationId: OrganizationIntegrationConfigurationId,
|
||||||
|
) {
|
||||||
|
if (organizationId != this.organizationId$.getValue()) {
|
||||||
|
throw new Error("Organization ID mismatch");
|
||||||
|
}
|
||||||
|
// delete the configuration first due to foreign key constraint
|
||||||
|
await this.integrationConfigurationApiService.deleteOrganizationIntegrationConfiguration(
|
||||||
|
organizationId,
|
||||||
|
OrganizationIntegrationId,
|
||||||
|
OrganizationIntegrationConfigurationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// delete the integration
|
||||||
|
await this.integrationApiService.deleteOrganizationIntegration(
|
||||||
|
organizationId,
|
||||||
|
OrganizationIntegrationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// update the local observable
|
||||||
|
const updatedIntegrations = this._integrations$
|
||||||
|
.getValue()
|
||||||
|
.filter((i) => i.id !== OrganizationIntegrationId);
|
||||||
|
this._integrations$.next(updatedIntegrations);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a OrganizationIntegration for an OrganizationIntegrationId
|
* Gets a OrganizationIntegration for an OrganizationIntegrationId
|
||||||
* @param integrationId id of the integration
|
* @param integrationId id of the integration
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import { DialogService, ToastService } from "@bitwarden/components";
|
|||||||
import { I18nPipe } from "@bitwarden/ui-common";
|
import { I18nPipe } from "@bitwarden/ui-common";
|
||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
import { openHecConnectDialog } from "../integration-dialog";
|
import { HecConnectDialogResultStatus, openHecConnectDialog } from "../integration-dialog";
|
||||||
|
|
||||||
import { IntegrationCardComponent } from "./integration-card.component";
|
import { IntegrationCardComponent } from "./integration-card.component";
|
||||||
|
|
||||||
jest.mock("../integration-dialog", () => ({
|
jest.mock("../integration-dialog", () => ({
|
||||||
openHecConnectDialog: jest.fn(),
|
openHecConnectDialog: jest.fn(),
|
||||||
|
HecConnectDialogResultStatus: { Edited: "edit", Delete: "delete" },
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("IntegrationCardComponent", () => {
|
describe("IntegrationCardComponent", () => {
|
||||||
@@ -272,7 +273,7 @@ describe("IntegrationCardComponent", () => {
|
|||||||
it("should call updateHec if isUpdateAvailable is true", async () => {
|
it("should call updateHec if isUpdateAvailable is true", async () => {
|
||||||
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
closed: of({
|
closed: of({
|
||||||
success: true,
|
success: HecConnectDialogResultStatus.Edited,
|
||||||
url: "test-url",
|
url: "test-url",
|
||||||
bearerToken: "token",
|
bearerToken: "token",
|
||||||
index: "index",
|
index: "index",
|
||||||
@@ -304,7 +305,7 @@ describe("IntegrationCardComponent", () => {
|
|||||||
|
|
||||||
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
closed: of({
|
closed: of({
|
||||||
success: true,
|
success: HecConnectDialogResultStatus.Edited,
|
||||||
url: "test-url",
|
url: "test-url",
|
||||||
bearerToken: "token",
|
bearerToken: "token",
|
||||||
index: "index",
|
index: "index",
|
||||||
@@ -327,10 +328,66 @@ describe("IntegrationCardComponent", () => {
|
|||||||
expect(mockIntegrationService.updateHec).not.toHaveBeenCalled();
|
expect(mockIntegrationService.updateHec).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should show toast on error", async () => {
|
it("should call deleteHec when a delete is requested", async () => {
|
||||||
|
component.organizationId = "org-id" as any;
|
||||||
|
|
||||||
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
closed: of({
|
closed: of({
|
||||||
success: true,
|
success: HecConnectDialogResultStatus.Delete,
|
||||||
|
url: "test-url",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "index",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockIntegrationService.deleteHec.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await component.setupConnection();
|
||||||
|
|
||||||
|
expect(mockIntegrationService.deleteHec).toHaveBeenCalledWith(
|
||||||
|
"org-id",
|
||||||
|
"integration-id",
|
||||||
|
"config-id",
|
||||||
|
);
|
||||||
|
expect(mockIntegrationService.saveHec).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not call deleteHec if no existing configuration", async () => {
|
||||||
|
component.integrationSettings = {
|
||||||
|
organizationIntegration: null,
|
||||||
|
name: OrganizationIntegrationServiceType.CrowdStrike,
|
||||||
|
} as any;
|
||||||
|
component.organizationId = "org-id" as any;
|
||||||
|
|
||||||
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
|
closed: of({
|
||||||
|
success: HecConnectDialogResultStatus.Delete,
|
||||||
|
url: "test-url",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "index",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
mockIntegrationService.deleteHec.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await component.setupConnection();
|
||||||
|
|
||||||
|
expect(mockIntegrationService.deleteHec).not.toHaveBeenCalledWith(
|
||||||
|
"org-id",
|
||||||
|
"integration-id",
|
||||||
|
"config-id",
|
||||||
|
OrganizationIntegrationServiceType.CrowdStrike,
|
||||||
|
"test-url",
|
||||||
|
"token",
|
||||||
|
"index",
|
||||||
|
);
|
||||||
|
expect(mockIntegrationService.updateHec).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should show toast on error while saving", async () => {
|
||||||
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
|
closed: of({
|
||||||
|
success: HecConnectDialogResultStatus.Edited,
|
||||||
url: "test-url",
|
url: "test-url",
|
||||||
bearerToken: "token",
|
bearerToken: "token",
|
||||||
index: "index",
|
index: "index",
|
||||||
@@ -349,5 +406,28 @@ describe("IntegrationCardComponent", () => {
|
|||||||
message: mockI18nService.t("failedToSaveIntegration"),
|
message: mockI18nService.t("failedToSaveIntegration"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should show toast on error while deleting", async () => {
|
||||||
|
(openHecConnectDialog as jest.Mock).mockReturnValue({
|
||||||
|
closed: of({
|
||||||
|
success: HecConnectDialogResultStatus.Delete,
|
||||||
|
url: "test-url",
|
||||||
|
bearerToken: "token",
|
||||||
|
index: "index",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.spyOn(component, "isUpdateAvailable", "get").mockReturnValue(true);
|
||||||
|
mockIntegrationService.deleteHec.mockRejectedValue(new Error("fail"));
|
||||||
|
|
||||||
|
await component.setupConnection();
|
||||||
|
|
||||||
|
expect(mockIntegrationService.deleteHec).toHaveBeenCalled();
|
||||||
|
expect(toastService.showToast).toHaveBeenCalledWith({
|
||||||
|
variant: "error",
|
||||||
|
title: "",
|
||||||
|
message: mockI18nService.t("failedToDeleteIntegration"),
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ import { OrganizationId } from "@bitwarden/common/types/guid";
|
|||||||
import { DialogService, ToastService } from "@bitwarden/components";
|
import { DialogService, ToastService } from "@bitwarden/components";
|
||||||
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
import { SharedModule } from "@bitwarden/web-vault/app/shared";
|
||||||
|
|
||||||
import { openHecConnectDialog } from "../integration-dialog/index";
|
import {
|
||||||
|
HecConnectDialogResult,
|
||||||
|
HecConnectDialogResultStatus,
|
||||||
|
openHecConnectDialog,
|
||||||
|
} from "../integration-dialog/index";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-integration-card",
|
selector: "app-integration-card",
|
||||||
@@ -142,32 +146,20 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.isUpdateAvailable) {
|
if (result.success === HecConnectDialogResultStatus.Delete) {
|
||||||
const orgIntegrationId = this.integrationSettings.organizationIntegration?.id;
|
await this.deleteHec();
|
||||||
const orgIntegrationConfigurationId =
|
}
|
||||||
this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id;
|
} catch {
|
||||||
|
this.toastService.showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: "",
|
||||||
|
message: this.i18nService.t("failedToDeleteIntegration"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!orgIntegrationId || !orgIntegrationConfigurationId) {
|
try {
|
||||||
throw Error("Organization Integration ID or Configuration ID is missing");
|
if (result.success === HecConnectDialogResultStatus.Edited) {
|
||||||
}
|
await this.saveHec(result);
|
||||||
|
|
||||||
await this.hecOrganizationIntegrationService.updateHec(
|
|
||||||
this.organizationId,
|
|
||||||
orgIntegrationId,
|
|
||||||
orgIntegrationConfigurationId,
|
|
||||||
this.integrationSettings.name as OrganizationIntegrationServiceType,
|
|
||||||
result.url,
|
|
||||||
result.bearerToken,
|
|
||||||
result.index,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await this.hecOrganizationIntegrationService.saveHec(
|
|
||||||
this.organizationId,
|
|
||||||
this.integrationSettings.name as OrganizationIntegrationServiceType,
|
|
||||||
result.url,
|
|
||||||
result.bearerToken,
|
|
||||||
result.index,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
this.toastService.showToast({
|
this.toastService.showToast({
|
||||||
@@ -175,7 +167,55 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy {
|
|||||||
title: "",
|
title: "",
|
||||||
message: this.i18nService.t("failedToSaveIntegration"),
|
message: this.i18nService.t("failedToSaveIntegration"),
|
||||||
});
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveHec(result: HecConnectDialogResult) {
|
||||||
|
if (this.isUpdateAvailable) {
|
||||||
|
// retrieve org integration and configuration ids
|
||||||
|
const orgIntegrationId = this.integrationSettings.organizationIntegration?.id;
|
||||||
|
const orgIntegrationConfigurationId =
|
||||||
|
this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id;
|
||||||
|
|
||||||
|
if (!orgIntegrationId || !orgIntegrationConfigurationId) {
|
||||||
|
throw Error("Organization Integration ID or Configuration ID is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
// update existing integration and configuration
|
||||||
|
await this.hecOrganizationIntegrationService.updateHec(
|
||||||
|
this.organizationId,
|
||||||
|
orgIntegrationId,
|
||||||
|
orgIntegrationConfigurationId,
|
||||||
|
this.integrationSettings.name as OrganizationIntegrationServiceType,
|
||||||
|
result.url,
|
||||||
|
result.bearerToken,
|
||||||
|
result.index,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// create new integration and configuration
|
||||||
|
await this.hecOrganizationIntegrationService.saveHec(
|
||||||
|
this.organizationId,
|
||||||
|
this.integrationSettings.name as OrganizationIntegrationServiceType,
|
||||||
|
result.url,
|
||||||
|
result.bearerToken,
|
||||||
|
result.index,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteHec() {
|
||||||
|
const orgIntegrationId = this.integrationSettings.organizationIntegration?.id;
|
||||||
|
const orgIntegrationConfigurationId =
|
||||||
|
this.integrationSettings.organizationIntegration?.integrationConfiguration[0]?.id;
|
||||||
|
|
||||||
|
if (!orgIntegrationId || !orgIntegrationConfigurationId) {
|
||||||
|
throw Error("Organization Integration ID or Configuration ID is missing");
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.hecOrganizationIntegrationService.deleteHec(
|
||||||
|
this.organizationId,
|
||||||
|
orgIntegrationId,
|
||||||
|
orgIntegrationConfigurationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,19 @@
|
|||||||
<button type="button" bitButton bitDialogClose buttonType="secondary" [disabled]="loading">
|
<button type="button" bitButton bitDialogClose buttonType="secondary" [disabled]="loading">
|
||||||
{{ "cancel" | i18n }}
|
{{ "cancel" | i18n }}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
@if (canDelete) {
|
||||||
|
<div class="tw-ml-auto">
|
||||||
|
<button
|
||||||
|
bitIconButton="bwi-trash"
|
||||||
|
type="button"
|
||||||
|
buttonType="danger"
|
||||||
|
label="'delete' | i18n"
|
||||||
|
[appA11yTitle]="'delete' | i18n"
|
||||||
|
[bitAction]="delete"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</bit-dialog>
|
</bit-dialog>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
ConnectHecDialogComponent,
|
ConnectHecDialogComponent,
|
||||||
HecConnectDialogParams,
|
HecConnectDialogParams,
|
||||||
HecConnectDialogResult,
|
HecConnectDialogResult,
|
||||||
|
HecConnectDialogResultStatus,
|
||||||
openHecConnectDialog,
|
openHecConnectDialog,
|
||||||
} from "./connect-dialog-hec.component";
|
} from "./connect-dialog-hec.component";
|
||||||
|
|
||||||
@@ -65,7 +66,6 @@ describe("ConnectDialogHecComponent", () => {
|
|||||||
imageDarkMode: "test-image-dark.png",
|
imageDarkMode: "test-image-dark.png",
|
||||||
newBadgeExpiration: "2024-12-31",
|
newBadgeExpiration: "2024-12-31",
|
||||||
description: "Test Description",
|
description: "Test Description",
|
||||||
isConnected: false,
|
|
||||||
canSetupConnection: true,
|
canSetupConnection: true,
|
||||||
type: IntegrationType.EVENT,
|
type: IntegrationType.EVENT,
|
||||||
} as Integration;
|
} as Integration;
|
||||||
@@ -155,8 +155,7 @@ describe("ConnectDialogHecComponent", () => {
|
|||||||
bearerToken: "token",
|
bearerToken: "token",
|
||||||
index: "1",
|
index: "1",
|
||||||
service: "Test Service",
|
service: "Test Service",
|
||||||
success: true,
|
success: HecConnectDialogResultStatus.Edited,
|
||||||
error: null,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,10 +17,17 @@ export interface HecConnectDialogResult {
|
|||||||
bearerToken: string;
|
bearerToken: string;
|
||||||
index: string;
|
index: string;
|
||||||
service: string;
|
service: string;
|
||||||
success: boolean;
|
success: HecConnectDialogResultStatusType | null;
|
||||||
error: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HecConnectDialogResultStatus = {
|
||||||
|
Edited: "edit",
|
||||||
|
Delete: "delete",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type HecConnectDialogResultStatusType =
|
||||||
|
(typeof HecConnectDialogResultStatus)[keyof typeof HecConnectDialogResultStatus];
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: "./connect-dialog-hec.component.html",
|
templateUrl: "./connect-dialog-hec.component.html",
|
||||||
imports: [SharedModule],
|
imports: [SharedModule],
|
||||||
@@ -40,6 +47,7 @@ export class ConnectHecDialogComponent implements OnInit {
|
|||||||
@Inject(DIALOG_DATA) protected connectInfo: HecConnectDialogParams,
|
@Inject(DIALOG_DATA) protected connectInfo: HecConnectDialogParams,
|
||||||
protected formBuilder: FormBuilder,
|
protected formBuilder: FormBuilder,
|
||||||
private dialogRef: DialogRef<HecConnectDialogResult>,
|
private dialogRef: DialogRef<HecConnectDialogResult>,
|
||||||
|
private dialogService: DialogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -62,23 +70,51 @@ export class ConnectHecDialogComponent implements OnInit {
|
|||||||
return !!this.hecConfig;
|
return !!this.hecConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = async (): Promise<void> => {
|
get canDelete(): boolean {
|
||||||
const formJson = this.formGroup.getRawValue();
|
return !!this.hecConfig;
|
||||||
|
}
|
||||||
|
|
||||||
const result: HecConnectDialogResult = {
|
submit = async (): Promise<void> => {
|
||||||
integrationSettings: this.connectInfo.settings,
|
if (this.formGroup.invalid) {
|
||||||
url: formJson.url || "",
|
this.formGroup.markAllAsTouched();
|
||||||
bearerToken: formJson.bearerToken || "",
|
return;
|
||||||
index: formJson.index || "",
|
}
|
||||||
service: formJson.service || "",
|
const result = this.getHecConnectDialogResult(HecConnectDialogResultStatus.Edited);
|
||||||
success: true,
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.dialogRef.close(result);
|
this.dialogRef.close(result);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
delete = async (): Promise<void> => {
|
||||||
|
const confirmed = await this.dialogService.openSimpleDialog({
|
||||||
|
title: { key: "deleteItem" },
|
||||||
|
content: {
|
||||||
|
key: "deleteItemConfirmation",
|
||||||
|
},
|
||||||
|
type: "warning",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
const result = this.getHecConnectDialogResult(HecConnectDialogResultStatus.Delete);
|
||||||
|
this.dialogRef.close(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private getHecConnectDialogResult(
|
||||||
|
status: HecConnectDialogResultStatusType,
|
||||||
|
): HecConnectDialogResult {
|
||||||
|
const formJson = this.formGroup.getRawValue();
|
||||||
|
|
||||||
|
return {
|
||||||
|
integrationSettings: this.connectInfo.settings,
|
||||||
|
url: formJson.url || "",
|
||||||
|
bearerToken: formJson.bearerToken || "",
|
||||||
|
index: formJson.index || "",
|
||||||
|
service: formJson.service || "",
|
||||||
|
success: status,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openHecConnectDialog(
|
export function openHecConnectDialog(
|
||||||
|
|||||||
@@ -253,7 +253,6 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
|||||||
image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
|
image: "../../../../../../../images/integrations/logo-crowdstrike-black.svg",
|
||||||
type: IntegrationType.EVENT,
|
type: IntegrationType.EVENT,
|
||||||
description: "crowdstrikeEventIntegrationDesc",
|
description: "crowdstrikeEventIntegrationDesc",
|
||||||
isConnected: false,
|
|
||||||
canSetupConnection: true,
|
canSetupConnection: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -265,6 +264,11 @@ export class AdminConsoleIntegrationsComponent implements OnInit, OnDestroy {
|
|||||||
this.hecOrganizationIntegrationService.integrations$
|
this.hecOrganizationIntegrationService.integrations$
|
||||||
.pipe(takeUntil(this.destroy$))
|
.pipe(takeUntil(this.destroy$))
|
||||||
.subscribe((integrations) => {
|
.subscribe((integrations) => {
|
||||||
|
// reset all integrations to null first - in case one was deleted
|
||||||
|
this.integrationsList.forEach((i) => {
|
||||||
|
i.organizationIntegration = null;
|
||||||
|
});
|
||||||
|
|
||||||
integrations.map((integration) => {
|
integrations.map((integration) => {
|
||||||
const item = this.integrationsList.find((i) => i.name === integration.serviceType);
|
const item = this.integrationsList.find((i) => i.name === integration.serviceType);
|
||||||
if (item) {
|
if (item) {
|
||||||
|
|||||||
Reference in New Issue
Block a user