diff --git a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html
index a53ede221c..60c8410f85 100644
--- a/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html
+++ b/bitwarden_license/bit-web/src/app/secrets-manager/overview/overview.component.html
@@ -2,7 +2,7 @@
-
+
;
constructor(
@@ -78,6 +85,7 @@ export class OverviewComponent implements OnInit, OnDestroy {
private serviceAccountService: ServiceAccountService,
private dialogService: DialogService,
private organizationService: OrganizationService,
+ private stateService: StateService,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService
) {}
@@ -97,37 +105,47 @@ export class OverviewComponent implements OnInit, OnDestroy {
this.organizationId = org.id;
this.organizationName = org.name;
this.userIsAdmin = org.isAdmin;
+ this.loading = true;
});
const projects$ = combineLatest([
orgId$,
this.projectService.project$.pipe(startWith(null)),
- ]).pipe(switchMap(([orgId]) => this.projectService.getProjects(orgId)));
+ ]).pipe(
+ switchMap(([orgId]) => this.projectService.getProjects(orgId)),
+ share()
+ );
const secrets$ = combineLatest([orgId$, this.secretService.secret$.pipe(startWith(null))]).pipe(
- switchMap(([orgId]) => this.secretService.getSecrets(orgId))
+ switchMap(([orgId]) => this.secretService.getSecrets(orgId)),
+ share()
);
const serviceAccounts$ = combineLatest([
orgId$,
this.serviceAccountService.serviceAccount$.pipe(startWith(null)),
- ]).pipe(switchMap(([orgId]) => this.serviceAccountService.getServiceAccounts(orgId)));
+ ]).pipe(
+ switchMap(([orgId]) => this.serviceAccountService.getServiceAccounts(orgId)),
+ share()
+ );
- this.view$ = combineLatest([projects$, secrets$, serviceAccounts$]).pipe(
- map(([projects, secrets, serviceAccounts]) => {
- return {
- latestProjects: this.getRecentItems(projects, this.tableSize),
- latestSecrets: this.getRecentItems(secrets, this.tableSize),
- allProjects: projects,
- allSecrets: secrets,
- tasks: {
- importSecrets: secrets.length > 0,
- createSecret: secrets.length > 0,
- createProject: projects.length > 0,
- createServiceAccount: serviceAccounts.length > 0,
- },
- };
- })
+ this.view$ = orgId$.pipe(
+ switchMap((orgId) =>
+ combineLatest([projects$, secrets$, serviceAccounts$]).pipe(
+ switchMap(async ([projects, secrets, serviceAccounts]) => ({
+ latestProjects: this.getRecentItems(projects, this.tableSize),
+ latestSecrets: this.getRecentItems(secrets, this.tableSize),
+ allProjects: projects,
+ allSecrets: secrets,
+ tasks: await this.saveCompletedTasks(orgId, {
+ importSecrets: secrets.length > 0,
+ createSecret: secrets.length > 0,
+ createProject: projects.length > 0,
+ createServiceAccount: serviceAccounts.length > 0,
+ }),
+ }))
+ )
+ )
);
// Refresh onboarding status when orgId changes by fetching the first value from view$.
@@ -138,6 +156,7 @@ export class OverviewComponent implements OnInit, OnDestroy {
)
.subscribe((view) => {
this.showOnboarding = Object.values(view.tasks).includes(false);
+ this.loading = false;
});
}
@@ -154,6 +173,29 @@ export class OverviewComponent implements OnInit, OnDestroy {
.slice(0, length) as T;
}
+ private async saveCompletedTasks(
+ organizationId: string,
+ orgTasks: OrganizationTasks
+ ): Promise {
+ const prevTasks = ((await this.stateService.getSMOnboardingTasks()) || {}) as Tasks;
+ const newlyCompletedOrgTasks = Object.fromEntries(
+ Object.entries(orgTasks).filter(([_k, v]) => v === true)
+ );
+ const nextOrgTasks = {
+ importSecrets: false,
+ createSecret: false,
+ createProject: false,
+ createServiceAccount: false,
+ ...prevTasks[organizationId],
+ ...newlyCompletedOrgTasks,
+ };
+ this.stateService.setSMOnboardingTasks({
+ ...prevTasks,
+ [organizationId]: nextOrgTasks,
+ });
+ return nextOrgTasks as OrganizationTasks;
+ }
+
// Projects ---
openEditProject(projectId: string) {
diff --git a/libs/common/src/abstractions/state.service.ts b/libs/common/src/abstractions/state.service.ts
index 51b939893d..c244c34ae0 100644
--- a/libs/common/src/abstractions/state.service.ts
+++ b/libs/common/src/abstractions/state.service.ts
@@ -357,4 +357,12 @@ export abstract class StateService {
getAvatarColor: (options?: StorageOptions) => Promise;
setAvatarColor: (value: string, options?: StorageOptions) => Promise;
+
+ getSMOnboardingTasks: (
+ options?: StorageOptions
+ ) => Promise>>;
+ setSMOnboardingTasks: (
+ value: Record>,
+ options?: StorageOptions
+ ) => Promise;
}
diff --git a/libs/common/src/models/domain/account.ts b/libs/common/src/models/domain/account.ts
index 821800e701..175eeaa3b4 100644
--- a/libs/common/src/models/domain/account.ts
+++ b/libs/common/src/models/domain/account.ts
@@ -238,6 +238,7 @@ export class AccountSettings {
serverConfig?: ServerConfigData;
approveLoginRequests?: boolean;
avatarColor?: string;
+ smOnboardingTasks?: Record>;
static fromJSON(obj: Jsonify): AccountSettings {
if (obj == null) {
diff --git a/libs/common/src/services/state.service.ts b/libs/common/src/services/state.service.ts
index 11aab653db..48d83e841d 100644
--- a/libs/common/src/services/state.service.ts
+++ b/libs/common/src/services/state.service.ts
@@ -2364,6 +2364,28 @@ export class StateService<
);
}
+ async getSMOnboardingTasks(
+ options?: StorageOptions
+ ): Promise>> {
+ return (
+ await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
+ )?.settings?.smOnboardingTasks;
+ }
+
+ async setSMOnboardingTasks(
+ value: Record>,
+ options?: StorageOptions
+ ): Promise {
+ const account = await this.getAccount(
+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
+ );
+ account.settings.smOnboardingTasks = value;
+ return await this.saveAccount(
+ account,
+ this.reconcileOptions(options, await this.defaultOnDiskLocalOptions())
+ );
+ }
+
protected async getGlobals(options: StorageOptions): Promise {
let globals: TGlobalState;
if (this.useMemory(options.storageLocation)) {