Skip to content

Commit

Permalink
feat(admin-ui): Add support for dashboard widgets
Browse files Browse the repository at this point in the history
Relates to #334
  • Loading branch information
michaelbromley committed Nov 18, 2020
1 parent 70e14f2 commit aa835e8
Show file tree
Hide file tree
Showing 44 changed files with 729 additions and 61 deletions.
9 changes: 5 additions & 4 deletions packages/admin-ui/scripts/build-public-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const path = require('path');
console.log('Generating public apis...');
const SOURCES_DIR = path.join(__dirname, '/../src/lib');
const APP_SOURCE_FILE_PATTERN = /\.ts$/;
const EXCLUDED_PATTERN = /(public_api|spec|mock)\.ts$/;
const EXCLUDED_PATTERNS = [/(public_api|spec|mock)\.ts$/];

const MODULES = [
'catalog',
Expand All @@ -26,15 +26,16 @@ for (const moduleDir of MODULES) {
const modulePath = path.join(SOURCES_DIR, moduleDir, 'src');

const files = [];
forMatchingFiles(modulePath, APP_SOURCE_FILE_PATTERN, (filename) => {
if (!EXCLUDED_PATTERN.test(filename)) {
forMatchingFiles(modulePath, APP_SOURCE_FILE_PATTERN, filename => {
const excluded = EXCLUDED_PATTERNS.reduce((result, re) => result || re.test(filename), false);
if (!excluded) {
const relativeFilename =
'.' + filename.replace(modulePath, '').replace(/\\/g, '/').replace(/\.ts$/, '');
files.push(relativeFilename);
}
});
const header = `// This file was generated by the build-public-api.ts script\n`;
const fileContents = header + files.map((f) => `export * from '${f}';`).join('\n') + '\n';
const fileContents = header + files.map(f => `export * from '${f}';`).join('\n') + '\n';
const publicApiFile = path.join(modulePath, 'public_api.ts');
fs.writeFileSync(publicApiFile, fileContents, 'utf8');
console.log(`Created ${publicApiFile}`);
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { routes } from './app.routes';
@NgModule({
declarations: [],
imports: [AppComponentModule, RouterModule.forRoot(routes, { useHash: false })],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
36 changes: 36 additions & 0 deletions packages/admin-ui/src/lib/core/src/common/generated-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4314,6 +4314,14 @@ export type AdministratorFragment = (
) }
);

export type GetActiveAdministratorQueryVariables = Exact<{ [key: string]: never; }>;


export type GetActiveAdministratorQuery = { activeAdministrator?: Maybe<(
{ __typename?: 'Administrator' }
& AdministratorFragment
)> };

export type GetAdministratorsQueryVariables = Exact<{
options?: Maybe<AdministratorListOptions>;
}>;
Expand Down Expand Up @@ -5389,6 +5397,21 @@ export type TransitionFulfillmentToStateMutation = { transitionFulfillmentToStat
& ErrorResult_FulfillmentStateTransitionError_Fragment
) };

export type GetOrderSummaryQueryVariables = Exact<{
start: Scalars['DateTime'];
end: Scalars['DateTime'];
}>;


export type GetOrderSummaryQuery = { orders: (
{ __typename?: 'OrderList' }
& Pick<OrderList, 'totalItems'>
& { items: Array<(
{ __typename?: 'Order' }
& Pick<Order, 'id' | 'total' | 'currencyCode'>
)> }
) };

export type AssetFragment = (
{ __typename?: 'Asset' }
& Pick<Asset, 'id' | 'createdAt' | 'updatedAt' | 'name' | 'fileSize' | 'mimeType' | 'type' | 'preview' | 'source' | 'width' | 'height'>
Expand Down Expand Up @@ -7141,6 +7164,12 @@ export namespace Administrator {
export type Roles = NonNullable<(NonNullable<(NonNullable<AdministratorFragment['user']>)['roles']>)[number]>;
}

export namespace GetActiveAdministrator {
export type Variables = GetActiveAdministratorQueryVariables;
export type Query = GetActiveAdministratorQuery;
export type ActiveAdministrator = (NonNullable<GetActiveAdministratorQuery['activeAdministrator']>);
}

export namespace GetAdministrators {
export type Variables = GetAdministratorsQueryVariables;
export type Query = GetAdministratorsQuery;
Expand Down Expand Up @@ -7695,6 +7724,13 @@ export namespace TransitionFulfillmentToState {
export type FulfillmentStateTransitionErrorInlineFragment = (DiscriminateUnion<(NonNullable<TransitionFulfillmentToStateMutation['transitionFulfillmentToState']>), { __typename?: 'FulfillmentStateTransitionError' }>);
}

export namespace GetOrderSummary {
export type Variables = GetOrderSummaryQueryVariables;
export type Query = GetOrderSummaryQuery;
export type Orders = (NonNullable<GetOrderSummaryQuery['orders']>);
export type Items = NonNullable<(NonNullable<(NonNullable<GetOrderSummaryQuery['orders']>)['items']>)[number]>;
}

export namespace Asset {
export type Fragment = AssetFragment;
export type FocalPoint = (NonNullable<AssetFragment['focalPoint']>);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ export const ADMINISTRATOR_FRAGMENT = gql`
${ROLE_FRAGMENT}
`;

export const GET_ACTIVE_ADMINISTRATOR = gql`
query GetActiveAdministrator {
activeAdministrator {
...Administrator
}
}
${ADMINISTRATOR_FRAGMENT}
`;

export const GET_ADMINISTRATORS = gql`
query GetAdministrators($options: AdministratorListOptions) {
administrators(options: $options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,16 @@ export const TRANSITION_FULFILLMENT_TO_STATE = gql`
${FULFILLMENT_FRAGMENT}
${ERROR_RESULT_FRAGMENT}
`;

export const GET_ORDER_SUMMARY = gql`
query GetOrderSummary($start: DateTime!, $end: DateTime!) {
orders(options: { filter: { orderPlacedAt: { between: { start: $start, end: $end } } } }) {
totalItems
items {
id
total
currencyCode
}
}
}
`;
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { FetchPolicy } from '@apollo/client';

import {
CreateAdministrator,
CreateAdministratorInput,
CreateRole,
CreateRoleInput,
DeleteAdministrator,
DeleteRole,
GetActiveAdministrator,
GetAdministrator,
GetAdministrators,
GetRole,
Expand All @@ -19,6 +22,7 @@ import {
CREATE_ROLE,
DELETE_ADMINISTRATOR,
DELETE_ROLE,
GET_ACTIVE_ADMINISTRATOR,
GET_ADMINISTRATOR,
GET_ADMINISTRATORS,
GET_ROLE,
Expand All @@ -44,6 +48,14 @@ export class AdministratorDataService {
);
}

getActiveAdministrator(fetchPolicy: FetchPolicy = 'cache-first') {
return this.baseDataService.query<GetActiveAdministrator.Query>(
GET_ACTIVE_ADMINISTRATOR,
{},
fetchPolicy,
);
}

getAdministrator(id: string) {
return this.baseDataService.query<GetAdministrator.Query, GetAdministrator.Variables>(
GET_ADMINISTRATOR,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
GetOrder,
GetOrderHistory,
GetOrderList,
GetOrderSummary,
HistoryEntryListOptions,
OrderListOptions,
RefundOrder,
RefundOrderInput,
SettlePayment,
Expand All @@ -28,8 +30,9 @@ import {
CREATE_FULFILLMENT,
DELETE_ORDER_NOTE,
GET_ORDER,
GET_ORDER_HISTORY,
GET_ORDERS_LIST,
GET_ORDER_HISTORY,
GET_ORDER_SUMMARY,
REFUND_ORDER,
SETTLE_PAYMENT,
SETTLE_REFUND,
Expand All @@ -44,12 +47,9 @@ import { BaseDataService } from './base-data.service';
export class OrderDataService {
constructor(private baseDataService: BaseDataService) {}

getOrders(take: number = 10, skip: number = 0) {
getOrders(options: OrderListOptions = { take: 10 }) {
return this.baseDataService.query<GetOrderList.Query, GetOrderList.Variables>(GET_ORDERS_LIST, {
options: {
take,
skip,
},
options,
});
}

Expand Down Expand Up @@ -155,4 +155,14 @@ export class OrderDataService {
input,
});
}

getOrderSummary(start: Date, end: Date) {
return this.baseDataService.query<GetOrderSummary.Query, GetOrderSummary.Variables>(
GET_ORDER_SUMMARY,
{
start: start.toISOString(),
end: end.toISOString(),
},
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Type } from '@angular/core';

export interface DashboardWidgetConfig {
/**
* Used to specify the widget component. Supports both eager- and lazy-loading.
* @example
* ```TypeScript
* // eager-loading
* loadComponent: () => MyWidgetComponent,
*
* // lazy-loading
* loadComponent: () => import('./path-to/widget.component').then(m => m.MyWidgetComponent),
* ```
*/
loadComponent: () => Promise<Type<any>> | Type<any>;
/**
* The title of the widget. Can be a translation token as it will get passed
* through the `translate` pipe.
*/
title?: string;
/**
* The width of the widget, in terms of a Bootstrap-style 12-column grid.
*/
width: 3 | 4 | 6 | 12;
/**
* If set, the widget will only be displayed if the current user has all the
* specified permissions.
*/
requiresPermissions?: string[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';

import { DashboardWidgetConfig } from './dashboard-widget-types';

/**
* Registers a component to be used as a dashboard widget.
*/
@Injectable({
providedIn: 'root',
})
export class DashboardWidgetService {
private registry = new Map<string, DashboardWidgetConfig>();
private layout: ReadonlyArray<string> = [];

registerWidget(id: string, config: DashboardWidgetConfig) {
if (this.registry.has(id)) {
throw new Error(`A dashboard widget with the id "${id}" already exists`);
}

this.registry.set(id, config);
}

setDefaultLayout(ids: string[] | ReadonlyArray<string>) {
this.layout = ids;
}

getDefaultLayout(): ReadonlyArray<string> {
return this.layout;
}

getWidgets(): DashboardWidgetConfig[] {
return this.layout
.map(id => {
const config = this.registry.get(id);
if (!config) {
// tslint:disable-next-line:no-console
console.error(
`No dashboard widget was found with the id "${id}"\nAvailable ids: ${[
...this.registry.keys(),
]
.map(_id => `"${_id}"`)
.join(', ')}`,
);
}
return config;
})
.filter(notNullOrUndefined);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { APP_INITIALIZER, FactoryProvider } from '@angular/core';

import { DashboardWidgetConfig } from './dashboard-widget-types';
import { DashboardWidgetService } from './dashboard-widget.service';

export function registerDashboardWidget(id: string, config: DashboardWidgetConfig): FactoryProvider {
return {
provide: APP_INITIALIZER,
multi: true,
useFactory: (dashboardWidgetService: DashboardWidgetService) => () => {
dashboardWidgetService.registerWidget(id, config);
},
deps: [DashboardWidgetService],
};
}

export function setDashboardWidgetLayout(ids: string[] | ReadonlyArray<string>): FactoryProvider {
return {
provide: APP_INITIALIZER,
multi: true,
useFactory: (dashboardWidgetService: DashboardWidgetService) => () => {
dashboardWidgetService.setDefaultLayout(ids);
},
deps: [DashboardWidgetService],
};
}
3 changes: 3 additions & 0 deletions packages/admin-ui/src/lib/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export * from './data/utils/remove-readonly-custom-fields';
export * from './providers/auth/auth.service';
export * from './providers/component-registry/component-registry.service';
export * from './providers/custom-field-component/custom-field-component.service';
export * from './providers/dashboard-widget/dashboard-widget-types';
export * from './providers/dashboard-widget/dashboard-widget.service';
export * from './providers/dashboard-widget/register-dashboard-widget';
export * from './providers/guard/auth.guard';
export * from './providers/health-check/health-check.service';
export * from './providers/i18n/custom-http-loader';
Expand Down
Loading

0 comments on commit aa835e8

Please sign in to comment.