Skip to content

Commit

Permalink
feat: added DbxFirebaseDevelopmentSchedulerWidgetComponent
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed Oct 5, 2022
1 parent 713bac5 commit 99c5712
Show file tree
Hide file tree
Showing 9 changed files with 213 additions and 60 deletions.
41 changes: 35 additions & 6 deletions packages/dbx-firebase/src/lib/development/development.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,70 @@ import { MatIconModule } from '@angular/material/icon';
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { DbxInjectionComponentModule } from '@dereekb/dbx-core';
import { DbxActionModule, DbxRouterAnchorModule, DbxButtonModule, DbxReadableErrorModule, DbxPopupInteractionModule, DbxTwoColumnLayoutModule, DbxBlockLayoutModule, DbxWidgetModule } from '@dereekb/dbx-web';
import { DbxFirebaseDevelopmentWidgetEntry, DEFAULT_FIREBASE_DEVELOPMENT_WIDGET_PROVIDERS_TOKEN } from './development.widget.service';
import { DbxActionModule, DbxRouterAnchorModule, DbxButtonModule, DbxReadableErrorModule, DbxPopupInteractionModule, DbxTwoColumnLayoutModule, DbxBlockLayoutModule, DbxWidgetModule, DbxListLayoutModule, DbxTextModule } from '@dereekb/dbx-web';
import { DEFAULT_FIREBASE_DEVELOPMENT_WIDGET_PROVIDERS_TOKEN } from './development.widget.service';
import { DbxFormActionModule, DbxFormFormlyTextFieldModule, DbxFormIoModule, DbxFormlyModule, DbxFormModule } from '@dereekb/dbx-form';
import { DbxFirebaseDevelopmentPopupComponent } from './development.popup.component';
import { DbxFirebaseDevelopmentPopupContentComponent } from './development.popup.content.component';
import { DbxFirebaseDevelopmentDirective } from './development.popup.directive';
import { DEFAULT_FIREBASE_DEVELOPMENT_ENABLED_TOKEN } from './development.service';
import { DbxFirebaseDevelopmentPopupContentFormComponent } from './development.popup.content.form.component';
import { DbxFirebaseDevelopmentSchedulerService } from './development.scheduler.service';
import { DbxFirebaseDevelopmentWidgetEntry } from './development.widget';
import { DbxFirebaseDevelopmentSchedulerListComponent, DbxFirebaseDevelopmentSchedulerListViewComponent, DbxFirebaseDevelopmentSchedulerListViewItemComponent } from './development.scheduler.list.component';
import { DbxFirebaseDevelopmentSchedulerWidgetComponent, developmentFirebaseServerSchedulerWidgetEntry } from './development.scheduler.widget.component';

export abstract class DbxFirebaseDevelopmentModuleRootConfig {
abstract readonly enabled: boolean;
abstract readonly entries: DbxFirebaseDevelopmentWidgetEntry[];
abstract readonly addDevelopmentSchedulerWidget?: boolean;
}

/**
* Contains components related to logging in.
*/
@NgModule({
imports: [CommonModule, MatIconModule, DbxWidgetModule, DbxBlockLayoutModule, DbxTwoColumnLayoutModule, MatButtonModule, DbxRouterAnchorModule, DbxPopupInteractionModule, DbxFormIoModule, DbxFormModule, DbxFormlyModule, DbxFormActionModule, DbxFormFormlyTextFieldModule, DbxReadableErrorModule, DbxActionModule, DbxButtonModule, DbxInjectionComponentModule],
declarations: [DbxFirebaseDevelopmentPopupContentFormComponent, DbxFirebaseDevelopmentDirective, DbxFirebaseDevelopmentPopupComponent, DbxFirebaseDevelopmentPopupContentComponent],
exports: [DbxFirebaseDevelopmentDirective, DbxFirebaseDevelopmentPopupComponent, DbxFirebaseDevelopmentPopupContentComponent]
imports: [CommonModule, MatIconModule, DbxWidgetModule, DbxTextModule, DbxBlockLayoutModule, DbxTwoColumnLayoutModule, MatButtonModule, DbxRouterAnchorModule, DbxPopupInteractionModule, DbxFormIoModule, DbxFormModule, DbxFormlyModule, DbxFormActionModule, DbxFormFormlyTextFieldModule, DbxReadableErrorModule, DbxActionModule, DbxButtonModule, DbxInjectionComponentModule, DbxListLayoutModule],
declarations: [
//
DbxFirebaseDevelopmentPopupContentFormComponent,
DbxFirebaseDevelopmentDirective,
DbxFirebaseDevelopmentPopupComponent,
DbxFirebaseDevelopmentPopupContentComponent,
DbxFirebaseDevelopmentSchedulerWidgetComponent,
DbxFirebaseDevelopmentSchedulerListComponent,
DbxFirebaseDevelopmentSchedulerListViewComponent,
DbxFirebaseDevelopmentSchedulerListViewItemComponent
],
exports: [
//
DbxFirebaseDevelopmentDirective,
DbxFirebaseDevelopmentPopupComponent,
DbxFirebaseDevelopmentPopupContentComponent,
DbxFirebaseDevelopmentSchedulerWidgetComponent,
DbxFirebaseDevelopmentSchedulerListComponent,
DbxFirebaseDevelopmentSchedulerListViewComponent,
DbxFirebaseDevelopmentSchedulerListViewItemComponent
]
})
export class DbxFirebaseDevelopmentModule {
constructor(readonly dbxFirebaseDevelopmentSchedulerService: DbxFirebaseDevelopmentSchedulerService) {
dbxFirebaseDevelopmentSchedulerService.init();
}

static forRoot(config: DbxFirebaseDevelopmentModuleRootConfig): ModuleWithProviders<DbxFirebaseDevelopmentModule> {
let entries = config.entries;

if (config.addDevelopmentSchedulerWidget !== false) {
entries = [developmentFirebaseServerSchedulerWidgetEntry(), ...config.entries];
}

return {
ngModule: DbxFirebaseDevelopmentModule,
providers: [
{
provide: DEFAULT_FIREBASE_DEVELOPMENT_WIDGET_PROVIDERS_TOKEN,
useValue: config.entries
useValue: entries
},
{
provide: DEFAULT_FIREBASE_DEVELOPMENT_ENABLED_TOKEN,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
<dbx-two-block left>
<div>
<h4>Info</h4>
<p>Dev Scheduler: {{ schedulerEnabled$ | async }}</p>
<p>Scheduler (Running): {{ schedulerRunning$ | async }} : {{ schedulerInterval$ | async }}s : {{ schedulerError$ | async }}</p>
</div>
<div>
<h4>Tools</h4>
<dbx-firebase-development-popup-content-form [config]="formConfig$ | async"></dbx-firebase-development-popup-content-form>
<div dbxAction dbxActionEnforceModified dbxActionAutoTrigger instantTrigger dbxActionLogger [dbxActionHandler]="handleFormUpdate">
<dbx-firebase-development-popup-content-form dbxActionForm [dbxFormSource]="formData$" dbxFormSourceMode="always" [dbxActionFormModified]="isFormModified" [config]="formConfig$ | async"></dbx-firebase-development-popup-content-form>
</div>
</div>
</dbx-two-block>
<dbx-two-column-right right [header]="(rightTitle$ | async) || ''">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { DbxAuthService } from '@dereekb/dbx-core';
import { DbxAuthService, HandleActionWithContext } from '@dereekb/dbx-core';
import { Component, OnDestroy, OnInit } from '@angular/core';
import { DbxWidgetDataPair, TwoColumnsContextStore } from '@dereekb/dbx-web';
import { DevelopmentFirebaseFunctionSpecifier } from '@dereekb/firebase';
import { filterMaybe, SubscriptionObject } from '@dereekb/rxjs';
import { filterMaybe, IsModifiedFunction, SubscriptionObject, tapLog } from '@dereekb/rxjs';
import { Maybe } from '@dereekb/util';
import { BehaviorSubject, distinctUntilChanged, map, shareReplay, combineLatest, Observable } from 'rxjs';
import { first, BehaviorSubject, distinctUntilChanged, map, shareReplay, combineLatest, Observable } from 'rxjs';
import { DbxFirebaseDevelopmentWidgetService } from './development.widget.service';
import { DbxFirebaseDevelopmentSchedulerService } from './development.scheduler.service';
import { DbxFirebaseDevelopmentPopupContentFormValue } from './development.popup.content.form.component';
import { msToSeconds } from '@dereekb/date';
import { DEVELOPMENT_FIREBASE_SERVER_SCHEDULER_WIDGET_KEY } from './development.scheduler.widget.component';

@Component({
selector: 'dbx-firebase-development-popup-content',
Expand All @@ -19,21 +22,14 @@ export class DbxFirebaseDevelopmentPopupContentComponent implements OnInit, OnDe

readonly entries = this.dbxFirebaseDevelopmentWidgetService.getEntries();

private _activeEntrySelector = new BehaviorSubject<Maybe<DevelopmentFirebaseFunctionSpecifier>>(undefined);
private _activeEntrySelector = new BehaviorSubject<Maybe<DevelopmentFirebaseFunctionSpecifier>>(DEVELOPMENT_FIREBASE_SERVER_SCHEDULER_WIDGET_KEY);

readonly isLoggedIn$ = this.dbxAuthService.isLoggedIn$;

readonly entries$ = this.isLoggedIn$.pipe(
distinctUntilChanged(),
map((isLoggedIn) => {
let entries = this.entries;

if (!isLoggedIn) {
entries = this.entries.filter((x) => x.auth === true);
}

return entries;
}),
map((isLoggedIn) => this.entries),
tapLog('Entries'),
shareReplay(1)
);

Expand All @@ -45,12 +41,28 @@ export class DbxFirebaseDevelopmentPopupContentComponent implements OnInit, OnDe
);

readonly showRight$ = this.currentActiveEntry$.pipe(map((x) => x != null));
readonly activeEntry$ = this.currentActiveEntry$.pipe(filterMaybe(), shareReplay(1));
readonly activeEntry$ = this.currentActiveEntry$.pipe(filterMaybe(), distinctUntilChanged(), shareReplay(1));

readonly rightTitle$ = this.activeEntry$.pipe(map((x) => x.label));
readonly widgetConfig$: Observable<DbxWidgetDataPair> = this.activeEntry$.pipe(map((x) => ({ data: undefined, type: x.widget.type })));
readonly widgetConfig$: Observable<DbxWidgetDataPair> = this.activeEntry$.pipe(
map((x) => ({ data: undefined, type: x.widget.type })),
shareReplay(1)
);

readonly schedulerEnabled$ = this.dbxFirebaseDevelopmentSchedulerService.enabled$;
readonly schedulerRunning$ = this.dbxFirebaseDevelopmentSchedulerService.running$;
readonly schedulerInterval$ = this.dbxFirebaseDevelopmentSchedulerService.timerInterval$.pipe(
map((x) => msToSeconds(x)),
shareReplay(1)
);
readonly schedulerError$ = this.dbxFirebaseDevelopmentSchedulerService.error$.pipe(
map((x) => (x ? 'Error Occured' : 'Ok')),
shareReplay(1)
);

readonly formData$: Observable<DbxFirebaseDevelopmentPopupContentFormValue> = this._activeEntrySelector.pipe(
distinctUntilChanged(),
map((specifier) => ({ specifier }))
);

constructor(readonly twoColumnsContextStore: TwoColumnsContextStore, readonly dbxAuthService: DbxAuthService, readonly dbxFirebaseDevelopmentWidgetService: DbxFirebaseDevelopmentWidgetService, readonly dbxFirebaseDevelopmentSchedulerService: DbxFirebaseDevelopmentSchedulerService) {}

Expand All @@ -65,6 +77,18 @@ export class DbxFirebaseDevelopmentPopupContentComponent implements OnInit, OnDe
this._activeEntrySelector.complete();
}

readonly handleFormUpdate: HandleActionWithContext<DbxFirebaseDevelopmentPopupContentFormValue, void> = (value, context) => {
this._activeEntrySelector.next(value.specifier);
context.success();
};

readonly isFormModified: IsModifiedFunction<DbxFirebaseDevelopmentPopupContentFormValue> = (value) => {
return this._activeEntrySelector.pipe(
map((currentSelector) => value.specifier !== currentSelector),
first()
);
};

clearSelection() {
this._activeEntrySelector.next(undefined);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Component } from '@angular/core';
import { provideFormlyContext, AbstractConfigAsyncFormlyFormDirective, AbstractAsyncFormlyFormDirective, AbstractSyncFormlyFormDirective, pickableItemChipField, filterPickableItemFieldValuesByLabel, SearchableValueFieldDisplayFn, SearchableValueFieldDisplayValue, SearchableValueFieldValue } from '@dereekb/dbx-form';
import { Maybe } from '@dereekb/util';
import { FormlyFieldConfig } from '@ngx-formly/core';
import { map, Observable, of } from 'rxjs';
import { DbxFirebaseDevelopmentWidgetEntry } from './development.widget.service';
import { DbxFirebaseDevelopmentWidgetEntry } from './development.widget';

export interface DbxFirebaseDevelopmentPopupContentFormInput {
readonly entries: DbxFirebaseDevelopmentWidgetEntry[];
}

export interface DbxFirebaseDevelopmentPopupContentFormValue {
readonly specifier?: string;
readonly specifier?: Maybe<string>;
}

export const DISPLAY_FOR_STRING_VALUE: SearchableValueFieldDisplayFn<string, DbxFirebaseDevelopmentWidgetEntry> = (values: SearchableValueFieldValue<string, DbxFirebaseDevelopmentWidgetEntry>[]) => {
Expand All @@ -35,7 +36,8 @@ export class DbxFirebaseDevelopmentPopupContentFormComponent extends AbstractCon
description: 'Pick a tool to get started.',
filterValues: filterPickableItemFieldValuesByLabel,
loadValues: () => of(config.entries.map((y) => ({ value: y.widget.type, meta: y }))),
displayForValue: DISPLAY_FOR_STRING_VALUE
displayForValue: DISPLAY_FOR_STRING_VALUE,
asArrayValue: false
})
];
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { Guestbook } from '@dereekb/demo-firebase';
import { Component, Inject } from '@angular/core';
import { AbstractDbxSelectionListWrapperDirective, AbstractDbxValueListViewItemComponent, AbstractDbxSelectionListViewDirective, DEFAULT_LIST_WRAPPER_DIRECTIVE_TEMPLATE, DbxSelectionValueListViewConfig, provideDbxListView, DEFAULT_DBX_SELECTION_VALUE_LIST_DIRECTIVE_TEMPLATE, DbxValueAsListItem, provideDbxListViewWrapper, DBX_VALUE_LIST_VIEW_ITEM, DbxValueListItem } from '@dereekb/dbx-web';
import { from, of } from 'rxjs';
import { ScheduledFunctionDevelopmentFirebaseFunctionListEntry } from '@dereekb/firebase';
import { DbxFirebaseDevelopmentSchedulerService } from './development.scheduler.service';
import { HandleActionWithContext } from '@dereekb/dbx-core';

export type ScheduledFunctionDevelopmentFirebaseFunctionListEntryWithSelection = DbxValueAsListItem<ScheduledFunctionDevelopmentFirebaseFunctionListEntry>;

@Component({
selector: 'dbx-firebase-development-scheduler-list',
template: DEFAULT_LIST_WRAPPER_DIRECTIVE_TEMPLATE,
providers: provideDbxListViewWrapper(DbxFirebaseDevelopmentSchedulerListComponent)
})
export class DbxFirebaseDevelopmentSchedulerListComponent extends AbstractDbxSelectionListWrapperDirective<ScheduledFunctionDevelopmentFirebaseFunctionListEntry> {
constructor() {
super({
componentClass: DbxFirebaseDevelopmentSchedulerListViewComponent,
defaultSelectionMode: 'view'
});
}
}

@Component({
template: DEFAULT_DBX_SELECTION_VALUE_LIST_DIRECTIVE_TEMPLATE,
providers: provideDbxListView(DbxFirebaseDevelopmentSchedulerListViewComponent)
})
export class DbxFirebaseDevelopmentSchedulerListViewComponent extends AbstractDbxSelectionListViewDirective<ScheduledFunctionDevelopmentFirebaseFunctionListEntry> {
readonly config: DbxSelectionValueListViewConfig<ScheduledFunctionDevelopmentFirebaseFunctionListEntryWithSelection> = {
componentClass: DbxFirebaseDevelopmentSchedulerListViewItemComponent,
mapValuesToItemValues: (x) => of(x.map((y) => ({ ...y, icon: y.icon, itemValue: y })))
};
}

@Component({
template: `
<div dbxAction dbxActionValue fastTrigger [dbxActionHandler]="handleRun">
<dbx-button dbxActionButton [text]="'Run ' + name"></dbx-button>
<dbx-success *dbxActionHasSuccess="3000" dbxActionSuccess>Success</dbx-success>
</div>
`
})
export class DbxFirebaseDevelopmentSchedulerListViewItemComponent extends AbstractDbxValueListViewItemComponent<ScheduledFunctionDevelopmentFirebaseFunctionListEntry> {
get name() {
return this.itemValue.name;
}

constructor(@Inject(DBX_VALUE_LIST_VIEW_ITEM) item: DbxValueListItem<ScheduledFunctionDevelopmentFirebaseFunctionListEntry>, readonly dbxFirebaseDevelopmentSchedulerService: DbxFirebaseDevelopmentSchedulerService) {
super(item);
}

readonly handleRun: HandleActionWithContext<unknown, unknown> = (value, context) => {
context.startWorkingWithObservable(from(this.dbxFirebaseDevelopmentSchedulerService.runScheduledFunction(this.name)));
};
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { DbxAuthService } from '@dereekb/dbx-core';
import { tap, switchMap, BehaviorSubject, Observable, interval, combineLatest, map, exhaustMap } from 'rxjs';
import { Initialized, iterableToArray, Milliseconds, PromiseUtility } from '@dereekb/util';
import { tap, switchMap, BehaviorSubject, Observable, interval, combineLatest, map, exhaustMap, distinctUntilChanged, shareReplay } from 'rxjs';
import { Initialized, Milliseconds, PromiseUtility } from '@dereekb/util';
import { Inject, Injectable, InjectionToken, Optional } from '@angular/core';
import { DbxWidgetEntry, DbxWidgetType } from '@dereekb/dbx-web';
import { lazyFrom, SubscriptionObject, switchMapToDefault, switchMapWhileTrue, tapLog } from '@dereekb/rxjs';
import { lazyFrom, SubscriptionObject, switchMapWhileTrue, tapLog } from '@dereekb/rxjs';
import { FirebaseDevelopmentFunctions, ScheduledFunctionDevelopmentFirebaseFunctionListEntry, ScheduledFunctionDevelopmentFirebaseFunctionListResult, ScheduledFunctionDevelopmentFunctionTypeEnum } from '@dereekb/firebase';

/**
Expand All @@ -20,9 +19,17 @@ export const DEFAULT_FIREBASE_DEVELOPMENT_SCHEDULER_ENABLED_TOKEN = new Injectio
export class DbxFirebaseDevelopmentSchedulerService implements Initialized {
private _sub = new SubscriptionObject();
private _enabled = new BehaviorSubject<boolean>(this._startEnabled !== false);
private _timer = new BehaviorSubject<Milliseconds>(60 * 1000);
private _timerInterval = new BehaviorSubject<Milliseconds>(60 * 1000);
private _error = new BehaviorSubject<boolean>(false);

readonly enabled$ = this._enabled.asObservable();
readonly running$ = combineLatest([this._enabled, this.dbxAuthService.authUserState$.pipe(map((x) => x === 'user'))]).pipe(
map(([enabled, userReady]) => enabled && userReady),
distinctUntilChanged(),
shareReplay(1)
);
readonly timerInterval$ = this._timerInterval.asObservable();
readonly error$ = this._error.pipe(distinctUntilChanged());

readonly schedulerList$: Observable<ScheduledFunctionDevelopmentFirebaseFunctionListEntry[]> = lazyFrom(() => {
return this.firebaseDevelopmentFunctions
Expand All @@ -40,33 +47,26 @@ export class DbxFirebaseDevelopmentSchedulerService implements Initialized {
constructor(@Optional() @Inject(DEFAULT_FIREBASE_DEVELOPMENT_SCHEDULER_ENABLED_TOKEN) private _startEnabled: boolean, readonly dbxAuthService: DbxAuthService, readonly firebaseDevelopmentFunctions: FirebaseDevelopmentFunctions) {}

init(): void {
this._sub.subscription = combineLatest([this._enabled, this.dbxAuthService.authUserState$.pipe(map((x) => x === 'user'))])
this._sub.subscription = this.running$
.pipe(
map(([enabled, userReady]) => enabled && userReady),
tapLog('DbxFirebaseDevelopmentSchedulerService enabled state:'),
switchMapWhileTrue(() => {
return combineLatest([this._timer, this.schedulerList$]).pipe(
switchMap(([timer, schedulerList]) => {
return combineLatest([this._timerInterval, this.schedulerList$]).pipe(
switchMap(([timerInterval, schedulerList]) => {
const executionOrder: string[] = schedulerList.map((x) => x.name);

return interval(timer).pipe(
return interval(timerInterval).pipe(
exhaustMap(() => {
console.log('Running scheduled tasks in order... ', executionOrder);

return PromiseUtility.runTasksForValues(
executionOrder,
(taskName) =>
this.firebaseDevelopmentFunctions.scheduledFunction({
run: taskName,
type: ScheduledFunctionDevelopmentFunctionTypeEnum.RUN
}),
{ sequential: true, retriesAllowed: 0, retryWait: 0 }
).catch((e) => {
return PromiseUtility.runTasksForValues(executionOrder, (taskName) => this.runScheduledFunction(taskName), { sequential: true, retriesAllowed: 0, retryWait: 0 }).catch((e) => {
console.log('Failed running scheduled task: ', e);
this._error.next(true);
});
}),
tap(() => {
console.log('Successfully finished running all scheduled tasks.');
this._error.next(false);
})
);
})
Expand All @@ -75,4 +75,15 @@ export class DbxFirebaseDevelopmentSchedulerService implements Initialized {
)
.subscribe();
}

runScheduledFunction(taskName: string) {
return this.firebaseDevelopmentFunctions.scheduledFunction({
run: taskName,
type: ScheduledFunctionDevelopmentFunctionTypeEnum.RUN
});
}

setTimer(timerInterval: number) {
this._timerInterval.next(timerInterval);
}
}
Loading

0 comments on commit 99c5712

Please sign in to comment.