mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 16:45:13 +00:00
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:
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user