perf: query projected milestones for onboarding view (#6760)

* feat: support list milestones api

* show milestones in onboarding view

* add authenticated milestone

* add icon to login milestone

* update main

* lint

* fix import

* fix import

* lint

* reuse proto milestone type mapping
This commit is contained in:
Elio Bischof
2023-10-25 11:16:34 +00:00
committed by GitHub
parent 73dbf31368
commit 1c839e308b
27 changed files with 445 additions and 180 deletions
+49 -49
View File
@@ -152,6 +152,8 @@ import {
ListLoginPolicyMultiFactorsResponse,
ListLoginPolicySecondFactorsRequest,
ListLoginPolicySecondFactorsResponse,
ListMilestonesRequest,
ListMilestonesResponse,
ListProvidersRequest,
ListProvidersResponse,
ListSecretGeneratorsRequest,
@@ -296,85 +298,77 @@ import { SearchQuery } from '../proto/generated/zitadel/member_pb';
import { ListQuery } from '../proto/generated/zitadel/object_pb';
import { GrpcService } from './grpc.service';
import { StorageLocation, StorageService } from './storage.service';
import {
IsReachedQuery,
Milestone,
MilestoneQuery,
MilestoneType,
} from '../proto/generated/zitadel/milestone/v1/milestone_pb';
export interface OnboardingActions {
order: number;
eventType: string;
oneof: string[];
link: string | string[];
milestoneType: MilestoneType;
link: string;
fragment?: string | undefined;
iconClasses?: string;
darkcolor: string;
lightcolor: string;
aggregateType: string;
}
type OnboardingEvent = {
type OnboardingMilestone = {
order: number;
link: string;
fragment: string | undefined;
event: Event.AsObject | undefined;
reached: Milestone.AsObject | undefined;
iconClasses?: string;
darkcolor: string;
lightcolor: string;
};
type OnboardingEventEntries = Array<[string, OnboardingEvent]> | [];
type OnboardingMilestoneEntries = Array<[string, OnboardingMilestone]> | [];
@Injectable({
providedIn: 'root',
})
export class AdminService {
private readonly milestoneTypePrefixLength = 'MILESTONE_TYPE_'.length;
public hideOnboarding: boolean = false;
public loadEvents: Subject<OnboardingActions[]> = new Subject();
public loadMilestones: Subject<OnboardingActions[]> = new Subject();
public onboardingLoading: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
public progressEvents$: Observable<OnboardingEventEntries> = this.loadEvents.pipe(
public progressMilestones$: Observable<OnboardingMilestoneEntries> = this.loadMilestones.pipe(
tap(() => this.onboardingLoading.next(true)),
switchMap((actions) => {
const searchForTypes = actions.map((oe) => oe.oneof).flat();
const aggregateTypes = actions.map((oe) => oe.aggregateType);
const eventsReq = new ListEventsRequest()
.setAsc(true)
.setEventTypesList(searchForTypes)
.setAggregateTypesList(aggregateTypes)
.setAsc(false);
return from(this.listEvents(eventsReq)).pipe(
map((events) => {
const el = events.toObject().eventsList.filter((e) => e.editor?.service !== 'System-API' && e.editor?.userId);
let obj: { [type: string]: OnboardingEvent } = {};
const milestonesListQuery = new ListQuery();
milestonesListQuery.setAsc(true);
milestonesListQuery.setLimit(20);
const milestoneIsReachedQuery = new IsReachedQuery().setReached(true);
const milestonesQuery = new MilestoneQuery().setIsReachedQuery(milestoneIsReachedQuery);
const milestonesReq = new ListMilestonesRequest().setQuery(milestonesListQuery).setQueriesList([milestonesQuery]);
return from(this.listMilestones(milestonesReq)).pipe(
map((reachedMilestones) => {
let obj: { [type: string]: OnboardingMilestone } = {};
actions.map((action) => {
const filtered = el.filter((event) => event.type?.type && action.oneof.includes(event.type.type));
(obj as any)[action.eventType] = filtered.length
? {
order: action.order,
link: action.link,
fragment: action.fragment,
event: filtered[0],
iconClasses: action.iconClasses,
darkcolor: action.darkcolor,
lightcolor: action.lightcolor,
}
: {
order: action.order,
link: action.link,
fragment: action.fragment,
event: undefined,
iconClasses: action.iconClasses,
darkcolor: action.darkcolor,
lightcolor: action.lightcolor,
};
obj[Object.keys(MilestoneType)[action.milestoneType].substring(this.milestoneTypePrefixLength)] = {
order: action.order,
link: action.link,
fragment: action.fragment,
iconClasses: action.iconClasses,
darkcolor: action.darkcolor,
lightcolor: action.lightcolor,
reached: reachedMilestones.resultList.find((reached) => {
return reached.type.valueOf() == action.milestoneType;
}),
};
});
const toArray = Object.entries(obj).sort(([key0, a], [key1, b]) => a.order - b.order);
const toDo = toArray.filter(([key, value]) => value.event === undefined);
const done = toArray.filter(([key, value]) => !!value.event);
const toDo = toArray.filter(([key, value]) => value.reached === undefined);
const done = toArray.filter(([key, value]) => !!value.reached);
return [...toDo, ...done];
}),
tap((events) => {
const total = events.length;
const done = events.map(([type, value]) => value.event !== undefined).filter((res) => !!res).length;
tap((milestones) => {
const total = milestones.length;
const done = milestones.map(([type, value]) => value.reached !== undefined).filter((res) => !!res).length;
const percentage = Math.round((done / total) * 100);
this.progressDone.next(done);
this.progressTotal.next(total);
@@ -390,7 +384,9 @@ export class AdminService {
}),
);
public progressEvents: BehaviorSubject<OnboardingEventEntries> = new BehaviorSubject<OnboardingEventEntries>([]);
public progressMilestones: BehaviorSubject<OnboardingMilestoneEntries> = new BehaviorSubject<OnboardingMilestoneEntries>(
[],
);
public progressPercentage: BehaviorSubject<number> = new BehaviorSubject(0);
public progressDone: BehaviorSubject<number> = new BehaviorSubject(0);
public progressTotal: BehaviorSubject<number> = new BehaviorSubject(0);
@@ -400,7 +396,7 @@ export class AdminService {
private readonly grpcService: GrpcService,
private storageService: StorageService,
) {
this.progressEvents$.subscribe(this.progressEvents);
this.progressMilestones$.subscribe(this.progressMilestones);
this.hideOnboarding =
this.storageService.getItem('onboarding-dismissed', StorageLocation.local) === 'true' ? true : false;
@@ -1254,4 +1250,8 @@ export class AdminService {
return this.grpcService.admin.updateIAMMember(req, null).then((resp) => resp.toObject());
}
public listMilestones(req: ListMilestonesRequest): Promise<ListMilestonesResponse.AsObject> {
return this.grpcService.admin.listMilestones(req, null).then((resp) => resp.toObject());
}
}