feat: a dashboard for clients, behind a secret link
Owners and admins turn it on in Configuración → General, pick what it
shows (tickets per status, the sprint under way, release forecasts,
open epics and activity per day) and can renew or drop the link. Whoever
has it sees /share/{token} without an account: only those blocks, no
people nor single tickets but the epics, no links into the app, read
again every minute. A renewed or dropped link says it no longer works.
REST (GET/PUT/DELETE /api/projects/{project}/share and the public
GET /api/shared/dashboards/{token}) and GraphQL (dashboardShare,
shareDashboard, unshareDashboard, sharedDashboard). The token is kept
in dashboard_shares and goes with the project.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import {
|
||||
DashboardShare,
|
||||
SharedBlock,
|
||||
SharedDashboard,
|
||||
SharingGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
import { map, Observable } from 'rxjs';
|
||||
import {
|
||||
DashboardShareDocument,
|
||||
ShareDashboardDocument,
|
||||
SharedDashboardDocument,
|
||||
UnshareDashboardDocument,
|
||||
} from '../graphql/generated';
|
||||
import { GraphqlOperations } from './graphql-operations';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlSharingGateway extends SharingGateway {
|
||||
private readonly gql = inject(GraphqlOperations);
|
||||
|
||||
share(projectId: string): Observable<DashboardShare | null> {
|
||||
return this.gql
|
||||
.query(DashboardShareDocument, { projectId })
|
||||
.pipe(map((d) => (d.dashboardShare as DashboardShare | null) ?? null));
|
||||
}
|
||||
|
||||
save(
|
||||
projectId: string,
|
||||
blocks: SharedBlock[],
|
||||
renew: boolean,
|
||||
): Observable<DashboardShare> {
|
||||
return this.gql
|
||||
.mutate(ShareDashboardDocument, { input: { projectId, blocks, renew } })
|
||||
.pipe(map((d) => d.shareDashboard as DashboardShare));
|
||||
}
|
||||
|
||||
remove(projectId: string): Observable<void> {
|
||||
return this.gql
|
||||
.mutate(UnshareDashboardDocument, { projectId })
|
||||
.pipe(map(() => undefined));
|
||||
}
|
||||
|
||||
shared(token: string): Observable<SharedDashboard | null> {
|
||||
return this.gql
|
||||
.query(SharedDashboardDocument, { token })
|
||||
.pipe(map((d) => (d.sharedDashboard as SharedDashboard | null) ?? null));
|
||||
}
|
||||
}
|
||||
@@ -329,6 +329,15 @@ export type SavedFilterCriteriaInput = {
|
||||
unassigned?: boolean | null | undefined;
|
||||
};
|
||||
|
||||
/** The project's link for clients */
|
||||
export type ShareDashboardInput = {
|
||||
/** Blocks to show: flow, sprint, releases, epics, activity */
|
||||
blocks: Array<string>;
|
||||
projectId: string;
|
||||
/** A new secret: the old link stops working */
|
||||
renew?: boolean | null | undefined;
|
||||
};
|
||||
|
||||
/** PLANNED, ACTIVE (one per project at a time) or CLOSED */
|
||||
export type SprintStatus = 'ACTIVE' | 'CLOSED' | 'PLANNED';
|
||||
|
||||
@@ -2437,6 +2446,98 @@ export type DeleteMyViewMutationVariables = Exact<{
|
||||
|
||||
export type DeleteMyViewMutation = { deleteMyView: boolean };
|
||||
|
||||
export type DashboardShareFieldsFragment = {
|
||||
projectId: string;
|
||||
token: string;
|
||||
blocks: Array<string>;
|
||||
createdAt: string;
|
||||
};
|
||||
|
||||
export type DashboardShareQueryVariables = Exact<{
|
||||
projectId: string;
|
||||
}>;
|
||||
|
||||
export type DashboardShareQuery = {
|
||||
dashboardShare: {
|
||||
projectId: string;
|
||||
token: string;
|
||||
blocks: Array<string>;
|
||||
createdAt: string;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type ShareDashboardMutationVariables = Exact<{
|
||||
input: ShareDashboardInput;
|
||||
}>;
|
||||
|
||||
export type ShareDashboardMutation = {
|
||||
shareDashboard: {
|
||||
projectId: string;
|
||||
token: string;
|
||||
blocks: Array<string>;
|
||||
createdAt: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type UnshareDashboardMutationVariables = Exact<{
|
||||
projectId: string;
|
||||
}>;
|
||||
|
||||
export type UnshareDashboardMutation = { unshareDashboard: boolean };
|
||||
|
||||
export type SharedDashboardQueryVariables = Exact<{
|
||||
token: string;
|
||||
}>;
|
||||
|
||||
export type SharedDashboardQuery = {
|
||||
sharedDashboard: {
|
||||
projectKey: string;
|
||||
projectName: string;
|
||||
generatedAt: string;
|
||||
blocks: Array<string>;
|
||||
flow: Array<{
|
||||
status: string;
|
||||
name: string;
|
||||
color: string;
|
||||
count: number;
|
||||
}> | null;
|
||||
sprint: {
|
||||
sprintId: string;
|
||||
name: string;
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
totalDays: number;
|
||||
daysLeft: number;
|
||||
tickets: number;
|
||||
doneTickets: number;
|
||||
points: number;
|
||||
donePoints: number;
|
||||
pacePerDay: number | null;
|
||||
neededPerDay: number | null;
|
||||
behind: boolean;
|
||||
} | null;
|
||||
releases: Array<{
|
||||
releaseId: string;
|
||||
name: string;
|
||||
targetDate: string | null;
|
||||
total: number;
|
||||
finished: number;
|
||||
likelyDate: string | null;
|
||||
safeDate: string | null;
|
||||
verdict: string;
|
||||
}> | null;
|
||||
epics: Array<{
|
||||
key: string;
|
||||
title: string;
|
||||
dueDate: string | null;
|
||||
done: number;
|
||||
total: number;
|
||||
percent: number;
|
||||
}> | null;
|
||||
activity: Array<{ day: string; count: number }> | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
export type SprintFieldsFragment = {
|
||||
id: string;
|
||||
projectId: string;
|
||||
@@ -3948,6 +4049,28 @@ export const SavedFilterFieldsFragmentDoc = {
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<SavedFilterFieldsFragment, unknown>;
|
||||
export const DashboardShareFieldsFragmentDoc = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'DashboardShareFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'DashboardShare' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'token' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'blocks' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<DashboardShareFieldsFragment, unknown>;
|
||||
export const SprintFieldsFragmentDoc = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
@@ -14377,6 +14500,391 @@ export const DeleteMyViewDocument = {
|
||||
DeleteMyViewMutation,
|
||||
DeleteMyViewMutationVariables
|
||||
>;
|
||||
export const DashboardShareDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'DashboardShare' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'dashboardShare' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'DashboardShareFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'DashboardShareFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'DashboardShare' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'token' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'blocks' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<DashboardShareQuery, DashboardShareQueryVariables>;
|
||||
export const ShareDashboardDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'ShareDashboard' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'ShareDashboardInput' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'shareDashboard' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'input' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'FragmentSpread',
|
||||
name: { kind: 'Name', value: 'DashboardShareFields' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'FragmentDefinition',
|
||||
name: { kind: 'Name', value: 'DashboardShareFields' },
|
||||
typeCondition: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'DashboardShare' },
|
||||
},
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectId' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'token' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'blocks' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'createdAt' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
ShareDashboardMutation,
|
||||
ShareDashboardMutationVariables
|
||||
>;
|
||||
export const UnshareDashboardDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'mutation',
|
||||
name: { kind: 'Name', value: 'UnshareDashboard' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: { kind: 'NamedType', name: { kind: 'Name', value: 'ID' } },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'unshareDashboard' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'projectId' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
UnshareDashboardMutation,
|
||||
UnshareDashboardMutationVariables
|
||||
>;
|
||||
export const SharedDashboardDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
{
|
||||
kind: 'OperationDefinition',
|
||||
operation: 'query',
|
||||
name: { kind: 'Name', value: 'SharedDashboard' },
|
||||
variableDefinitions: [
|
||||
{
|
||||
kind: 'VariableDefinition',
|
||||
variable: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'token' },
|
||||
},
|
||||
type: {
|
||||
kind: 'NonNullType',
|
||||
type: {
|
||||
kind: 'NamedType',
|
||||
name: { kind: 'Name', value: 'String' },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'sharedDashboard' },
|
||||
arguments: [
|
||||
{
|
||||
kind: 'Argument',
|
||||
name: { kind: 'Name', value: 'token' },
|
||||
value: {
|
||||
kind: 'Variable',
|
||||
name: { kind: 'Name', value: 'token' },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectKey' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'projectName' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'generatedAt' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'blocks' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'flow' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'status' },
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'color' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'count' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'sprint' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'sprintId' },
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'startDate' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'endDate' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'totalDays' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'daysLeft' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'tickets' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'doneTickets' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'points' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'donePoints' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'pacePerDay' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'neededPerDay' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'behind' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'releases' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'releaseId' },
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'name' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'targetDate' },
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'total' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'finished' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'likelyDate' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'safeDate' },
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'verdict' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'epics' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'key' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'title' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'dueDate' },
|
||||
},
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'done' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'total' } },
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'percent' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
kind: 'Field',
|
||||
name: { kind: 'Name', value: 'activity' },
|
||||
selectionSet: {
|
||||
kind: 'SelectionSet',
|
||||
selections: [
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'day' } },
|
||||
{ kind: 'Field', name: { kind: 'Name', value: 'count' } },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
} as unknown as DocumentNode<
|
||||
SharedDashboardQuery,
|
||||
SharedDashboardQueryVariables
|
||||
>;
|
||||
export const SprintsDocument = {
|
||||
kind: 'Document',
|
||||
definitions: [
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
fragment DashboardShareFields on DashboardShare {
|
||||
projectId
|
||||
token
|
||||
blocks
|
||||
createdAt
|
||||
}
|
||||
|
||||
query DashboardShare($projectId: ID!) {
|
||||
dashboardShare(projectId: $projectId) {
|
||||
...DashboardShareFields
|
||||
}
|
||||
}
|
||||
|
||||
mutation ShareDashboard($input: ShareDashboardInput!) {
|
||||
shareDashboard(input: $input) {
|
||||
...DashboardShareFields
|
||||
}
|
||||
}
|
||||
|
||||
mutation UnshareDashboard($projectId: ID!) {
|
||||
unshareDashboard(projectId: $projectId)
|
||||
}
|
||||
|
||||
query SharedDashboard($token: String!) {
|
||||
sharedDashboard(token: $token) {
|
||||
projectKey
|
||||
projectName
|
||||
generatedAt
|
||||
blocks
|
||||
flow {
|
||||
status
|
||||
name
|
||||
color
|
||||
count
|
||||
}
|
||||
sprint {
|
||||
sprintId
|
||||
name
|
||||
startDate
|
||||
endDate
|
||||
totalDays
|
||||
daysLeft
|
||||
tickets
|
||||
doneTickets
|
||||
points
|
||||
donePoints
|
||||
pacePerDay
|
||||
neededPerDay
|
||||
behind
|
||||
}
|
||||
releases {
|
||||
releaseId
|
||||
name
|
||||
targetDate
|
||||
total
|
||||
finished
|
||||
likelyDate
|
||||
safeDate
|
||||
verdict
|
||||
}
|
||||
epics {
|
||||
key
|
||||
title
|
||||
dueDate
|
||||
done
|
||||
total
|
||||
percent
|
||||
}
|
||||
activity {
|
||||
day
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
WebhooksGateway,
|
||||
TransferGateway,
|
||||
AutomationsGateway,
|
||||
SharingGateway,
|
||||
TrackerRealtimeGateway,
|
||||
UsersGateway,
|
||||
} from '@task-stream/web/tracker/domain';
|
||||
@@ -44,6 +45,7 @@ import { GraphqlGitGateway } from './adapters/graphql-git.gateway';
|
||||
import { GraphqlWebhooksGateway } from './adapters/graphql-webhooks.gateway';
|
||||
import { HttpTransferGateway } from './adapters/http-transfer.gateway';
|
||||
import { GraphqlAutomationsGateway } from './adapters/graphql-automations.gateway';
|
||||
import { GraphqlSharingGateway } from './adapters/graphql-sharing.gateway';
|
||||
|
||||
/**
|
||||
* The adapters the app shell needs from the start (the command palette, the
|
||||
@@ -79,5 +81,6 @@ export function provideTrackerPagesInfrastructure(): EnvironmentProviders {
|
||||
{ provide: WebhooksGateway, useClass: GraphqlWebhooksGateway },
|
||||
{ provide: TransferGateway, useClass: HttpTransferGateway },
|
||||
{ provide: AutomationsGateway, useClass: GraphqlAutomationsGateway },
|
||||
{ provide: SharingGateway, useClass: GraphqlSharingGateway },
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user