@ministryofjustice/hmpps-digital-prison-reporting-frontend 4.28.2 → 4.28.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -116,6 +116,17 @@ class ReportingClient {
116
116
  }
117
117
  }).then((response) => response);
118
118
  }
119
+ downloadAsyncReport(token, reportId, variantId, tableId, query, res) {
120
+ this.logInfo("Streaming download data", { reportId, variantId, tableId });
121
+ return this.restClient.getStream(
122
+ {
123
+ path: `/reports/${reportId}/${variantId}/tables/${tableId}/download`,
124
+ query,
125
+ token
126
+ },
127
+ res
128
+ );
129
+ }
119
130
  getAsyncReport(token, reportId, variantId, tableId, query) {
120
131
  this.logInfo("Get Data", { reportId, variantId, tableId });
121
132
  return this.restClient.get({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../dpr/data/reportingClient.ts"],
4
- "sourcesContent": ["import ReportQuery from '../types/ReportQuery'\nimport logger from '../utils/logger'\nimport RestClient from './restClient'\nimport Dict = NodeJS.Dict\nimport { components, operations } from '../types/api'\nimport { ApiConfig, Count, ListWithWarnings } from './types'\nimport type { ResultWithHeaders } from './restClient'\n\nclass ReportingClient {\n restClient: RestClient\n\n constructor(config: ApiConfig) {\n this.restClient = new RestClient('Reporting API Client', config)\n }\n\n getCount(resourceName: string, token: string, countRequest: ReportQuery): Promise<number> {\n logger.info(`Reporting client: Get count. { resourceName: ${resourceName} }`)\n\n return this.restClient\n .get({\n path: `/${resourceName}/count`,\n query: countRequest.toRecordWithFilterPrefix(true),\n token,\n })\n .then((response) => (<Count>response).count)\n }\n\n getList(resourceName: string, token: string, listRequest: ReportQuery): Promise<Array<Dict<string>>> {\n return this.getListWithWarnings(resourceName, token, listRequest).then((response) => response.data)\n }\n\n getListWithWarnings(resourceName: string, token: string, listRequest: ReportQuery): Promise<ListWithWarnings> {\n logger.info(`Reporting client: Get list. { resourceName: ${resourceName} }`)\n\n return this.restClient\n .getWithHeaders<Array<Dict<string>>>({\n path: `/${resourceName}`,\n query: listRequest.toRecordWithFilterPrefix(true),\n token,\n })\n .then((response: ResultWithHeaders<Array<Dict<string>>>) => ({\n data: response.data,\n warnings: {\n noDataAvailable: response.headers['x-no-data-warning'],\n },\n }))\n }\n\n getDefinitionSummary(\n token: string,\n reportId: string,\n definitionsPath?: string,\n ): Promise<components['schemas']['ReportDefinitionSummary']> {\n this.logInfo('Get definition summary', { reportId })\n\n const queryParams: operations['definitionSummary']['parameters']['query'] = {\n ...(definitionsPath && { dataProductDefinitionsPath: definitionsPath }),\n }\n\n return this.restClient\n .get({\n path: `/definitions/${reportId}`,\n query: queryParams,\n token,\n })\n .then((response) => <components['schemas']['ReportDefinitionSummary']>response)\n }\n\n getDefinitions(\n token: string,\n definitionsPath?: string,\n ): Promise<Array<components['schemas']['ReportDefinitionSummary']>> {\n this.logInfo('Get definitions')\n\n const queryParams: operations['definitions_1']['parameters']['query'] = {\n renderMethod: 'HTML',\n ...(definitionsPath && { dataProductDefinitionsPath: definitionsPath }),\n }\n\n return this.restClient\n .get({\n path: '/definitions',\n query: queryParams,\n token,\n })\n .then((response) => <Array<components['schemas']['ReportDefinitionSummary']>>response)\n }\n\n getDefinition(\n token: string,\n reportId: string,\n variantId: string,\n definitionsPath?: string,\n queryData?: Dict<string | string[]>,\n ): Promise<components['schemas']['SingleVariantReportDefinition']> {\n const query = {\n ...queryData,\n dataProductDefinitionsPath: definitionsPath,\n }\n\n this.logInfo('Get definition', { reportId, variantId, ...query })\n\n return this.restClient\n .get({\n path: `/definitions/${reportId}/${variantId}`,\n query,\n token,\n })\n .then((response) => <components['schemas']['SingleVariantReportDefinition']>response)\n }\n\n requestAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n query: Record<string, string | boolean | number>,\n ): Promise<Dict<string>> {\n this.logInfo('Request report', { reportId, variantId })\n\n return this.restClient\n .get({\n path: `/async/reports/${reportId}/${variantId}`,\n token,\n query,\n })\n .then((response) => <Dict<string>>response)\n }\n\n cancelAsyncRequest(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Dict<string>> {\n this.logInfo('Cancel Request', { reportId, variantId, executionId })\n\n return this.restClient\n .delete({\n path: `/reports/${reportId}/${variantId}/statements/${executionId}`,\n token,\n query: {\n dataProductDefinitionsPath,\n },\n })\n .then((response) => <Dict<string>>response)\n }\n\n getAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n ): Promise<Array<Dict<string>>> {\n this.logInfo('Get Data', { reportId, variantId, tableId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/tables/${tableId}/result`,\n token,\n query,\n })\n .then((response) => <Array<Dict<string>>>response)\n }\n\n getAsyncSummaryReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n summaryId: string,\n query: Dict<string | number>,\n ): Promise<Array<Dict<string>>> {\n this.logInfo('Get summary data', { reportId, variantId, tableId, summaryId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/tables/${tableId}/result/summary/${summaryId}`,\n token,\n query,\n })\n .then((response) => <Array<Dict<string>>>response)\n }\n\n getAsyncReportStatus(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n tableId?: string,\n ): Promise<components['schemas']['StatementExecutionStatus']> {\n this.logInfo('Get status', { reportId, variantId, tableId, executionId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/statements/${executionId}/status`,\n token,\n query: {\n dataProductDefinitionsPath,\n tableId,\n },\n })\n .then((response) => <components['schemas']['StatementExecutionStatus']>response)\n }\n\n getAsyncCount(token: string, tableId: string, dataProductDefinitionsPath?: string): Promise<number> {\n this.logInfo('Get count', { tableId })\n\n return this.restClient\n .get({\n path: `/report/tables/${tableId}/count`,\n token,\n query: {\n dataProductDefinitionsPath,\n },\n })\n .then((response) => (<Count>response).count)\n }\n\n getAsyncInteractiveCount(\n token: string,\n tableId: string,\n reportId: string,\n id: string,\n filters: ReportQuery,\n ): Promise<number> {\n this.logInfo('Get interactive count', { tableId, reportId, id })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${id}/tables/${tableId}/count`,\n token,\n query: filters.toRecordWithFilterPrefix(true),\n })\n .then((response) => (<Count>response).count)\n }\n\n logInfo(title: string, args?: Dict<string>) {\n logger.info(`Reporting Client: ${title}:`)\n if (args && Object.keys(args).length) logger.info(JSON.stringify(args, null, 2))\n }\n}\n\nexport { ReportingClient }\nexport default ReportingClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Response } from 'express'\nimport ReportQuery from '../types/ReportQuery'\nimport logger from '../utils/logger'\nimport RestClient from './restClient'\nimport Dict = NodeJS.Dict\nimport { components, operations } from '../types/api'\nimport { ApiConfig, Count, ListWithWarnings } from './types'\nimport type { ResultWithHeaders } from './restClient'\n\nclass ReportingClient {\n restClient: RestClient\n\n constructor(config: ApiConfig) {\n this.restClient = new RestClient('Reporting API Client', config)\n }\n\n getCount(resourceName: string, token: string, countRequest: ReportQuery): Promise<number> {\n logger.info(`Reporting client: Get count. { resourceName: ${resourceName} }`)\n\n return this.restClient\n .get({\n path: `/${resourceName}/count`,\n query: countRequest.toRecordWithFilterPrefix(true),\n token,\n })\n .then((response) => (<Count>response).count)\n }\n\n getList(resourceName: string, token: string, listRequest: ReportQuery): Promise<Array<Dict<string>>> {\n return this.getListWithWarnings(resourceName, token, listRequest).then((response) => response.data)\n }\n\n getListWithWarnings(resourceName: string, token: string, listRequest: ReportQuery): Promise<ListWithWarnings> {\n logger.info(`Reporting client: Get list. { resourceName: ${resourceName} }`)\n\n return this.restClient\n .getWithHeaders<Array<Dict<string>>>({\n path: `/${resourceName}`,\n query: listRequest.toRecordWithFilterPrefix(true),\n token,\n })\n .then((response: ResultWithHeaders<Array<Dict<string>>>) => ({\n data: response.data,\n warnings: {\n noDataAvailable: response.headers['x-no-data-warning'],\n },\n }))\n }\n\n getDefinitionSummary(\n token: string,\n reportId: string,\n definitionsPath?: string,\n ): Promise<components['schemas']['ReportDefinitionSummary']> {\n this.logInfo('Get definition summary', { reportId })\n\n const queryParams: operations['definitionSummary']['parameters']['query'] = {\n ...(definitionsPath && { dataProductDefinitionsPath: definitionsPath }),\n }\n\n return this.restClient\n .get({\n path: `/definitions/${reportId}`,\n query: queryParams,\n token,\n })\n .then((response) => <components['schemas']['ReportDefinitionSummary']>response)\n }\n\n getDefinitions(\n token: string,\n definitionsPath?: string,\n ): Promise<Array<components['schemas']['ReportDefinitionSummary']>> {\n this.logInfo('Get definitions')\n\n const queryParams: operations['definitions_1']['parameters']['query'] = {\n renderMethod: 'HTML',\n ...(definitionsPath && { dataProductDefinitionsPath: definitionsPath }),\n }\n\n return this.restClient\n .get({\n path: '/definitions',\n query: queryParams,\n token,\n })\n .then((response) => <Array<components['schemas']['ReportDefinitionSummary']>>response)\n }\n\n getDefinition(\n token: string,\n reportId: string,\n variantId: string,\n definitionsPath?: string,\n queryData?: Dict<string | string[]>,\n ): Promise<components['schemas']['SingleVariantReportDefinition']> {\n const query = {\n ...queryData,\n dataProductDefinitionsPath: definitionsPath,\n }\n\n this.logInfo('Get definition', { reportId, variantId, ...query })\n\n return this.restClient\n .get({\n path: `/definitions/${reportId}/${variantId}`,\n query,\n token,\n })\n .then((response) => <components['schemas']['SingleVariantReportDefinition']>response)\n }\n\n requestAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n query: Record<string, string | boolean | number>,\n ): Promise<Dict<string>> {\n this.logInfo('Request report', { reportId, variantId })\n\n return this.restClient\n .get({\n path: `/async/reports/${reportId}/${variantId}`,\n token,\n query,\n })\n .then((response) => <Dict<string>>response)\n }\n\n cancelAsyncRequest(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Dict<string>> {\n this.logInfo('Cancel Request', { reportId, variantId, executionId })\n\n return this.restClient\n .delete({\n path: `/reports/${reportId}/${variantId}/statements/${executionId}`,\n token,\n query: {\n dataProductDefinitionsPath,\n },\n })\n .then((response) => <Dict<string>>response)\n }\n\n downloadAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n res: Response,\n ): Promise<void> {\n this.logInfo('Streaming download data', { reportId, variantId, tableId })\n\n return this.restClient.getStream(\n {\n path: `/reports/${reportId}/${variantId}/tables/${tableId}/download`,\n query,\n token,\n },\n res,\n )\n }\n\n getAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n ): Promise<Array<Dict<string>>> {\n this.logInfo('Get Data', { reportId, variantId, tableId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/tables/${tableId}/result`,\n token,\n query,\n })\n .then((response) => <Array<Dict<string>>>response)\n }\n\n getAsyncSummaryReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n summaryId: string,\n query: Dict<string | number>,\n ): Promise<Array<Dict<string>>> {\n this.logInfo('Get summary data', { reportId, variantId, tableId, summaryId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/tables/${tableId}/result/summary/${summaryId}`,\n token,\n query,\n })\n .then((response) => <Array<Dict<string>>>response)\n }\n\n getAsyncReportStatus(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n tableId?: string,\n ): Promise<components['schemas']['StatementExecutionStatus']> {\n this.logInfo('Get status', { reportId, variantId, tableId, executionId })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${variantId}/statements/${executionId}/status`,\n token,\n query: {\n dataProductDefinitionsPath,\n tableId,\n },\n })\n .then((response) => <components['schemas']['StatementExecutionStatus']>response)\n }\n\n getAsyncCount(token: string, tableId: string, dataProductDefinitionsPath?: string): Promise<number> {\n this.logInfo('Get count', { tableId })\n\n return this.restClient\n .get({\n path: `/report/tables/${tableId}/count`,\n token,\n query: {\n dataProductDefinitionsPath,\n },\n })\n .then((response) => (<Count>response).count)\n }\n\n getAsyncInteractiveCount(\n token: string,\n tableId: string,\n reportId: string,\n id: string,\n filters: ReportQuery,\n ): Promise<number> {\n this.logInfo('Get interactive count', { tableId, reportId, id })\n\n return this.restClient\n .get({\n path: `/reports/${reportId}/${id}/tables/${tableId}/count`,\n token,\n query: filters.toRecordWithFilterPrefix(true),\n })\n .then((response) => (<Count>response).count)\n }\n\n logInfo(title: string, args?: Dict<string>) {\n logger.info(`Reporting Client: ${title}:`)\n if (args && Object.keys(args).length) logger.info(JSON.stringify(args, null, 2))\n }\n}\n\nexport { ReportingClient }\nexport default ReportingClient\n"],
5
+ "mappings": "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",
6
6
  "names": ["RestClient", "logger"]
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { Response } from 'express'
1
2
  import ReportQuery from '../types/ReportQuery'
2
3
  import logger from '../utils/logger'
3
4
  import RestClient from './restClient'
@@ -146,6 +147,26 @@ class ReportingClient {
146
147
  .then((response) => <Dict<string>>response)
147
148
  }
148
149
 
150
+ downloadAsyncReport(
151
+ token: string,
152
+ reportId: string,
153
+ variantId: string,
154
+ tableId: string,
155
+ query: Record<string, string | string[]>,
156
+ res: Response,
157
+ ): Promise<void> {
158
+ this.logInfo('Streaming download data', { reportId, variantId, tableId })
159
+
160
+ return this.restClient.getStream(
161
+ {
162
+ path: `/reports/${reportId}/${variantId}/tables/${tableId}/download`,
163
+ query,
164
+ token,
165
+ },
166
+ res,
167
+ )
168
+ }
169
+
149
170
  getAsyncReport(
150
171
  token: string,
151
172
  reportId: string,
@@ -52,6 +52,32 @@ class RestClient {
52
52
  async get(request) {
53
53
  return this.getWithHeaders(request).then((result) => result.data);
54
54
  }
55
+ async getStream({ path = "", query = {}, headers = {}, token }, res) {
56
+ import_logger.default.info(`${this.name} STREAM GET: ${this.apiUrl()}${path}`);
57
+ import_logger.default.info(`query: ${JSON.stringify(query)}`);
58
+ const req = import_superagent.default.get(`${this.apiUrl()}${path}`).agent(this.agent).query(query).auth(token, { type: "bearer" }).set(headers).timeout(this.timeoutConfig());
59
+ req.on("response", (upstream) => {
60
+ res.status(upstream.status);
61
+ Object.entries(upstream.headers).forEach(([key, value]) => {
62
+ if (value !== void 0) {
63
+ res.setHeader(key, value);
64
+ }
65
+ });
66
+ res.on("close", () => {
67
+ import_logger.default.info("Client disconnected, aborting upstream request.");
68
+ req.abort();
69
+ });
70
+ upstream.pipe(res);
71
+ });
72
+ req.on("error", (error) => {
73
+ import_logger.default.warn({ error }, `Error streaming from ${this.name}, path: '${path}'`);
74
+ if (!res.headersSent) {
75
+ res.status(502).end("Download request failed. Error streaming response");
76
+ } else {
77
+ res.destroy(error);
78
+ }
79
+ });
80
+ }
55
81
  async requestWithBody(method, { path, query = {}, headers = {}, responseType = "", data = {}, raw = false, retry = false }, token) {
56
82
  import_logger.default.info(`${this.name} ${method.toUpperCase()}: ${path}`);
57
83
  import_logger.default.info(`info about request: ${method} | ${path} | ${JSON.stringify(data)} | ${query}`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../dpr/data/restClient.ts"],
4
- "sourcesContent": ["import superagent, { ResponseError } from 'superagent'\nimport Agent, { HttpsAgent } from 'agentkeepalive'\n\nimport logger from '../utils/logger'\nimport sanitiseError from '../utils/sanitisedError'\nimport { ApiConfig, GetRequest } from './types'\nimport Dict = NodeJS.Dict\n\nexport interface ResultWithHeaders<T> {\n data: T\n headers: Dict<string>\n}\n\ninterface Request {\n path: string\n query?: object | string\n headers?: Record<string, string>\n responseType?: string\n raw?: boolean\n}\ninterface RequestWithBody extends Request {\n data?: Record<string, unknown> | string\n retry?: boolean\n}\n\nclass RestClient {\n agent: Agent\n\n constructor(private readonly name: string, private readonly config: ApiConfig) {\n this.agent = config.url.startsWith('https') ? new HttpsAgent(config.agent) : new Agent(config.agent)\n }\n\n private apiUrl() {\n return this.config.url\n }\n\n private timeoutConfig() {\n return this.config.agent.timeout\n }\n\n async get<T>(request: GetRequest): Promise<T> {\n return this.getWithHeaders<T>(request).then((result) => result.data)\n }\n\n private async requestWithBody<Response = unknown>(\n method: 'patch' | 'post' | 'put',\n { path, query = {}, headers = {}, responseType = '', data = {}, raw = false, retry = false }: RequestWithBody,\n token: string,\n ): Promise<Response> {\n logger.info(`${this.name} ${method.toUpperCase()}: ${path}`)\n logger.info(`info about request: ${method} | ${path} | ${JSON.stringify(data)} | ${query}`)\n try {\n const result = await superagent[method](`${this.apiUrl()}${path}`)\n .query(query)\n .send(data)\n .agent(this.agent)\n .retry(2, (err) => {\n if (retry === false) {\n return false\n }\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return raw ? (result as Response) : result.body\n } catch (error) {\n if (!(error instanceof Error)) {\n throw error\n }\n const sanitisedError = sanitiseError(error)\n logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: '${method.toUpperCase()}'`)\n throw sanitisedError\n }\n }\n\n async post<Response = unknown>(request: RequestWithBody, token: string): Promise<Response> {\n return this.requestWithBody('post', request, token)\n }\n\n async getWithHeaders<T>({\n path = '',\n query = {},\n headers = {},\n responseType = '',\n raw = false,\n token,\n }: GetRequest): Promise<ResultWithHeaders<T>> {\n const loggerData = {\n path: `${this.config.url}${path}`,\n query,\n }\n logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`)\n try {\n const result = await superagent\n .get(`${this.apiUrl()}${path}`)\n .agent(this.agent)\n .retry(2, (err) => {\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return {\n data: raw ? result : result.body,\n headers: result.headers,\n }\n } catch (error) {\n const sanitisedError = sanitiseError(<ResponseError>error)\n logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`)\n throw sanitisedError\n }\n }\n\n async deleteWithHeaders<T>({\n path = '',\n query = {},\n headers = {},\n responseType = '',\n raw = false,\n token,\n }: GetRequest): Promise<ResultWithHeaders<T>> {\n const loggerData = {\n path: `${this.config.url}${path}`,\n query,\n }\n logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`)\n try {\n const result = await superagent\n .delete(`${this.apiUrl()}${path}`)\n .agent(this.agent)\n .retry(2, (err) => {\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return {\n data: raw ? result : result.body,\n headers: result.headers,\n }\n } catch (error) {\n const sanitisedError = sanitiseError(<ResponseError>error)\n logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`)\n throw sanitisedError\n }\n }\n\n async delete<T>(request: GetRequest): Promise<T> {\n return this.deleteWithHeaders<T>(request).then((result) => result.data)\n }\n}\n\nexport { RestClient }\nexport default RestClient\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import superagent, { ResponseError } from 'superagent'\nimport Agent, { HttpsAgent } from 'agentkeepalive'\nimport { Response as ExpressResponse } from 'express'\n\nimport logger from '../utils/logger'\nimport sanitiseError from '../utils/sanitisedError'\nimport { ApiConfig, GetRequest } from './types'\nimport Dict = NodeJS.Dict\n\nexport interface ResultWithHeaders<T> {\n data: T\n headers: Dict<string>\n}\n\ninterface Request {\n path: string\n query?: object | string\n headers?: Record<string, string>\n responseType?: string\n raw?: boolean\n}\ninterface RequestWithBody extends Request {\n data?: Record<string, unknown> | string\n retry?: boolean\n}\n\nclass RestClient {\n agent: Agent\n\n constructor(private readonly name: string, private readonly config: ApiConfig) {\n this.agent = config.url.startsWith('https') ? new HttpsAgent(config.agent) : new Agent(config.agent)\n }\n\n private apiUrl() {\n return this.config.url\n }\n\n private timeoutConfig() {\n return this.config.agent.timeout\n }\n\n async get<T>(request: GetRequest): Promise<T> {\n return this.getWithHeaders<T>(request).then((result) => result.data)\n }\n\n async getStream({ path = '', query = {}, headers = {}, token }: GetRequest, res: ExpressResponse): Promise<void> {\n logger.info(`${this.name} STREAM GET: ${this.apiUrl()}${path}`)\n logger.info(`query: ${JSON.stringify(query)}`)\n\n const req = superagent\n .get(`${this.apiUrl()}${path}`)\n .agent(this.agent)\n .query(query)\n .auth(token, { type: 'bearer' })\n .set(headers)\n .timeout(this.timeoutConfig())\n\n req.on('response', (upstream) => {\n // Forward status\n res.status(upstream.status)\n\n // Forward headers\n Object.entries(upstream.headers).forEach(([key, value]) => {\n if (value !== undefined) {\n res.setHeader(key, value as string)\n }\n })\n res.on('close', () => {\n logger.info('Client disconnected, aborting upstream request.')\n req.abort()\n })\n upstream.pipe(res)\n })\n\n req.on('error', (error) => {\n logger.warn({ error }, `Error streaming from ${this.name}, path: '${path}'`)\n if (!res.headersSent) {\n res.status(502).end('Download request failed. Error streaming response')\n } else {\n res.destroy(error)\n }\n })\n }\n\n private async requestWithBody<Response = unknown>(\n method: 'patch' | 'post' | 'put',\n { path, query = {}, headers = {}, responseType = '', data = {}, raw = false, retry = false }: RequestWithBody,\n token: string,\n ): Promise<Response> {\n logger.info(`${this.name} ${method.toUpperCase()}: ${path}`)\n logger.info(`info about request: ${method} | ${path} | ${JSON.stringify(data)} | ${query}`)\n try {\n const result = await superagent[method](`${this.apiUrl()}${path}`)\n .query(query)\n .send(data)\n .agent(this.agent)\n .retry(2, (err) => {\n if (retry === false) {\n return false\n }\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return raw ? (result as Response) : result.body\n } catch (error) {\n if (!(error instanceof Error)) {\n throw error\n }\n const sanitisedError = sanitiseError(error)\n logger.warn({ ...sanitisedError }, `Error calling ${this.name}, path: '${path}', verb: '${method.toUpperCase()}'`)\n throw sanitisedError\n }\n }\n\n async post<Response = unknown>(request: RequestWithBody, token: string): Promise<Response> {\n return this.requestWithBody('post', request, token)\n }\n\n async getWithHeaders<T>({\n path = '',\n query = {},\n headers = {},\n responseType = '',\n raw = false,\n token,\n }: GetRequest): Promise<ResultWithHeaders<T>> {\n const loggerData = {\n path: `${this.config.url}${path}`,\n query,\n }\n logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`)\n try {\n const result = await superagent\n .get(`${this.apiUrl()}${path}`)\n .agent(this.agent)\n .retry(2, (err) => {\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return {\n data: raw ? result : result.body,\n headers: result.headers,\n }\n } catch (error) {\n const sanitisedError = sanitiseError(<ResponseError>error)\n logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`)\n throw sanitisedError\n }\n }\n\n async deleteWithHeaders<T>({\n path = '',\n query = {},\n headers = {},\n responseType = '',\n raw = false,\n token,\n }: GetRequest): Promise<ResultWithHeaders<T>> {\n const loggerData = {\n path: `${this.config.url}${path}`,\n query,\n }\n logger.info(`${this.name}: ${JSON.stringify(loggerData, null, 2)}`)\n try {\n const result = await superagent\n .delete(`${this.apiUrl()}${path}`)\n .agent(this.agent)\n .retry(2, (err) => {\n if (err) logger.info(`Retry handler found API error with ${err.code} ${err.message}`)\n return undefined // retry handler only for logging retries, not to influence retry logic\n })\n .query(query)\n .auth(token, { type: 'bearer' })\n .set(headers)\n .responseType(responseType)\n .timeout(this.timeoutConfig())\n\n return {\n data: raw ? result : result.body,\n headers: result.headers,\n }\n } catch (error) {\n const sanitisedError = sanitiseError(<ResponseError>error)\n logger.warn({ ...sanitisedError, query }, `Error calling ${this.name}, path: '${path}', verb: 'GET'`)\n throw sanitisedError\n }\n }\n\n async delete<T>(request: GetRequest): Promise<T> {\n return this.deleteWithHeaders<T>(request).then((result) => result.data)\n }\n}\n\nexport { RestClient }\nexport default RestClient\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,wBAA0C;AAC1C,4BAAkC;AAGlC,oBAAmB;AACnB,4BAA0B;AAqB1B,MAAM,WAAW;AAAA,EAGf,YAA6B,MAA+B,QAAmB;AAAlD;AAA+B;AAC1D,SAAK,QAAQ,OAAO,IAAI,WAAW,OAAO,IAAI,IAAI,iCAAW,OAAO,KAAK,IAAI,IAAI,sBAAAA,QAAM,OAAO,KAAK;AAAA,EACrG;AAAA,EAJA;AAAA,EAMQ,SAAS;AACf,WAAO,KAAK,OAAO;AAAA,EACrB;AAAA,EAEQ,gBAAgB;AACtB,WAAO,KAAK,OAAO,MAAM;AAAA,EAC3B;AAAA,EAEA,MAAM,IAAO,SAAiC;AAC5C,WAAO,KAAK,eAAkB,OAAO,EAAE,KAAK,CAAC,WAAW,OAAO,IAAI;AAAA,EACrE;AAAA,EAEA,MAAM,UAAU,EAAE,OAAO,IAAI,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG,MAAM,GAAe,KAAqC;AAC/G,kBAAAC,QAAO,KAAK,GAAG,KAAK,IAAI,gBAAgB,KAAK,OAAO,CAAC,GAAG,IAAI,EAAE;AAC9D,kBAAAA,QAAO,KAAK,UAAU,KAAK,UAAU,KAAK,CAAC,EAAE;AAE7C,UAAM,MAAM,kBAAAC,QACT,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,EAAE,EAC7B,MAAM,KAAK,KAAK,EAChB,MAAM,KAAK,EACX,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC,EAC9B,IAAI,OAAO,EACX,QAAQ,KAAK,cAAc,CAAC;AAE/B,QAAI,GAAG,YAAY,CAAC,aAAa;AAE/B,UAAI,OAAO,SAAS,MAAM;AAG1B,aAAO,QAAQ,SAAS,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACzD,YAAI,UAAU,QAAW;AACvB,cAAI,UAAU,KAAK,KAAe;AAAA,QACpC;AAAA,MACF,CAAC;AACD,UAAI,GAAG,SAAS,MAAM;AACpB,sBAAAD,QAAO,KAAK,iDAAiD;AAC7D,YAAI,MAAM;AAAA,MACZ,CAAC;AACD,eAAS,KAAK,GAAG;AAAA,IACnB,CAAC;AAED,QAAI,GAAG,SAAS,CAAC,UAAU;AACzB,oBAAAA,QAAO,KAAK,EAAE,MAAM,GAAG,wBAAwB,KAAK,IAAI,YAAY,IAAI,GAAG;AAC3E,UAAI,CAAC,IAAI,aAAa;AACpB,YAAI,OAAO,GAAG,EAAE,IAAI,mDAAmD;AAAA,MACzE,OAAO;AACL,YAAI,QAAQ,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,gBACZ,QACA,EAAE,MAAM,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG,eAAe,IAAI,OAAO,CAAC,GAAG,MAAM,OAAO,QAAQ,MAAM,GAC3F,OACmB;AACnB,kBAAAA,QAAO,KAAK,GAAG,KAAK,IAAI,IAAI,OAAO,YAAY,CAAC,KAAK,IAAI,EAAE;AAC3D,kBAAAA,QAAO,KAAK,uBAAuB,MAAM,MAAM,IAAI,MAAM,KAAK,UAAU,IAAI,CAAC,MAAM,KAAK,EAAE;AAC1F,QAAI;AACF,YAAM,SAAS,MAAM,kBAAAC,QAAW,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,EAAE,EAC9D,MAAM,KAAK,EACX,KAAK,IAAI,EACT,MAAM,KAAK,KAAK,EAChB,MAAM,GAAG,CAAC,QAAQ;AACjB,YAAI,UAAU,OAAO;AACnB,iBAAO;AAAA,QACT;AACA,YAAI,IAAK,eAAAD,QAAO,KAAK,sCAAsC,IAAI,IAAI,IAAI,IAAI,OAAO,EAAE;AACpF,eAAO;AAAA,MACT,CAAC,EACA,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC,EAC9B,IAAI,OAAO,EACX,aAAa,YAAY,EACzB,QAAQ,KAAK,cAAc,CAAC;AAE/B,aAAO,MAAO,SAAsB,OAAO;AAAA,IAC7C,SAAS,OAAO;AACd,UAAI,EAAE,iBAAiB,QAAQ;AAC7B,cAAM;AAAA,MACR;AACA,YAAM,qBAAiB,sBAAAE,SAAc,KAAK;AAC1C,oBAAAF,QAAO,KAAK,EAAE,GAAG,eAAe,GAAG,iBAAiB,KAAK,IAAI,YAAY,IAAI,aAAa,OAAO,YAAY,CAAC,GAAG;AACjH,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,KAAyB,SAA0B,OAAkC;AACzF,WAAO,KAAK,gBAAgB,QAAQ,SAAS,KAAK;AAAA,EACpD;AAAA,EAEA,MAAM,eAAkB;AAAA,IACtB,OAAO;AAAA,IACP,QAAQ,CAAC;AAAA,IACT,UAAU,CAAC;AAAA,IACX,eAAe;AAAA,IACf,MAAM;AAAA,IACN;AAAA,EACF,GAA8C;AAC5C,UAAM,aAAa;AAAA,MACjB,MAAM,GAAG,KAAK,OAAO,GAAG,GAAG,IAAI;AAAA,MAC/B;AAAA,IACF;AACA,kBAAAA,QAAO,KAAK,GAAG,KAAK,IAAI,KAAK,KAAK,UAAU,YAAY,MAAM,CAAC,CAAC,EAAE;AAClE,QAAI;AACF,YAAM,SAAS,MAAM,kBAAAC,QAClB,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,EAAE,EAC7B,MAAM,KAAK,KAAK,EAChB,MAAM,GAAG,CAAC,QAAQ;AACjB,YAAI,IAAK,eAAAD,QAAO,KAAK,sCAAsC,IAAI,IAAI,IAAI,IAAI,OAAO,EAAE;AACpF,eAAO;AAAA,MACT,CAAC,EACA,MAAM,KAAK,EACX,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC,EAC9B,IAAI,OAAO,EACX,aAAa,YAAY,EACzB,QAAQ,KAAK,cAAc,CAAC;AAE/B,aAAO;AAAA,QACL,MAAM,MAAM,SAAS,OAAO;AAAA,QAC5B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,qBAAiB,sBAAAE,SAA6B,KAAK;AACzD,oBAAAF,QAAO,KAAK,EAAE,GAAG,gBAAgB,MAAM,GAAG,iBAAiB,KAAK,IAAI,YAAY,IAAI,gBAAgB;AACpG,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,kBAAqB;AAAA,IACzB,OAAO;AAAA,IACP,QAAQ,CAAC;AAAA,IACT,UAAU,CAAC;AAAA,IACX,eAAe;AAAA,IACf,MAAM;AAAA,IACN;AAAA,EACF,GAA8C;AAC5C,UAAM,aAAa;AAAA,MACjB,MAAM,GAAG,KAAK,OAAO,GAAG,GAAG,IAAI;AAAA,MAC/B;AAAA,IACF;AACA,kBAAAA,QAAO,KAAK,GAAG,KAAK,IAAI,KAAK,KAAK,UAAU,YAAY,MAAM,CAAC,CAAC,EAAE;AAClE,QAAI;AACF,YAAM,SAAS,MAAM,kBAAAC,QAClB,OAAO,GAAG,KAAK,OAAO,CAAC,GAAG,IAAI,EAAE,EAChC,MAAM,KAAK,KAAK,EAChB,MAAM,GAAG,CAAC,QAAQ;AACjB,YAAI,IAAK,eAAAD,QAAO,KAAK,sCAAsC,IAAI,IAAI,IAAI,IAAI,OAAO,EAAE;AACpF,eAAO;AAAA,MACT,CAAC,EACA,MAAM,KAAK,EACX,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC,EAC9B,IAAI,OAAO,EACX,aAAa,YAAY,EACzB,QAAQ,KAAK,cAAc,CAAC;AAE/B,aAAO;AAAA,QACL,MAAM,MAAM,SAAS,OAAO;AAAA,QAC5B,SAAS,OAAO;AAAA,MAClB;AAAA,IACF,SAAS,OAAO;AACd,YAAM,qBAAiB,sBAAAE,SAA6B,KAAK;AACzD,oBAAAF,QAAO,KAAK,EAAE,GAAG,gBAAgB,MAAM,GAAG,iBAAiB,KAAK,IAAI,YAAY,IAAI,gBAAgB;AACpG,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,OAAU,SAAiC;AAC/C,WAAO,KAAK,kBAAqB,OAAO,EAAE,KAAK,CAAC,WAAW,OAAO,IAAI;AAAA,EACxE;AACF;AAGA,IAAO,qBAAQ;",
6
6
  "names": ["Agent", "logger", "superagent", "sanitiseError"]
7
7
  }
@@ -1,5 +1,6 @@
1
1
  import superagent, { ResponseError } from 'superagent'
2
2
  import Agent, { HttpsAgent } from 'agentkeepalive'
3
+ import { Response as ExpressResponse } from 'express'
3
4
 
4
5
  import logger from '../utils/logger'
5
6
  import sanitiseError from '../utils/sanitisedError'
@@ -42,6 +43,45 @@ class RestClient {
42
43
  return this.getWithHeaders<T>(request).then((result) => result.data)
43
44
  }
44
45
 
46
+ async getStream({ path = '', query = {}, headers = {}, token }: GetRequest, res: ExpressResponse): Promise<void> {
47
+ logger.info(`${this.name} STREAM GET: ${this.apiUrl()}${path}`)
48
+ logger.info(`query: ${JSON.stringify(query)}`)
49
+
50
+ const req = superagent
51
+ .get(`${this.apiUrl()}${path}`)
52
+ .agent(this.agent)
53
+ .query(query)
54
+ .auth(token, { type: 'bearer' })
55
+ .set(headers)
56
+ .timeout(this.timeoutConfig())
57
+
58
+ req.on('response', (upstream) => {
59
+ // Forward status
60
+ res.status(upstream.status)
61
+
62
+ // Forward headers
63
+ Object.entries(upstream.headers).forEach(([key, value]) => {
64
+ if (value !== undefined) {
65
+ res.setHeader(key, value as string)
66
+ }
67
+ })
68
+ res.on('close', () => {
69
+ logger.info('Client disconnected, aborting upstream request.')
70
+ req.abort()
71
+ })
72
+ upstream.pipe(res)
73
+ })
74
+
75
+ req.on('error', (error) => {
76
+ logger.warn({ error }, `Error streaming from ${this.name}, path: '${path}'`)
77
+ if (!res.headersSent) {
78
+ res.status(502).end('Download request failed. Error streaming response')
79
+ } else {
80
+ res.destroy(error)
81
+ }
82
+ })
83
+ }
84
+
45
85
  private async requestWithBody<Response = unknown>(
46
86
  method: 'patch' | 'post' | 'put',
47
87
  { path, query = {}, headers = {}, responseType = '', data = {}, raw = false, retry = false }: RequestWithBody,
@@ -39,6 +39,7 @@ var import_node = require("@sentry/node");
39
39
  var import_definitionUtils = __toESM(require("../utils/definitionUtils"));
40
40
  var import_localsHelper = __toESM(require("../utils/localsHelper"));
41
41
  var import_featureFlagService = require("../services/featureFlagService");
42
+ var import_logger = __toESM(require("../utils/logger"));
42
43
  const getQueryParamAsString = (query, name) => query[name] ? query[name].toString() : null;
43
44
  const getDefinitionsPath = (query) => getQueryParamAsString(query, "dataProductDefinitionsPath");
44
45
  const deriveDefinitionsPath = (query) => {
@@ -98,6 +99,14 @@ const setFeatures = async (res, featureFlagService) => {
98
99
  throw e;
99
100
  });
100
101
  res.app.locals.featureFlags.flags = Object.fromEntries(flags.flags.map((flag) => [flag.key, flag]));
102
+ import_logger.default.info(
103
+ {
104
+ flags: Object.fromEntries(
105
+ Object.entries(res.app.locals.featureFlags.flags).map(([k, v]) => [k, { key: v.key, enabled: v.enabled }])
106
+ )
107
+ },
108
+ "Feature Flags updated."
109
+ );
101
110
  }
102
111
  };
103
112
  const populateValidationErrors = (req, res) => {
@@ -154,7 +163,7 @@ const populateRequestedReports = async (services, res) => {
154
163
  res.locals["bookmarkingEnabled"] = services.bookmarkService.enabled;
155
164
  res.locals["collectionsEnabled"] = services.productCollectionService.enabled;
156
165
  res.locals["requestMissingEnabled"] = services.missingReportService.enabled;
157
- res.locals["saveDefaultsEnabled"] = (0, import_featureFlagService.isBooleanFlagEnabled)("saveDefaultsEnabled", res.app);
166
+ res.locals["saveDefaultsEnabled"] = (0, import_featureFlagService.isBooleanFlagEnabledOrMissing)("saveDefaultsEnabled", res.app);
158
167
  if (res.locals["bookmarkingEnabled"]) {
159
168
  const bookmarks = await services.bookmarkService.getAllBookmarks(dprUser.id);
160
169
  res.locals["bookmarks"] = !definitionsPath ? bookmarks : bookmarks.filter((bookmark) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../dpr/middleware/setUpDprResources.ts"],
4
- "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RequestHandler, Response, Request, ErrorRequestHandler, NextFunction } from 'express'\nimport type { ParsedQs } from 'qs'\nimport { HTTPError } from 'superagent'\nimport type { Environment } from 'nunjucks'\nimport { captureException } from '@sentry/node'\nimport { Services } from '../types/Services'\nimport { RequestedReport, StoredReportData } from '../types/UserReports'\nimport DefinitionUtils from '../utils/definitionUtils'\nimport { BookmarkStoreData } from '../types/Bookmark'\nimport { DprConfig } from '../types/DprConfig'\nimport localsHelper from '../utils/localsHelper'\nimport { FeatureFlagService, isBooleanFlagEnabled } from '../services/featureFlagService'\n\nconst getQueryParamAsString = (query: ParsedQs, name: string) => (query[name] ? query[name].toString() : null)\nconst getDefinitionsPath = (query: ParsedQs) => getQueryParamAsString(query, 'dataProductDefinitionsPath')\n\nconst deriveDefinitionsPath = (query: ParsedQs): string | null => {\n const definitionsPath = getDefinitionsPath(query)\n if (definitionsPath) {\n return definitionsPath\n }\n\n return null\n}\n\nexport const errorRequestHandler =\n (layoutPath: string): ErrorRequestHandler =>\n (error: HTTPError, _req: Request, res: Response, next: NextFunction) => {\n if (error.status === 401 || error.status === 403) {\n return res.render('dpr/routes/authError.njk', {\n layoutPath,\n message: 'Sorry, there is a problem with authenticating your request',\n })\n }\n captureException(error)\n if (error.status >= 400) {\n return res.render('dpr/routes/serviceProblem.njk', {\n layoutPath,\n })\n }\n return next(error)\n }\n\nexport const setupResources = (\n services: Services,\n layoutPath: string,\n env: Environment,\n config?: DprConfig,\n): RequestHandler => {\n return async (req, res, next) => {\n populateValidationErrors(req, res)\n try {\n await setFeatures(res, services.featureFlagService)\n await populateDefinitions(services, req, res, config)\n await populateRequestedReports(services, res)\n setupRequestAwareNunjucks(env, res)\n return next()\n } catch (error) {\n return errorRequestHandler(layoutPath)(error, req, res, next)\n }\n }\n}\n\nconst setupRequestAwareNunjucks = (env: Environment, res: Response) => {\n env.addGlobal('getLocals', () => ({ locals: { ...res.locals, ...res.app.locals } }))\n}\n\nconst setFeatures = async (res: Response, featureFlagService: FeatureFlagService) => {\n if (res.app.locals.featureFlags === undefined) {\n res.app.locals.featureFlags = {\n flags: {},\n lastUpdated: new Date().getTime() - 601 * 1000,\n }\n }\n const { featureFlags } = res.app.locals\n const currentTime = new Date().getTime()\n const timeSinceLastUpdatedSeconds = (currentTime - featureFlags.lastUpdated) / 1000\n const shouldUpdate = timeSinceLastUpdatedSeconds > 600\n if (shouldUpdate) {\n // Refresh every 10 mins\n res.app.locals.featureFlags.lastUpdated = currentTime\n const flags = await featureFlagService.getFlags().catch((e) => {\n res.app.locals.featureFlags.lastUpdated = currentTime - 601 * 1000\n throw e\n })\n res.app.locals.featureFlags.flags = Object.fromEntries(flags.flags.map((flag) => [flag.key, flag]))\n }\n}\n\nconst populateValidationErrors = (req: Request, res: Response) => {\n const errors = req.flash(`DPR_ERRORS`)\n if (errors && errors[0]) {\n res.locals['validationErrors'] = JSON.parse(errors[0])\n }\n}\n\nexport const populateDefinitions = async (services: Services, req: Request, res: Response, config?: DprConfig) => {\n // Get the DPD path from the query\n const { token, dprUser } = localsHelper.getValues(res)\n\n const dpdPathFromQuery = deriveDefinitionsPath(req.query)\n const dpdPathFromBody = req.body?.dataProductDefinitionsPath\n const definitionsPathFromQuery = dpdPathFromQuery || dpdPathFromBody\n\n if (definitionsPathFromQuery) {\n res.locals['dpdPathFromQuery'] = true\n }\n\n // Get the DPD path from the config\n const dpdPathFromConfig = config?.dataProductDefinitionsPath\n if (dpdPathFromConfig) {\n res.locals['dpdPathFromConfig'] = true\n }\n\n // query takes presedence over config\n res.locals['definitionsPath'] = definitionsPathFromQuery || dpdPathFromConfig\n res.locals['pathSuffix'] = `?dataProductDefinitionsPath=${res.locals['definitionsPath']}`\n\n let selectedProductCollectionId: string | undefined\n if (token) {\n selectedProductCollectionId = await services.productCollectionStoreService.getSelectedProductCollectionId(\n dprUser.id,\n )\n }\n\n res.locals['definitions'] =\n (await Promise.all([\n services.reportingService.getDefinitions(token, res.locals['definitionsPath']),\n selectedProductCollectionId &&\n services.productCollectionService.getProductCollection(token, selectedProductCollectionId),\n ]).then(([defs, selectedProductCollection]) => {\n if (selectedProductCollection && selectedProductCollection) {\n const productIds = selectedProductCollection.products.map((product) => product.productId)\n return defs.filter((def) => productIds.includes(def.id))\n }\n return defs\n })) ?? []\n}\n\nexport const populateRequestedReports = async (services: Services, res: Response) => {\n const { dprUser } = localsHelper.getValues(res)\n if (dprUser.id) {\n const { definitions, definitionsPath } = res.locals\n\n const recent = await services.recentlyViewedService.getAllReports(dprUser.id)\n await services.requestedReportService.cleanList(dprUser.id, recent)\n const requested = await services.requestedReportService.getAllReports(dprUser.id)\n\n res.locals['requestedReports'] = !definitionsPath\n ? requested\n : requested.filter((report: RequestedReport) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, report.reportId, report.id)\n })\n\n res.locals['recentlyViewedReports'] = !definitionsPath\n ? recent\n : recent.filter((report: StoredReportData) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, report.reportId, report.id)\n })\n\n res.locals['downloadingEnabled'] = services.downloadPermissionService.enabled\n res.locals['bookmarkingEnabled'] = services.bookmarkService.enabled\n res.locals['collectionsEnabled'] = services.productCollectionService.enabled\n res.locals['requestMissingEnabled'] = services.missingReportService.enabled\n res.locals['saveDefaultsEnabled'] = isBooleanFlagEnabled('saveDefaultsEnabled', res.app)\n\n if (res.locals['bookmarkingEnabled']) {\n const bookmarks = await services.bookmarkService.getAllBookmarks(dprUser.id)\n res.locals['bookmarks'] = !definitionsPath\n ? bookmarks\n : bookmarks.filter((bookmark: BookmarkStoreData) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, bookmark.reportId, bookmark.id)\n })\n }\n }\n}\n\nexport default setupResources\n"],
5
- "mappings": "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",
6
- "names": ["localsHelper", "DefinitionUtils"]
4
+ "sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RequestHandler, Response, Request, ErrorRequestHandler, NextFunction } from 'express'\nimport type { ParsedQs } from 'qs'\nimport { HTTPError } from 'superagent'\nimport type { Environment } from 'nunjucks'\nimport { captureException } from '@sentry/node'\nimport { Services } from '../types/Services'\nimport { RequestedReport, StoredReportData } from '../types/UserReports'\nimport DefinitionUtils from '../utils/definitionUtils'\nimport { BookmarkStoreData } from '../types/Bookmark'\nimport { DprConfig } from '../types/DprConfig'\nimport localsHelper from '../utils/localsHelper'\nimport { FeatureFlagService, isBooleanFlagEnabledOrMissing } from '../services/featureFlagService'\nimport logger from '../utils/logger'\n\nconst getQueryParamAsString = (query: ParsedQs, name: string) => (query[name] ? query[name].toString() : null)\nconst getDefinitionsPath = (query: ParsedQs) => getQueryParamAsString(query, 'dataProductDefinitionsPath')\n\nconst deriveDefinitionsPath = (query: ParsedQs): string | null => {\n const definitionsPath = getDefinitionsPath(query)\n if (definitionsPath) {\n return definitionsPath\n }\n\n return null\n}\n\nexport const errorRequestHandler =\n (layoutPath: string): ErrorRequestHandler =>\n (error: HTTPError, _req: Request, res: Response, next: NextFunction) => {\n if (error.status === 401 || error.status === 403) {\n return res.render('dpr/routes/authError.njk', {\n layoutPath,\n message: 'Sorry, there is a problem with authenticating your request',\n })\n }\n captureException(error)\n if (error.status >= 400) {\n return res.render('dpr/routes/serviceProblem.njk', {\n layoutPath,\n })\n }\n return next(error)\n }\n\nexport const setupResources = (\n services: Services,\n layoutPath: string,\n env: Environment,\n config?: DprConfig,\n): RequestHandler => {\n return async (req, res, next) => {\n populateValidationErrors(req, res)\n try {\n await setFeatures(res, services.featureFlagService)\n await populateDefinitions(services, req, res, config)\n await populateRequestedReports(services, res)\n setupRequestAwareNunjucks(env, res)\n return next()\n } catch (error) {\n return errorRequestHandler(layoutPath)(error, req, res, next)\n }\n }\n}\n\nconst setupRequestAwareNunjucks = (env: Environment, res: Response) => {\n env.addGlobal('getLocals', () => ({ locals: { ...res.locals, ...res.app.locals } }))\n}\n\nconst setFeatures = async (res: Response, featureFlagService: FeatureFlagService) => {\n if (res.app.locals.featureFlags === undefined) {\n res.app.locals.featureFlags = {\n flags: {},\n lastUpdated: new Date().getTime() - 601 * 1000,\n }\n }\n const { featureFlags } = res.app.locals\n const currentTime = new Date().getTime()\n const timeSinceLastUpdatedSeconds = (currentTime - featureFlags.lastUpdated) / 1000\n const shouldUpdate = timeSinceLastUpdatedSeconds > 600\n if (shouldUpdate) {\n // Refresh every 10 mins\n res.app.locals.featureFlags.lastUpdated = currentTime\n const flags = await featureFlagService.getFlags().catch((e) => {\n res.app.locals.featureFlags.lastUpdated = currentTime - 601 * 1000\n throw e\n })\n res.app.locals.featureFlags.flags = Object.fromEntries(flags.flags.map((flag) => [flag.key, flag]))\n logger.info(\n {\n flags: Object.fromEntries(\n Object.entries(res.app.locals.featureFlags.flags).map(([k, v]) => [k, { key: v.key, enabled: v.enabled }]),\n ),\n },\n 'Feature Flags updated.',\n )\n }\n}\n\nconst populateValidationErrors = (req: Request, res: Response) => {\n const errors = req.flash(`DPR_ERRORS`)\n if (errors && errors[0]) {\n res.locals['validationErrors'] = JSON.parse(errors[0])\n }\n}\n\nexport const populateDefinitions = async (services: Services, req: Request, res: Response, config?: DprConfig) => {\n // Get the DPD path from the query\n const { token, dprUser } = localsHelper.getValues(res)\n\n const dpdPathFromQuery = deriveDefinitionsPath(req.query)\n const dpdPathFromBody = req.body?.dataProductDefinitionsPath\n const definitionsPathFromQuery = dpdPathFromQuery || dpdPathFromBody\n\n if (definitionsPathFromQuery) {\n res.locals['dpdPathFromQuery'] = true\n }\n\n // Get the DPD path from the config\n const dpdPathFromConfig = config?.dataProductDefinitionsPath\n if (dpdPathFromConfig) {\n res.locals['dpdPathFromConfig'] = true\n }\n\n // query takes presedence over config\n res.locals['definitionsPath'] = definitionsPathFromQuery || dpdPathFromConfig\n res.locals['pathSuffix'] = `?dataProductDefinitionsPath=${res.locals['definitionsPath']}`\n\n let selectedProductCollectionId: string | undefined\n if (token) {\n selectedProductCollectionId = await services.productCollectionStoreService.getSelectedProductCollectionId(\n dprUser.id,\n )\n }\n\n res.locals['definitions'] =\n (await Promise.all([\n services.reportingService.getDefinitions(token, res.locals['definitionsPath']),\n selectedProductCollectionId &&\n services.productCollectionService.getProductCollection(token, selectedProductCollectionId),\n ]).then(([defs, selectedProductCollection]) => {\n if (selectedProductCollection && selectedProductCollection) {\n const productIds = selectedProductCollection.products.map((product) => product.productId)\n return defs.filter((def) => productIds.includes(def.id))\n }\n return defs\n })) ?? []\n}\n\nexport const populateRequestedReports = async (services: Services, res: Response) => {\n const { dprUser } = localsHelper.getValues(res)\n if (dprUser.id) {\n const { definitions, definitionsPath } = res.locals\n\n const recent = await services.recentlyViewedService.getAllReports(dprUser.id)\n await services.requestedReportService.cleanList(dprUser.id, recent)\n const requested = await services.requestedReportService.getAllReports(dprUser.id)\n\n res.locals['requestedReports'] = !definitionsPath\n ? requested\n : requested.filter((report: RequestedReport) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, report.reportId, report.id)\n })\n\n res.locals['recentlyViewedReports'] = !definitionsPath\n ? recent\n : recent.filter((report: StoredReportData) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, report.reportId, report.id)\n })\n\n res.locals['downloadingEnabled'] = services.downloadPermissionService.enabled\n res.locals['bookmarkingEnabled'] = services.bookmarkService.enabled\n res.locals['collectionsEnabled'] = services.productCollectionService.enabled\n res.locals['requestMissingEnabled'] = services.missingReportService.enabled\n res.locals['saveDefaultsEnabled'] = isBooleanFlagEnabledOrMissing('saveDefaultsEnabled', res.app)\n\n if (res.locals['bookmarkingEnabled']) {\n const bookmarks = await services.bookmarkService.getAllBookmarks(dprUser.id)\n res.locals['bookmarks'] = !definitionsPath\n ? bookmarks\n : bookmarks.filter((bookmark: BookmarkStoreData) => {\n return DefinitionUtils.getCurrentVariantDefinition(definitions, bookmark.reportId, bookmark.id)\n })\n }\n }\n}\n\nexport default setupResources\n"],
5
+ "mappings": "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",
6
+ "names": ["logger", "localsHelper", "DefinitionUtils"]
7
7
  }
@@ -10,7 +10,8 @@ import DefinitionUtils from '../utils/definitionUtils'
10
10
  import { BookmarkStoreData } from '../types/Bookmark'
11
11
  import { DprConfig } from '../types/DprConfig'
12
12
  import localsHelper from '../utils/localsHelper'
13
- import { FeatureFlagService, isBooleanFlagEnabled } from '../services/featureFlagService'
13
+ import { FeatureFlagService, isBooleanFlagEnabledOrMissing } from '../services/featureFlagService'
14
+ import logger from '../utils/logger'
14
15
 
15
16
  const getQueryParamAsString = (query: ParsedQs, name: string) => (query[name] ? query[name].toString() : null)
16
17
  const getDefinitionsPath = (query: ParsedQs) => getQueryParamAsString(query, 'dataProductDefinitionsPath')
@@ -85,6 +86,14 @@ const setFeatures = async (res: Response, featureFlagService: FeatureFlagService
85
86
  throw e
86
87
  })
87
88
  res.app.locals.featureFlags.flags = Object.fromEntries(flags.flags.map((flag) => [flag.key, flag]))
89
+ logger.info(
90
+ {
91
+ flags: Object.fromEntries(
92
+ Object.entries(res.app.locals.featureFlags.flags).map(([k, v]) => [k, { key: v.key, enabled: v.enabled }]),
93
+ ),
94
+ },
95
+ 'Feature Flags updated.',
96
+ )
88
97
  }
89
98
  }
90
99
 
@@ -163,7 +172,7 @@ export const populateRequestedReports = async (services: Services, res: Response
163
172
  res.locals['bookmarkingEnabled'] = services.bookmarkService.enabled
164
173
  res.locals['collectionsEnabled'] = services.productCollectionService.enabled
165
174
  res.locals['requestMissingEnabled'] = services.missingReportService.enabled
166
- res.locals['saveDefaultsEnabled'] = isBooleanFlagEnabled('saveDefaultsEnabled', res.app)
175
+ res.locals['saveDefaultsEnabled'] = isBooleanFlagEnabledOrMissing('saveDefaultsEnabled', res.app)
167
176
 
168
177
  if (res.locals['bookmarkingEnabled']) {
169
178
  const bookmarks = await services.bookmarkService.getAllBookmarks(dprUser.id)
@@ -37,6 +37,8 @@ var import_json_2_csv = require("json-2-csv");
37
37
  var import_UserReports = require("../../../types/UserReports");
38
38
  var import_localsHelper = __toESM(require("../../../utils/localsHelper"));
39
39
  var import_ReportQuery = __toESM(require("../../../types/ReportQuery"));
40
+ var import_featureFlagService = require("../../../services/featureFlagService");
41
+ var import_logger = __toESM(require("../../../utils/logger"));
40
42
  const convertToCsv = (reportData, options) => {
41
43
  const csvData = (0, import_json_2_csv.json2csv)(reportData, options);
42
44
  return csvData;
@@ -89,6 +91,13 @@ const removeHtmlTags = (reportData, reportDefinition) => {
89
91
  }
90
92
  return reportData;
91
93
  };
94
+ const streamDownloadAsyncData = async (args) => {
95
+ const { token, services, tableId, reportId, id, queryParams, res } = args;
96
+ const query = {
97
+ ...queryParams
98
+ };
99
+ return services.reportingService.downloadAsyncReport(token, reportId, id, tableId, query, res);
100
+ };
92
101
  const dowloadAsyncData = async (args) => {
93
102
  const { token, services, tableId, reportId, id, queryParams } = args;
94
103
  const pageSize = await services.reportingService.getAsyncCount(token, tableId);
@@ -146,6 +155,8 @@ const downloadReport = async ({
146
155
  } = req.body;
147
156
  const { downloadPermissionService } = services;
148
157
  const canDownloadReport = await downloadPermissionService.downloadEnabledForReport(dprUser.id, reportId, id);
158
+ const streamingEnabledInFlipt = (0, import_featureFlagService.isBooleanFlagExplicitlyEnabled)("streamingDownloadEnabled", res.app);
159
+ const streamingEnabledInEnv = process.env["STREAMING_DOWNLOAD_ENABLED"] === "true";
149
160
  if (!canDownloadReport) {
150
161
  res.redirect(redirect);
151
162
  } else {
@@ -164,6 +175,27 @@ const downloadReport = async ({
164
175
  queryParams
165
176
  });
166
177
  } else {
178
+ import_logger.default.info(`Streaming enabled in Flipt: ${streamingEnabledInFlipt}`);
179
+ import_logger.default.info(`Streaming enabled in Env: ${streamingEnabledInEnv}`);
180
+ if (streamingEnabledInFlipt || streamingEnabledInEnv) {
181
+ import_logger.default.info(`Initiating streaming...`);
182
+ const streamDownloadQueryParams = {
183
+ dataProductDefinitionsPath,
184
+ ...columns && { columns: JSON.parse(columns) },
185
+ ...sortedAsc && { sortedAsc },
186
+ ...sortColumn && { sortColumn }
187
+ };
188
+ await streamDownloadAsyncData({
189
+ services,
190
+ token,
191
+ reportId,
192
+ id,
193
+ tableId,
194
+ queryParams: streamDownloadQueryParams,
195
+ res
196
+ });
197
+ return;
198
+ }
167
199
  reportData = await dowloadAsyncData({
168
200
  services,
169
201
  token,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../dpr/routes/journeys/download-report/utils.ts"],
4
- "sourcesContent": ["import { Response, Request } from 'express'\nimport { json2csv, Json2CsvOptions } from 'json-2-csv'\nimport { KeysList } from 'json-2-csv/lib/types'\nimport { Services } from '../../../types/Services'\nimport Dict = NodeJS.Dict\nimport { LoadType } from '../../../types/UserReports'\nimport { components } from '../../../types/api'\nimport LocalsHelper from '../../../utils/localsHelper'\nimport { Template } from '../../../types/Templates'\nimport ReportQuery from '../../../types/ReportQuery'\n\nconst convertToCsv = (reportData: Dict<string>[], options: Json2CsvOptions) => {\n const csvData = json2csv(reportData, options)\n return csvData\n}\n\nexport const getKeys = (reportData: Dict<string>[], fields: components['schemas']['FieldDefinition'][]): KeysList => {\n const keys: KeysList = []\n const keyNames: string[] = []\n reportData.forEach((row) => {\n Object.keys(row).forEach((key) => {\n const field = fields.find((f) => f.name === key)\n if (field && !keyNames.includes(key)) {\n keyNames.push(key)\n keys.push({\n field: key,\n title: field.display,\n })\n }\n })\n })\n return keys\n}\n\nconst applyColumnsAndSort = (data: Dict<string>[], columns: string[]) => {\n return data.map((row) => {\n return Object.keys(row)\n .filter((key) => columns.includes(key))\n .reduce((obj: Dict<string>, key) => {\n // eslint-disable-next-line no-param-reassign\n obj[key] = row[key]\n return obj\n }, {})\n })\n}\n\nconst removeHtmlTags = (\n reportData: Dict<string>[],\n reportDefinition: components['schemas']['SingleVariantReportDefinition'],\n) => {\n // Find HMTL field + name\n const { specification } = reportDefinition.variant\n if (specification) {\n const { fields } = specification\n const htmlFields = fields.filter((field) => {\n return field.type === 'HTML'\n })\n\n if (htmlFields.length) {\n reportData.map((d) => {\n htmlFields.forEach((field) => {\n const { name } = field\n const colValue = d[name]\n if (colValue) {\n const innerText = /target=\"_blank\">(.*?)<\\/a>/g.exec(colValue)\n // eslint-disable-next-line prefer-destructuring, no-param-reassign\n d[name] = innerText ? innerText[1] : colValue\n }\n })\n return d\n })\n }\n }\n return reportData\n}\n\nconst dowloadAsyncData = async (args: {\n services: Services\n token: string\n tableId: string\n reportId: string\n id: string\n queryParams: {\n dataProductDefinitionsPath: string\n sortedAsc?: string\n sortColumn?: string\n }\n}) => {\n const { token, services, tableId, reportId, id, queryParams } = args\n const pageSize = await services.reportingService.getAsyncCount(token, tableId)\n const query: Record<string, string | string[]> = {\n ...queryParams,\n pageSize: pageSize.toString(),\n }\n return services.reportingService.getAsyncReport(token, reportId, id, tableId, query)\n}\n\nconst downloadSyncData = async (args: {\n definition: components['schemas']['SingleVariantReportDefinition']\n services: Services\n token: string\n queryParams: {\n dataProductDefinitionsPath: string\n sortedAsc?: string\n sortColumn?: string\n }\n}) => {\n const { token, services, queryParams, definition } = args\n const { variant } = definition\n const { resourceName, specification } = variant\n let data: Dict<string>[] = []\n\n if (specification) {\n const countReportQuery = new ReportQuery({\n fields: specification.fields,\n template: specification.template as Template,\n queryParams,\n definitionsPath: <string>queryParams.dataProductDefinitionsPath,\n })\n const count = await services.reportingService.getCount(resourceName, token, countReportQuery)\n\n const dataReportQuery = new ReportQuery({\n fields: specification.fields,\n template: specification.template as Template,\n queryParams: {\n ...queryParams,\n pageSize: count.toString(),\n },\n definitionsPath: <string>queryParams.dataProductDefinitionsPath,\n })\n const reportData = await services.reportingService.getListWithWarnings(resourceName, token, dataReportQuery)\n data = reportData.data\n }\n\n return data\n}\n\nexport const downloadReport = async ({\n req,\n services,\n res,\n redirect,\n loadType,\n}: {\n req: Request\n services: Services\n res: Response\n redirect: string\n loadType?: LoadType\n}) => {\n const { dprUser, token } = LocalsHelper.getValues(res)\n const {\n reportId,\n id,\n tableId,\n dataProductDefinitionsPath,\n reportName,\n name,\n cols: columns,\n sortedAsc,\n sortColumn,\n } = req.body\n const { downloadPermissionService } = services\n const canDownloadReport = await downloadPermissionService.downloadEnabledForReport(dprUser.id, reportId, id)\n if (!canDownloadReport) {\n res.redirect(redirect)\n } else {\n const definition = await services.reportingService.getDefinition(token, reportId, id, dataProductDefinitionsPath)\n const queryParams = {\n dataProductDefinitionsPath,\n ...(sortedAsc && { sortedAsc }),\n ...(sortColumn && { sortColumn }),\n }\n\n let reportData\n if (loadType === LoadType.SYNC) {\n reportData = await downloadSyncData({\n definition,\n services,\n token,\n queryParams,\n })\n } else {\n reportData = await dowloadAsyncData({\n services,\n token,\n reportId,\n id,\n tableId,\n queryParams,\n })\n }\n if (columns) {\n reportData = applyColumnsAndSort(reportData, JSON.parse(columns))\n }\n reportData = removeHtmlTags(reportData, definition)\n const fields = definition.variant.specification?.fields || []\n const keys: KeysList = getKeys(reportData, fields)\n const csvData = convertToCsv(reportData, { keys, emptyFieldValue: '' })\n\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Content-disposition', `attachment; filename=${reportName}-${name}-${new Date().toISOString()}.csv`)\n res.end(csvData)\n }\n}\n\nexport default {\n downloadReport,\n getKeys,\n}\n"],
5
- "mappings": "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",
6
- "names": ["ReportQuery", "LocalsHelper"]
4
+ "sourcesContent": ["import { Response, Request } from 'express'\nimport { json2csv, Json2CsvOptions } from 'json-2-csv'\nimport { KeysList } from 'json-2-csv/lib/types'\nimport { Services } from '../../../types/Services'\nimport Dict = NodeJS.Dict\nimport { LoadType } from '../../../types/UserReports'\nimport { components } from '../../../types/api'\nimport LocalsHelper from '../../../utils/localsHelper'\nimport { Template } from '../../../types/Templates'\nimport ReportQuery from '../../../types/ReportQuery'\nimport { isBooleanFlagExplicitlyEnabled } from '../../../services/featureFlagService'\nimport logger from '../../../utils/logger'\n\nconst convertToCsv = (reportData: Dict<string>[], options: Json2CsvOptions) => {\n const csvData = json2csv(reportData, options)\n return csvData\n}\n\nexport const getKeys = (reportData: Dict<string>[], fields: components['schemas']['FieldDefinition'][]): KeysList => {\n const keys: KeysList = []\n const keyNames: string[] = []\n reportData.forEach((row) => {\n Object.keys(row).forEach((key) => {\n const field = fields.find((f) => f.name === key)\n if (field && !keyNames.includes(key)) {\n keyNames.push(key)\n keys.push({\n field: key,\n title: field.display,\n })\n }\n })\n })\n return keys\n}\n\nconst applyColumnsAndSort = (data: Dict<string>[], columns: string[]) => {\n return data.map((row) => {\n return Object.keys(row)\n .filter((key) => columns.includes(key))\n .reduce((obj: Dict<string>, key) => {\n // eslint-disable-next-line no-param-reassign\n obj[key] = row[key]\n return obj\n }, {})\n })\n}\n\nconst removeHtmlTags = (\n reportData: Dict<string>[],\n reportDefinition: components['schemas']['SingleVariantReportDefinition'],\n) => {\n // Find HMTL field + name\n const { specification } = reportDefinition.variant\n if (specification) {\n const { fields } = specification\n const htmlFields = fields.filter((field) => {\n return field.type === 'HTML'\n })\n\n if (htmlFields.length) {\n reportData.map((d) => {\n htmlFields.forEach((field) => {\n const { name } = field\n const colValue = d[name]\n if (colValue) {\n const innerText = /target=\"_blank\">(.*?)<\\/a>/g.exec(colValue)\n // eslint-disable-next-line prefer-destructuring, no-param-reassign\n d[name] = innerText ? innerText[1] : colValue\n }\n })\n return d\n })\n }\n }\n return reportData\n}\n\nconst streamDownloadAsyncData = async (args: {\n services: Services\n token: string\n tableId: string\n reportId: string\n id: string\n queryParams: {\n dataProductDefinitionsPath: string\n columns: string[]\n sortedAsc?: string\n sortColumn?: string\n }\n res: Response\n}) => {\n const { token, services, tableId, reportId, id, queryParams, res } = args\n const query: Record<string, string | string[]> = {\n ...queryParams,\n }\n return services.reportingService.downloadAsyncReport(token, reportId, id, tableId, query, res)\n}\n\nconst dowloadAsyncData = async (args: {\n services: Services\n token: string\n tableId: string\n reportId: string\n id: string\n queryParams: {\n dataProductDefinitionsPath: string\n sortedAsc?: string\n sortColumn?: string\n }\n}) => {\n const { token, services, tableId, reportId, id, queryParams } = args\n const pageSize = await services.reportingService.getAsyncCount(token, tableId)\n const query: Record<string, string | string[]> = {\n ...queryParams,\n pageSize: pageSize.toString(),\n }\n return services.reportingService.getAsyncReport(token, reportId, id, tableId, query)\n}\n\nconst downloadSyncData = async (args: {\n definition: components['schemas']['SingleVariantReportDefinition']\n services: Services\n token: string\n queryParams: {\n dataProductDefinitionsPath: string\n sortedAsc?: string\n sortColumn?: string\n }\n}) => {\n const { token, services, queryParams, definition } = args\n const { variant } = definition\n const { resourceName, specification } = variant\n let data: Dict<string>[] = []\n\n if (specification) {\n const countReportQuery = new ReportQuery({\n fields: specification.fields,\n template: specification.template as Template,\n queryParams,\n definitionsPath: <string>queryParams.dataProductDefinitionsPath,\n })\n const count = await services.reportingService.getCount(resourceName, token, countReportQuery)\n\n const dataReportQuery = new ReportQuery({\n fields: specification.fields,\n template: specification.template as Template,\n queryParams: {\n ...queryParams,\n pageSize: count.toString(),\n },\n definitionsPath: <string>queryParams.dataProductDefinitionsPath,\n })\n const reportData = await services.reportingService.getListWithWarnings(resourceName, token, dataReportQuery)\n data = reportData.data\n }\n\n return data\n}\n\nexport const downloadReport = async ({\n req,\n services,\n res,\n redirect,\n loadType,\n}: {\n req: Request\n services: Services\n res: Response\n redirect: string\n loadType?: LoadType\n}) => {\n const { dprUser, token } = LocalsHelper.getValues(res)\n const {\n reportId,\n id,\n tableId,\n dataProductDefinitionsPath,\n reportName,\n name,\n cols: columns,\n sortedAsc,\n sortColumn,\n } = req.body\n const { downloadPermissionService } = services\n const canDownloadReport = await downloadPermissionService.downloadEnabledForReport(dprUser.id, reportId, id)\n const streamingEnabledInFlipt = isBooleanFlagExplicitlyEnabled('streamingDownloadEnabled', res.app)\n const streamingEnabledInEnv = process.env['STREAMING_DOWNLOAD_ENABLED'] === 'true'\n\n if (!canDownloadReport) {\n res.redirect(redirect)\n } else {\n const definition = await services.reportingService.getDefinition(token, reportId, id, dataProductDefinitionsPath)\n const queryParams = {\n dataProductDefinitionsPath,\n ...(sortedAsc && { sortedAsc }),\n ...(sortColumn && { sortColumn }),\n }\n\n let reportData\n if (loadType === LoadType.SYNC) {\n reportData = await downloadSyncData({\n definition,\n services,\n token,\n queryParams,\n })\n } else {\n logger.info(`Streaming enabled in Flipt: ${streamingEnabledInFlipt}`)\n logger.info(`Streaming enabled in Env: ${streamingEnabledInEnv}`)\n if (streamingEnabledInFlipt || streamingEnabledInEnv) {\n logger.info(`Initiating streaming...`)\n const streamDownloadQueryParams = {\n dataProductDefinitionsPath,\n ...(columns && { columns: JSON.parse(columns) }),\n ...(sortedAsc && { sortedAsc }),\n ...(sortColumn && { sortColumn }),\n }\n await streamDownloadAsyncData({\n services,\n token,\n reportId,\n id,\n tableId,\n queryParams: streamDownloadQueryParams,\n res,\n })\n return\n }\n\n reportData = await dowloadAsyncData({\n services,\n token,\n reportId,\n id,\n tableId,\n queryParams,\n })\n }\n if (columns) {\n reportData = applyColumnsAndSort(reportData, JSON.parse(columns))\n }\n reportData = removeHtmlTags(reportData, definition)\n const fields = definition.variant.specification?.fields || []\n const keys: KeysList = getKeys(reportData, fields)\n const csvData = convertToCsv(reportData, { keys, emptyFieldValue: '' })\n\n res.setHeader('Content-Type', 'application/json')\n res.setHeader('Content-disposition', `attachment; filename=${reportName}-${name}-${new Date().toISOString()}.csv`)\n res.end(csvData)\n }\n}\n\nexport default {\n downloadReport,\n getKeys,\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,wBAA0C;AAI1C,yBAAyB;AAEzB,0BAAyB;AAEzB,yBAAwB;AACxB,gCAA+C;AAC/C,oBAAmB;AAEnB,MAAM,eAAe,CAAC,YAA4B,YAA6B;AAC7E,QAAM,cAAU,4BAAS,YAAY,OAAO;AAC5C,SAAO;AACT;AAEO,MAAM,UAAU,CAAC,YAA4B,WAAiE;AACnH,QAAM,OAAiB,CAAC;AACxB,QAAM,WAAqB,CAAC;AAC5B,aAAW,QAAQ,CAAC,QAAQ;AAC1B,WAAO,KAAK,GAAG,EAAE,QAAQ,CAAC,QAAQ;AAChC,YAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,SAAS,GAAG;AAC/C,UAAI,SAAS,CAAC,SAAS,SAAS,GAAG,GAAG;AACpC,iBAAS,KAAK,GAAG;AACjB,aAAK,KAAK;AAAA,UACR,OAAO;AAAA,UACP,OAAO,MAAM;AAAA,QACf,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,SAAO;AACT;AAEA,MAAM,sBAAsB,CAAC,MAAsB,YAAsB;AACvE,SAAO,KAAK,IAAI,CAAC,QAAQ;AACvB,WAAO,OAAO,KAAK,GAAG,EACnB,OAAO,CAAC,QAAQ,QAAQ,SAAS,GAAG,CAAC,EACrC,OAAO,CAAC,KAAmB,QAAQ;AAElC,UAAI,GAAG,IAAI,IAAI,GAAG;AAClB,aAAO;AAAA,IACT,GAAG,CAAC,CAAC;AAAA,EACT,CAAC;AACH;AAEA,MAAM,iBAAiB,CACrB,YACA,qBACG;AAEH,QAAM,EAAE,cAAc,IAAI,iBAAiB;AAC3C,MAAI,eAAe;AACjB,UAAM,EAAE,OAAO,IAAI;AACnB,UAAM,aAAa,OAAO,OAAO,CAAC,UAAU;AAC1C,aAAO,MAAM,SAAS;AAAA,IACxB,CAAC;AAED,QAAI,WAAW,QAAQ;AACrB,iBAAW,IAAI,CAAC,MAAM;AACpB,mBAAW,QAAQ,CAAC,UAAU;AAC5B,gBAAM,EAAE,KAAK,IAAI;AACjB,gBAAM,WAAW,EAAE,IAAI;AACvB,cAAI,UAAU;AACZ,kBAAM,YAAY,8BAA8B,KAAK,QAAQ;AAE7D,cAAE,IAAI,IAAI,YAAY,UAAU,CAAC,IAAI;AAAA,UACvC;AAAA,QACF,CAAC;AACD,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAEA,MAAM,0BAA0B,OAAO,SAajC;AACJ,QAAM,EAAE,OAAO,UAAU,SAAS,UAAU,IAAI,aAAa,IAAI,IAAI;AACrE,QAAM,QAA2C;AAAA,IAC/C,GAAG;AAAA,EACL;AACA,SAAO,SAAS,iBAAiB,oBAAoB,OAAO,UAAU,IAAI,SAAS,OAAO,GAAG;AAC/F;AAEA,MAAM,mBAAmB,OAAO,SAW1B;AACJ,QAAM,EAAE,OAAO,UAAU,SAAS,UAAU,IAAI,YAAY,IAAI;AAChE,QAAM,WAAW,MAAM,SAAS,iBAAiB,cAAc,OAAO,OAAO;AAC7E,QAAM,QAA2C;AAAA,IAC/C,GAAG;AAAA,IACH,UAAU,SAAS,SAAS;AAAA,EAC9B;AACA,SAAO,SAAS,iBAAiB,eAAe,OAAO,UAAU,IAAI,SAAS,KAAK;AACrF;AAEA,MAAM,mBAAmB,OAAO,SAS1B;AACJ,QAAM,EAAE,OAAO,UAAU,aAAa,WAAW,IAAI;AACrD,QAAM,EAAE,QAAQ,IAAI;AACpB,QAAM,EAAE,cAAc,cAAc,IAAI;AACxC,MAAI,OAAuB,CAAC;AAE5B,MAAI,eAAe;AACjB,UAAM,mBAAmB,IAAI,mBAAAA,QAAY;AAAA,MACvC,QAAQ,cAAc;AAAA,MACtB,UAAU,cAAc;AAAA,MACxB;AAAA,MACA,iBAAyB,YAAY;AAAA,IACvC,CAAC;AACD,UAAM,QAAQ,MAAM,SAAS,iBAAiB,SAAS,cAAc,OAAO,gBAAgB;AAE5F,UAAM,kBAAkB,IAAI,mBAAAA,QAAY;AAAA,MACtC,QAAQ,cAAc;AAAA,MACtB,UAAU,cAAc;AAAA,MACxB,aAAa;AAAA,QACX,GAAG;AAAA,QACH,UAAU,MAAM,SAAS;AAAA,MAC3B;AAAA,MACA,iBAAyB,YAAY;AAAA,IACvC,CAAC;AACD,UAAM,aAAa,MAAM,SAAS,iBAAiB,oBAAoB,cAAc,OAAO,eAAe;AAC3G,WAAO,WAAW;AAAA,EACpB;AAEA,SAAO;AACT;AAEO,MAAM,iBAAiB,OAAO;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAMM;AACJ,QAAM,EAAE,SAAS,MAAM,IAAI,oBAAAC,QAAa,UAAU,GAAG;AACrD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF,IAAI,IAAI;AACR,QAAM,EAAE,0BAA0B,IAAI;AACtC,QAAM,oBAAoB,MAAM,0BAA0B,yBAAyB,QAAQ,IAAI,UAAU,EAAE;AAC3G,QAAM,8BAA0B,0DAA+B,4BAA4B,IAAI,GAAG;AAClG,QAAM,wBAAwB,QAAQ,IAAI,4BAA4B,MAAM;AAE5E,MAAI,CAAC,mBAAmB;AACtB,QAAI,SAAS,QAAQ;AAAA,EACvB,OAAO;AACL,UAAM,aAAa,MAAM,SAAS,iBAAiB,cAAc,OAAO,UAAU,IAAI,0BAA0B;AAChH,UAAM,cAAc;AAAA,MAClB;AAAA,MACA,GAAI,aAAa,EAAE,UAAU;AAAA,MAC7B,GAAI,cAAc,EAAE,WAAW;AAAA,IACjC;AAEA,QAAI;AACJ,QAAI,aAAa,4BAAS,MAAM;AAC9B,mBAAa,MAAM,iBAAiB;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AACL,oBAAAC,QAAO,KAAK,+BAA+B,uBAAuB,EAAE;AACpE,oBAAAA,QAAO,KAAK,6BAA6B,qBAAqB,EAAE;AAChE,UAAI,2BAA2B,uBAAuB;AACpD,sBAAAA,QAAO,KAAK,yBAAyB;AACrC,cAAM,4BAA4B;AAAA,UAChC;AAAA,UACA,GAAI,WAAW,EAAE,SAAS,KAAK,MAAM,OAAO,EAAE;AAAA,UAC9C,GAAI,aAAa,EAAE,UAAU;AAAA,UAC7B,GAAI,cAAc,EAAE,WAAW;AAAA,QACjC;AACA,cAAM,wBAAwB;AAAA,UAC5B;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa;AAAA,UACb;AAAA,QACF,CAAC;AACD;AAAA,MACF;AAEA,mBAAa,MAAM,iBAAiB;AAAA,QAClC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AACA,QAAI,SAAS;AACX,mBAAa,oBAAoB,YAAY,KAAK,MAAM,OAAO,CAAC;AAAA,IAClE;AACA,iBAAa,eAAe,YAAY,UAAU;AAClD,UAAM,SAAS,WAAW,QAAQ,eAAe,UAAU,CAAC;AAC5D,UAAM,OAAiB,QAAQ,YAAY,MAAM;AACjD,UAAM,UAAU,aAAa,YAAY,EAAE,MAAM,iBAAiB,GAAG,CAAC;AAEtE,QAAI,UAAU,gBAAgB,kBAAkB;AAChD,QAAI,UAAU,uBAAuB,wBAAwB,UAAU,IAAI,IAAI,KAAI,oBAAI,KAAK,GAAE,YAAY,CAAC,MAAM;AACjH,QAAI,IAAI,OAAO;AAAA,EACjB;AACF;AAEA,IAAO,gBAAQ;AAAA,EACb;AAAA,EACA;AACF;",
6
+ "names": ["ReportQuery", "LocalsHelper", "logger"]
7
7
  }
@@ -8,6 +8,8 @@ import { components } from '../../../types/api'
8
8
  import LocalsHelper from '../../../utils/localsHelper'
9
9
  import { Template } from '../../../types/Templates'
10
10
  import ReportQuery from '../../../types/ReportQuery'
11
+ import { isBooleanFlagExplicitlyEnabled } from '../../../services/featureFlagService'
12
+ import logger from '../../../utils/logger'
11
13
 
12
14
  const convertToCsv = (reportData: Dict<string>[], options: Json2CsvOptions) => {
13
15
  const csvData = json2csv(reportData, options)
@@ -74,6 +76,27 @@ const removeHtmlTags = (
74
76
  return reportData
75
77
  }
76
78
 
79
+ const streamDownloadAsyncData = async (args: {
80
+ services: Services
81
+ token: string
82
+ tableId: string
83
+ reportId: string
84
+ id: string
85
+ queryParams: {
86
+ dataProductDefinitionsPath: string
87
+ columns: string[]
88
+ sortedAsc?: string
89
+ sortColumn?: string
90
+ }
91
+ res: Response
92
+ }) => {
93
+ const { token, services, tableId, reportId, id, queryParams, res } = args
94
+ const query: Record<string, string | string[]> = {
95
+ ...queryParams,
96
+ }
97
+ return services.reportingService.downloadAsyncReport(token, reportId, id, tableId, query, res)
98
+ }
99
+
77
100
  const dowloadAsyncData = async (args: {
78
101
  services: Services
79
102
  token: string
@@ -162,6 +185,9 @@ export const downloadReport = async ({
162
185
  } = req.body
163
186
  const { downloadPermissionService } = services
164
187
  const canDownloadReport = await downloadPermissionService.downloadEnabledForReport(dprUser.id, reportId, id)
188
+ const streamingEnabledInFlipt = isBooleanFlagExplicitlyEnabled('streamingDownloadEnabled', res.app)
189
+ const streamingEnabledInEnv = process.env['STREAMING_DOWNLOAD_ENABLED'] === 'true'
190
+
165
191
  if (!canDownloadReport) {
166
192
  res.redirect(redirect)
167
193
  } else {
@@ -181,6 +207,28 @@ export const downloadReport = async ({
181
207
  queryParams,
182
208
  })
183
209
  } else {
210
+ logger.info(`Streaming enabled in Flipt: ${streamingEnabledInFlipt}`)
211
+ logger.info(`Streaming enabled in Env: ${streamingEnabledInEnv}`)
212
+ if (streamingEnabledInFlipt || streamingEnabledInEnv) {
213
+ logger.info(`Initiating streaming...`)
214
+ const streamDownloadQueryParams = {
215
+ dataProductDefinitionsPath,
216
+ ...(columns && { columns: JSON.parse(columns) }),
217
+ ...(sortedAsc && { sortedAsc }),
218
+ ...(sortColumn && { sortColumn }),
219
+ }
220
+ await streamDownloadAsyncData({
221
+ services,
222
+ token,
223
+ reportId,
224
+ id,
225
+ tableId,
226
+ queryParams: streamDownloadQueryParams,
227
+ res,
228
+ })
229
+ return
230
+ }
231
+
184
232
  reportData = await dowloadAsyncData({
185
233
  services,
186
234
  token,
@@ -19,7 +19,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
19
19
  var featureFlagService_exports = {};
20
20
  __export(featureFlagService_exports, {
21
21
  FeatureFlagService: () => FeatureFlagService,
22
- isBooleanFlagEnabled: () => isBooleanFlagEnabled
22
+ isBooleanFlagEnabledOrMissing: () => isBooleanFlagEnabledOrMissing,
23
+ isBooleanFlagExplicitlyEnabled: () => isBooleanFlagExplicitlyEnabled
23
24
  });
24
25
  module.exports = __toCommonJS(featureFlagService_exports);
25
26
  var import_flipt = require("@flipt-io/flipt");
@@ -51,16 +52,24 @@ class FeatureFlagService {
51
52
  return this.restClient.flags.listFlags(this.namespace);
52
53
  }
53
54
  }
54
- const isBooleanFlagEnabled = (flagName, app) => {
55
+ const resolveFlag = (app, flagName) => {
55
56
  const flag = app.locals.featureFlags?.flags?.[flagName];
56
57
  if (flag && flag.type !== "BOOLEAN_FLAG_TYPE") {
57
58
  throw Error("Tried to validate whether a non-boolean flag was enabled");
58
59
  }
60
+ return flag;
61
+ };
62
+ const isBooleanFlagEnabledOrMissing = (flagName, app) => {
63
+ const flag = resolveFlag(app, flagName);
59
64
  return !flag || flag.enabled;
60
65
  };
66
+ const isBooleanFlagExplicitlyEnabled = (flagName, app) => {
67
+ return resolveFlag(app, flagName)?.enabled === true;
68
+ };
61
69
  // Annotate the CommonJS export names for ESM import in node:
62
70
  0 && (module.exports = {
63
71
  FeatureFlagService,
64
- isBooleanFlagEnabled
72
+ isBooleanFlagEnabledOrMissing,
73
+ isBooleanFlagExplicitlyEnabled
65
74
  });
66
75
  //# sourceMappingURL=featureFlagService.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../dpr/services/featureFlagService.ts"],
4
- "sourcesContent": ["import { FliptClient, ListFlagsResponse } from '@flipt-io/flipt'\nimport { Application } from 'express'\nimport { FeatureFlagConfig } from '../data/types'\n\nexport class FeatureFlagService {\n restClient: FliptClient | undefined\n\n namespace: string | undefined\n\n constructor(config: FeatureFlagConfig | Record<string, unknown> = {}) {\n const { namespace, token, url } = config && (config as FeatureFlagConfig)\n if (Object.keys(config).length !== 3 || !namespace || !token || !url) {\n return\n }\n this.restClient = new FliptClient({\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n url,\n })\n this.namespace = namespace\n }\n\n async getFlags(): Promise<ListFlagsResponse> {\n if (!this.restClient || !this.namespace) {\n return {\n flags: [],\n nextPageToken: '',\n totalCount: 0,\n }\n }\n return this.restClient.flags.listFlags(this.namespace)\n }\n}\n\nexport const isBooleanFlagEnabled = (flagName: string, app: Application): boolean => {\n const flag = app.locals.featureFlags?.flags?.[flagName]\n if (flag && flag.type !== 'BOOLEAN_FLAG_TYPE') {\n throw Error('Tried to validate whether a non-boolean flag was enabled')\n }\n return !flag || flag.enabled\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+C;AAIxC,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EAEA;AAAA,EAEA,YAAY,SAAsD,CAAC,GAAG;AACpE,UAAM,EAAE,WAAW,OAAO,IAAI,IAAI,UAAW;AAC7C,QAAI,OAAO,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK;AACpE;AAAA,IACF;AACA,SAAK,aAAa,IAAI,yBAAY;AAAA,MAChC,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,gBAAgB;AAAA,MAClB;AAAA,MACA;AAAA,IACF,CAAC;AACD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,WAAuC;AAC3C,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,WAAW;AACvC,aAAO;AAAA,QACL,OAAO,CAAC;AAAA,QACR,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF;AACA,WAAO,KAAK,WAAW,MAAM,UAAU,KAAK,SAAS;AAAA,EACvD;AACF;AAEO,MAAM,uBAAuB,CAAC,UAAkB,QAA8B;AACnF,QAAM,OAAO,IAAI,OAAO,cAAc,QAAQ,QAAQ;AACtD,MAAI,QAAQ,KAAK,SAAS,qBAAqB;AAC7C,UAAM,MAAM,0DAA0D;AAAA,EACxE;AACA,SAAO,CAAC,QAAQ,KAAK;AACvB;",
4
+ "sourcesContent": ["import { FliptClient, ListFlagsResponse } from '@flipt-io/flipt'\nimport { Application } from 'express'\nimport { FeatureFlagConfig } from '../data/types'\n\nexport class FeatureFlagService {\n restClient: FliptClient | undefined\n\n namespace: string | undefined\n\n constructor(config: FeatureFlagConfig | Record<string, unknown> = {}) {\n const { namespace, token, url } = config && (config as FeatureFlagConfig)\n if (Object.keys(config).length !== 3 || !namespace || !token || !url) {\n return\n }\n this.restClient = new FliptClient({\n headers: {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n url,\n })\n this.namespace = namespace\n }\n\n async getFlags(): Promise<ListFlagsResponse> {\n if (!this.restClient || !this.namespace) {\n return {\n flags: [],\n nextPageToken: '',\n totalCount: 0,\n }\n }\n return this.restClient.flags.listFlags(this.namespace)\n }\n}\n\nconst resolveFlag = (app: Application, flagName: string) => {\n const flag = app.locals.featureFlags?.flags?.[flagName]\n if (flag && flag.type !== 'BOOLEAN_FLAG_TYPE') {\n throw Error('Tried to validate whether a non-boolean flag was enabled')\n }\n return flag\n}\n\nexport const isBooleanFlagEnabledOrMissing = (flagName: string, app: Application): boolean => {\n const flag = resolveFlag(app, flagName)\n return !flag || flag.enabled\n}\n\nexport const isBooleanFlagExplicitlyEnabled = (flagName: string, app: Application): boolean => {\n return resolveFlag(app, flagName)?.enabled === true\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+C;AAIxC,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EAEA;AAAA,EAEA,YAAY,SAAsD,CAAC,GAAG;AACpE,UAAM,EAAE,WAAW,OAAO,IAAI,IAAI,UAAW;AAC7C,QAAI,OAAO,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK;AACpE;AAAA,IACF;AACA,SAAK,aAAa,IAAI,yBAAY;AAAA,MAChC,SAAS;AAAA,QACP,eAAe,UAAU,KAAK;AAAA,QAC9B,gBAAgB;AAAA,MAClB;AAAA,MACA;AAAA,IACF,CAAC;AACD,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,WAAuC;AAC3C,QAAI,CAAC,KAAK,cAAc,CAAC,KAAK,WAAW;AACvC,aAAO;AAAA,QACL,OAAO,CAAC;AAAA,QACR,eAAe;AAAA,QACf,YAAY;AAAA,MACd;AAAA,IACF;AACA,WAAO,KAAK,WAAW,MAAM,UAAU,KAAK,SAAS;AAAA,EACvD;AACF;AAEA,MAAM,cAAc,CAAC,KAAkB,aAAqB;AAC1D,QAAM,OAAO,IAAI,OAAO,cAAc,QAAQ,QAAQ;AACtD,MAAI,QAAQ,KAAK,SAAS,qBAAqB;AAC7C,UAAM,MAAM,0DAA0D;AAAA,EACxE;AACA,SAAO;AACT;AAEO,MAAM,gCAAgC,CAAC,UAAkB,QAA8B;AAC5F,QAAM,OAAO,YAAY,KAAK,QAAQ;AACtC,SAAO,CAAC,QAAQ,KAAK;AACvB;AAEO,MAAM,iCAAiC,CAAC,UAAkB,QAA8B;AAC7F,SAAO,YAAY,KAAK,QAAQ,GAAG,YAAY;AACjD;",
6
6
  "names": []
7
7
  }
@@ -34,10 +34,19 @@ export class FeatureFlagService {
34
34
  }
35
35
  }
36
36
 
37
- export const isBooleanFlagEnabled = (flagName: string, app: Application): boolean => {
37
+ const resolveFlag = (app: Application, flagName: string) => {
38
38
  const flag = app.locals.featureFlags?.flags?.[flagName]
39
39
  if (flag && flag.type !== 'BOOLEAN_FLAG_TYPE') {
40
40
  throw Error('Tried to validate whether a non-boolean flag was enabled')
41
41
  }
42
+ return flag
43
+ }
44
+
45
+ export const isBooleanFlagEnabledOrMissing = (flagName: string, app: Application): boolean => {
46
+ const flag = resolveFlag(app, flagName)
42
47
  return !flag || flag.enabled
43
48
  }
49
+
50
+ export const isBooleanFlagExplicitlyEnabled = (flagName: string, app: Application): boolean => {
51
+ return resolveFlag(app, flagName)?.enabled === true
52
+ }
@@ -51,6 +51,9 @@ class ReportingService {
51
51
  async cancelAsyncRequest(token, reportId, variantId, executionId, dataProductDefinitionsPath) {
52
52
  return this.reportingClient.cancelAsyncRequest(token, reportId, variantId, executionId, dataProductDefinitionsPath);
53
53
  }
54
+ async downloadAsyncReport(token, reportId, variantId, tableId, query, res) {
55
+ return this.reportingClient.downloadAsyncReport(token, reportId, variantId, tableId, query, res);
56
+ }
54
57
  async getAsyncReport(token, reportId, variantId, tableId, query) {
55
58
  return this.reportingClient.getAsyncReport(token, reportId, variantId, tableId, query);
56
59
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../dpr/services/reportingService.ts"],
4
- "sourcesContent": ["import { components } from '../types/api'\nimport type ReportingClient from '../data/reportingClient'\nimport ReportQuery from '../types/ReportQuery'\nimport Dict = NodeJS.Dict\nimport { ListWithWarnings } from '../data/types'\n\nclass ReportingService {\n constructor(private readonly reportingClient: ReportingClient) {\n this.reportingClient = reportingClient\n }\n\n async getCount(resourceName: string, token: string, listRequest: ReportQuery): Promise<number> {\n return this.reportingClient.getCount(resourceName, token, listRequest)\n }\n\n async getList(resourceName: string, token: string, listRequest: ReportQuery): Promise<Array<NodeJS.Dict<string>>> {\n return this.reportingClient.getList(resourceName, token, listRequest)\n }\n\n async getListWithWarnings(resourceName: string, token: string, listRequest: ReportQuery): Promise<ListWithWarnings> {\n return this.reportingClient.getListWithWarnings(resourceName, token, listRequest)\n }\n\n async getDefinitionSummary(\n token: string,\n reportId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<components['schemas']['ReportDefinitionSummary']> {\n return this.reportingClient.getDefinitionSummary(token, reportId, dataProductDefinitionsPath)\n }\n\n async getDefinitions(\n token: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Array<components['schemas']['ReportDefinitionSummary']>> {\n return this.reportingClient.getDefinitions(token, dataProductDefinitionsPath)\n }\n\n async getDefinition(\n token: string,\n reportId: string,\n variantId: string,\n dataProductDefinitionsPath?: string,\n query?: Dict<string | string[]>,\n ): Promise<components['schemas']['SingleVariantReportDefinition']> {\n return this.reportingClient.getDefinition(token, reportId, variantId, dataProductDefinitionsPath, query)\n }\n\n async requestAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n query: Record<string, string | boolean | number>,\n ): Promise<Dict<string>> {\n return this.reportingClient.requestAsyncReport(token, reportId, variantId, query)\n }\n\n async cancelAsyncRequest(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Dict<string>> {\n return this.reportingClient.cancelAsyncRequest(token, reportId, variantId, executionId, dataProductDefinitionsPath)\n }\n\n async getAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n ): Promise<Array<Dict<string>>> {\n return this.reportingClient.getAsyncReport(token, reportId, variantId, tableId, query)\n }\n\n async getAsyncSummaryReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n summaryId: string,\n query: Dict<string | number>,\n ): Promise<Array<Dict<string>>> {\n return this.reportingClient.getAsyncSummaryReport(token, reportId, variantId, tableId, summaryId, query)\n }\n\n async getAsyncReportStatus(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath: string,\n tableId: string,\n ): Promise<components['schemas']['StatementExecutionStatus']> {\n return this.reportingClient.getAsyncReportStatus(\n token,\n reportId,\n variantId,\n executionId,\n dataProductDefinitionsPath,\n tableId,\n )\n }\n\n async getAsyncCount(token: string, tableId: string, dataProductDefinitionsPath?: string): Promise<number> {\n return this.reportingClient.getAsyncCount(token, tableId, dataProductDefinitionsPath)\n }\n\n async getAsyncInteractiveCount(\n token: string,\n tableId: string,\n reportId: string,\n id: string,\n filters: ReportQuery,\n ): Promise<number> {\n return this.reportingClient.getAsyncInteractiveCount(token, tableId, reportId, id, filters)\n }\n}\n\nexport { ReportingService }\nexport default ReportingService\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAMA,MAAM,iBAAiB;AAAA,EACrB,YAA6B,iBAAkC;AAAlC;AAC3B,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,cAAsB,OAAe,aAA2C;AAC7F,WAAO,KAAK,gBAAgB,SAAS,cAAc,OAAO,WAAW;AAAA,EACvE;AAAA,EAEA,MAAM,QAAQ,cAAsB,OAAe,aAA+D;AAChH,WAAO,KAAK,gBAAgB,QAAQ,cAAc,OAAO,WAAW;AAAA,EACtE;AAAA,EAEA,MAAM,oBAAoB,cAAsB,OAAe,aAAqD;AAClH,WAAO,KAAK,gBAAgB,oBAAoB,cAAc,OAAO,WAAW;AAAA,EAClF;AAAA,EAEA,MAAM,qBACJ,OACA,UACA,4BAC2D;AAC3D,WAAO,KAAK,gBAAgB,qBAAqB,OAAO,UAAU,0BAA0B;AAAA,EAC9F;AAAA,EAEA,MAAM,eACJ,OACA,4BACkE;AAClE,WAAO,KAAK,gBAAgB,eAAe,OAAO,0BAA0B;AAAA,EAC9E;AAAA,EAEA,MAAM,cACJ,OACA,UACA,WACA,4BACA,OACiE;AACjE,WAAO,KAAK,gBAAgB,cAAc,OAAO,UAAU,WAAW,4BAA4B,KAAK;AAAA,EACzG;AAAA,EAEA,MAAM,mBACJ,OACA,UACA,WACA,OACuB;AACvB,WAAO,KAAK,gBAAgB,mBAAmB,OAAO,UAAU,WAAW,KAAK;AAAA,EAClF;AAAA,EAEA,MAAM,mBACJ,OACA,UACA,WACA,aACA,4BACuB;AACvB,WAAO,KAAK,gBAAgB,mBAAmB,OAAO,UAAU,WAAW,aAAa,0BAA0B;AAAA,EACpH;AAAA,EAEA,MAAM,eACJ,OACA,UACA,WACA,SACA,OAC8B;AAC9B,WAAO,KAAK,gBAAgB,eAAe,OAAO,UAAU,WAAW,SAAS,KAAK;AAAA,EACvF;AAAA,EAEA,MAAM,sBACJ,OACA,UACA,WACA,SACA,WACA,OAC8B;AAC9B,WAAO,KAAK,gBAAgB,sBAAsB,OAAO,UAAU,WAAW,SAAS,WAAW,KAAK;AAAA,EACzG;AAAA,EAEA,MAAM,qBACJ,OACA,UACA,WACA,aACA,4BACA,SAC4D;AAC5D,WAAO,KAAK,gBAAgB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAAe,SAAiB,4BAAsD;AACxG,WAAO,KAAK,gBAAgB,cAAc,OAAO,SAAS,0BAA0B;AAAA,EACtF;AAAA,EAEA,MAAM,yBACJ,OACA,SACA,UACA,IACA,SACiB;AACjB,WAAO,KAAK,gBAAgB,yBAAyB,OAAO,SAAS,UAAU,IAAI,OAAO;AAAA,EAC5F;AACF;AAGA,IAAO,2BAAQ;",
4
+ "sourcesContent": ["import { Response } from 'express'\nimport { components } from '../types/api'\nimport type ReportingClient from '../data/reportingClient'\nimport ReportQuery from '../types/ReportQuery'\nimport Dict = NodeJS.Dict\nimport { ListWithWarnings } from '../data/types'\n\nclass ReportingService {\n constructor(private readonly reportingClient: ReportingClient) {\n this.reportingClient = reportingClient\n }\n\n async getCount(resourceName: string, token: string, listRequest: ReportQuery): Promise<number> {\n return this.reportingClient.getCount(resourceName, token, listRequest)\n }\n\n async getList(resourceName: string, token: string, listRequest: ReportQuery): Promise<Array<NodeJS.Dict<string>>> {\n return this.reportingClient.getList(resourceName, token, listRequest)\n }\n\n async getListWithWarnings(resourceName: string, token: string, listRequest: ReportQuery): Promise<ListWithWarnings> {\n return this.reportingClient.getListWithWarnings(resourceName, token, listRequest)\n }\n\n async getDefinitionSummary(\n token: string,\n reportId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<components['schemas']['ReportDefinitionSummary']> {\n return this.reportingClient.getDefinitionSummary(token, reportId, dataProductDefinitionsPath)\n }\n\n async getDefinitions(\n token: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Array<components['schemas']['ReportDefinitionSummary']>> {\n return this.reportingClient.getDefinitions(token, dataProductDefinitionsPath)\n }\n\n async getDefinition(\n token: string,\n reportId: string,\n variantId: string,\n dataProductDefinitionsPath?: string,\n query?: Dict<string | string[]>,\n ): Promise<components['schemas']['SingleVariantReportDefinition']> {\n return this.reportingClient.getDefinition(token, reportId, variantId, dataProductDefinitionsPath, query)\n }\n\n async requestAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n query: Record<string, string | boolean | number>,\n ): Promise<Dict<string>> {\n return this.reportingClient.requestAsyncReport(token, reportId, variantId, query)\n }\n\n async cancelAsyncRequest(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath?: string,\n ): Promise<Dict<string>> {\n return this.reportingClient.cancelAsyncRequest(token, reportId, variantId, executionId, dataProductDefinitionsPath)\n }\n\n async downloadAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n res: Response,\n ): Promise<void> {\n return this.reportingClient.downloadAsyncReport(token, reportId, variantId, tableId, query, res)\n }\n\n async getAsyncReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n query: Record<string, string | string[]>,\n ): Promise<Array<Dict<string>>> {\n return this.reportingClient.getAsyncReport(token, reportId, variantId, tableId, query)\n }\n\n async getAsyncSummaryReport(\n token: string,\n reportId: string,\n variantId: string,\n tableId: string,\n summaryId: string,\n query: Dict<string | number>,\n ): Promise<Array<Dict<string>>> {\n return this.reportingClient.getAsyncSummaryReport(token, reportId, variantId, tableId, summaryId, query)\n }\n\n async getAsyncReportStatus(\n token: string,\n reportId: string,\n variantId: string,\n executionId: string,\n dataProductDefinitionsPath: string,\n tableId: string,\n ): Promise<components['schemas']['StatementExecutionStatus']> {\n return this.reportingClient.getAsyncReportStatus(\n token,\n reportId,\n variantId,\n executionId,\n dataProductDefinitionsPath,\n tableId,\n )\n }\n\n async getAsyncCount(token: string, tableId: string, dataProductDefinitionsPath?: string): Promise<number> {\n return this.reportingClient.getAsyncCount(token, tableId, dataProductDefinitionsPath)\n }\n\n async getAsyncInteractiveCount(\n token: string,\n tableId: string,\n reportId: string,\n id: string,\n filters: ReportQuery,\n ): Promise<number> {\n return this.reportingClient.getAsyncInteractiveCount(token, tableId, reportId, id, filters)\n }\n}\n\nexport { ReportingService }\nexport default ReportingService\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,MAAM,iBAAiB;AAAA,EACrB,YAA6B,iBAAkC;AAAlC;AAC3B,SAAK,kBAAkB;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,cAAsB,OAAe,aAA2C;AAC7F,WAAO,KAAK,gBAAgB,SAAS,cAAc,OAAO,WAAW;AAAA,EACvE;AAAA,EAEA,MAAM,QAAQ,cAAsB,OAAe,aAA+D;AAChH,WAAO,KAAK,gBAAgB,QAAQ,cAAc,OAAO,WAAW;AAAA,EACtE;AAAA,EAEA,MAAM,oBAAoB,cAAsB,OAAe,aAAqD;AAClH,WAAO,KAAK,gBAAgB,oBAAoB,cAAc,OAAO,WAAW;AAAA,EAClF;AAAA,EAEA,MAAM,qBACJ,OACA,UACA,4BAC2D;AAC3D,WAAO,KAAK,gBAAgB,qBAAqB,OAAO,UAAU,0BAA0B;AAAA,EAC9F;AAAA,EAEA,MAAM,eACJ,OACA,4BACkE;AAClE,WAAO,KAAK,gBAAgB,eAAe,OAAO,0BAA0B;AAAA,EAC9E;AAAA,EAEA,MAAM,cACJ,OACA,UACA,WACA,4BACA,OACiE;AACjE,WAAO,KAAK,gBAAgB,cAAc,OAAO,UAAU,WAAW,4BAA4B,KAAK;AAAA,EACzG;AAAA,EAEA,MAAM,mBACJ,OACA,UACA,WACA,OACuB;AACvB,WAAO,KAAK,gBAAgB,mBAAmB,OAAO,UAAU,WAAW,KAAK;AAAA,EAClF;AAAA,EAEA,MAAM,mBACJ,OACA,UACA,WACA,aACA,4BACuB;AACvB,WAAO,KAAK,gBAAgB,mBAAmB,OAAO,UAAU,WAAW,aAAa,0BAA0B;AAAA,EACpH;AAAA,EAEA,MAAM,oBACJ,OACA,UACA,WACA,SACA,OACA,KACe;AACf,WAAO,KAAK,gBAAgB,oBAAoB,OAAO,UAAU,WAAW,SAAS,OAAO,GAAG;AAAA,EACjG;AAAA,EAEA,MAAM,eACJ,OACA,UACA,WACA,SACA,OAC8B;AAC9B,WAAO,KAAK,gBAAgB,eAAe,OAAO,UAAU,WAAW,SAAS,KAAK;AAAA,EACvF;AAAA,EAEA,MAAM,sBACJ,OACA,UACA,WACA,SACA,WACA,OAC8B;AAC9B,WAAO,KAAK,gBAAgB,sBAAsB,OAAO,UAAU,WAAW,SAAS,WAAW,KAAK;AAAA,EACzG;AAAA,EAEA,MAAM,qBACJ,OACA,UACA,WACA,aACA,4BACA,SAC4D;AAC5D,WAAO,KAAK,gBAAgB;AAAA,MAC1B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAAe,SAAiB,4BAAsD;AACxG,WAAO,KAAK,gBAAgB,cAAc,OAAO,SAAS,0BAA0B;AAAA,EACtF;AAAA,EAEA,MAAM,yBACJ,OACA,SACA,UACA,IACA,SACiB;AACjB,WAAO,KAAK,gBAAgB,yBAAyB,OAAO,SAAS,UAAU,IAAI,OAAO;AAAA,EAC5F;AACF;AAGA,IAAO,2BAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ import { Response } from 'express'
1
2
  import { components } from '../types/api'
2
3
  import type ReportingClient from '../data/reportingClient'
3
4
  import ReportQuery from '../types/ReportQuery'
@@ -65,6 +66,17 @@ class ReportingService {
65
66
  return this.reportingClient.cancelAsyncRequest(token, reportId, variantId, executionId, dataProductDefinitionsPath)
66
67
  }
67
68
 
69
+ async downloadAsyncReport(
70
+ token: string,
71
+ reportId: string,
72
+ variantId: string,
73
+ tableId: string,
74
+ query: Record<string, string | string[]>,
75
+ res: Response,
76
+ ): Promise<void> {
77
+ return this.reportingClient.downloadAsyncReport(token, reportId, variantId, tableId, query, res)
78
+ }
79
+
68
80
  async getAsyncReport(
69
81
  token: string,
70
82
  reportId: string,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ministryofjustice/hmpps-digital-prison-reporting-frontend",
3
3
  "description": "The Digital Prison Reporting Frontend contains templates and code to help display data effectively in UI applications.",
4
- "version": "4.28.2",
4
+ "version": "4.28.4",
5
5
  "main": "dpr/all",
6
6
  "sass": "dpr/all.scss",
7
7
  "engines": {
@@ -186,7 +186,7 @@
186
186
  "@types/express": "4.17.21",
187
187
  "@types/express-session": "^1.18.2",
188
188
  "@types/jest": "^30.0.0",
189
- "@types/node": "24.10.4",
189
+ "@types/node": "24.10.9",
190
190
  "@types/nunjucks": "^3.2.3",
191
191
  "@types/nunjucks-date": "^0.0.10",
192
192
  "@types/parseurl": "^1.3.3",