diff --git a/dashboards-reports/public/components/context_menu/context_menu.js b/dashboards-reports/public/components/context_menu/context_menu.js index 109a3d17..6cf87e5d 100644 --- a/dashboards-reports/public/components/context_menu/context_menu.js +++ b/dashboards-reports/public/components/context_menu/context_menu.js @@ -45,6 +45,7 @@ import { import { timeRangeMatcher } from '../utils/utils'; import { parse } from 'url'; import { unhashUrl } from '../../../../../src/plugins/opensearch_dashboards_utils/public'; +import { uiSettingsService } from '../utils/settings_service'; const generateInContextReport = async ( timeRanges, @@ -106,7 +107,7 @@ const generateInContextReport = async ( fetch( `../api/reporting/generateReport?timezone=${ Intl.DateTimeFormat().resolvedOptions().timeZone - }`, + }&dateFormat=${uiSettingsService.get('dateFormat')}`, { headers: { 'Content-Type': 'application/json', diff --git a/dashboards-reports/public/components/main/main_utils.tsx b/dashboards-reports/public/components/main/main_utils.tsx index 726e5aca..8ab80da3 100644 --- a/dashboards-reports/public/components/main/main_utils.tsx +++ b/dashboards-reports/public/components/main/main_utils.tsx @@ -27,6 +27,7 @@ import 'babel-polyfill'; import { i18n } from '@osd/i18n'; import { HttpFetchOptions, HttpSetup } from '../../../../../src/core/public'; +import { uiSettingsService } from '../utils/settings_service'; export const displayDeliveryChannels = (configIds: Array, channels: Array<{label: string, id: string}>) => { let displayChannels = []; @@ -193,7 +194,10 @@ export const generateReportFromDefinitionId = async ( headers: { 'Content-Type': 'application/json', }, - query: { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }, + query: { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + dateFormat: uiSettingsService.get('dateFormat'), + }, }) .then(async (response: any) => { // for emailing a report, this API response doesn't have response body @@ -226,7 +230,10 @@ export const generateReportById = async ( ) => { await httpClient .get(`../api/reporting/generateReport/${reportId}`, { - query: { timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }, + query: { + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + dateFormat: uiSettingsService.get('dateFormat'), + }, }) .then(async (response) => { //TODO: duplicate code, extract to be a function that can reuse. e.g. handleResponse(response) diff --git a/dashboards-reports/public/components/utils/settings_service.ts b/dashboards-reports/public/components/utils/settings_service.ts new file mode 100644 index 00000000..f1baf906 --- /dev/null +++ b/dashboards-reports/public/components/utils/settings_service.ts @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +import { IUiSettingsClient } from '../../../../../src/core/public'; + +let uiSettings: IUiSettingsClient; + +export const uiSettingsService = { + init: (client: IUiSettingsClient) => { + uiSettings = client; + }, + get: (key: string, defaultOverride?: any) => { + return uiSettings?.get(key, defaultOverride) || ''; + }, +}; diff --git a/dashboards-reports/public/plugin.ts b/dashboards-reports/public/plugin.ts index 98d8bd71..c4bbb23d 100644 --- a/dashboards-reports/public/plugin.ts +++ b/dashboards-reports/public/plugin.ts @@ -38,11 +38,13 @@ import { import { i18n } from '@osd/i18n'; import './components/context_menu/context_menu'; import { PLUGIN_ID, PLUGIN_NAME } from '../common'; +import { uiSettingsService } from './components/utils/settings_service'; export class ReportsDashboardsPlugin implements Plugin { public setup(core: CoreSetup): ReportsDashboardsPluginSetup { + uiSettingsService.init(core.uiSettings); // Register an application into the side navigation menu core.application.register({ id: PLUGIN_ID, diff --git a/dashboards-reports/server/routes/lib/createReport.ts b/dashboards-reports/server/routes/lib/createReport.ts index 2c83e6b6..36709343 100644 --- a/dashboards-reports/server/routes/lib/createReport.ts +++ b/dashboards-reports/server/routes/lib/createReport.ts @@ -28,7 +28,8 @@ import { REPORT_TYPE, REPORT_STATE, DELIVERY_TYPE, - SECURITY_CONSTANTS, + DATA_REPORT_CONFIG, + EXTRA_HEADERS, } from '../utils/constants'; import { @@ -46,6 +47,7 @@ import { updateReportState } from './updateReportState'; import { saveReport } from './saveReport'; import { SemaphoreInterface } from 'async-mutex'; import { AccessInfoType } from 'server'; +import _ from 'lodash'; export const createReport = async ( request: OpenSearchDashboardsRequest, @@ -66,6 +68,8 @@ export const createReport = async ( const opensearchClient = context.core.opensearch.legacy.client; // @ts-ignore const timezone = request.query.timezone; + // @ts-ignore + const dateFormat = request.query.dateFormat || DATA_REPORT_CONFIG.excelDateFormat; const { basePath, serverInfo: { protocol, port, hostname }, @@ -75,9 +79,7 @@ export const createReport = async ( let reportId; const { - report_definition: { - report_params: reportParams, - }, + report_definition: { report_params: reportParams }, } = report; const { report_source: reportSource } = reportParams; @@ -94,6 +96,7 @@ export const createReport = async ( createReportResult = await createSavedSearchReport( report, opensearchClient, + dateFormat, isScheduledTask ); } else { @@ -103,40 +106,15 @@ export const createReport = async ( ? report.query_url : `${basePath}${report.query_url}`; const completeQueryUrl = `${protocol}://${hostname}:${port}${relativeUrl}`; - // Check if security is enabled. TODO: is there a better way to check? - let cookieObject: SetCookie | undefined; - if (request.headers.cookie) { - const cookies = request.headers.cookie.split(';'); - cookies.map((item: string) => { - const cookie = item.trim().split('='); - if (cookie[0] === SECURITY_CONSTANTS.AUTH_COOKIE_NAME) { - cookieObject = { - name: cookie[0], - value: cookie[1], - url: completeQueryUrl, - path: basePath, - }; - } - }); - } - // If header exists assuming that it needs forwarding - let additionalHeaders: Headers | undefined; - if (request.headers[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER]) { - additionalHeaders = {} - additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER]; - additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_IP_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_IP_HEADER]; - if (request.headers[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER]) { - additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER] - } - } + const extraHeaders = _.pick(request.headers, EXTRA_HEADERS); + const [value, release] = await semaphore.acquire(); try { createReportResult = await createVisualReport( reportParams, completeQueryUrl, logger, - cookieObject, - additionalHeaders, + extraHeaders, timezone ); } finally { diff --git a/dashboards-reports/server/routes/report.ts b/dashboards-reports/server/routes/report.ts index f4d406ca..be13d7ee 100644 --- a/dashboards-reports/server/routes/report.ts +++ b/dashboards-reports/server/routes/report.ts @@ -57,6 +57,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { body: schema.any(), query: schema.object({ timezone: schema.maybe(schema.string()), + dateFormat: schema.maybe(schema.string()), }), }, }, @@ -121,6 +122,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { }), query: schema.object({ timezone: schema.string(), + dateFormat: schema.maybe(schema.string()), }), }, }, @@ -186,6 +188,7 @@ export default function (router: IRouter, accessInfo: AccessInfoType) { }), query: schema.object({ timezone: schema.string(), + dateFormat: schema.maybe(schema.string()), }), }, }, diff --git a/dashboards-reports/server/routes/utils/constants.ts b/dashboards-reports/server/routes/utils/constants.ts index 8bb89058..465256d5 100644 --- a/dashboards-reports/server/routes/utils/constants.ts +++ b/dashboards-reports/server/routes/utils/constants.ts @@ -64,7 +64,7 @@ export enum REPORT_TYPE { } export enum DATA_REPORT_CONFIG { - excelDateFormat = 'MM/DD/YYYY h:mm:ss a', + excelDateFormat = 'MM/DD/YYYY h:mm:ss.SSS a', } export enum TRIGGER_TYPE { @@ -89,13 +89,16 @@ export const DEFAULT_MAX_SIZE = 10000; export const DEFAULT_REPORT_HEADER = '

OpenSearch Dashboards Reports

'; export const SECURITY_CONSTANTS = { - AUTH_COOKIE_NAME: 'security_authentication', TENANT_LOCAL_STORAGE_KEY: 'opendistro::security::tenant::show_popup', - PROXY_AUTH_USER_HEADER: 'x-proxy-user', - PROXY_AUTH_ROLES_HEADER: 'x-proxy-roles', - PROXY_AUTH_IP_HEADER: 'x-forwarded-for', }; +export const EXTRA_HEADERS = [ + 'cookie', + 'x-proxy-user', + 'x-proxy-roles', + 'x-forwarded-for', +]; + export const CHROMIUM_PATH = `${__dirname}/../../../.chromium/headless_shell`; /** diff --git a/dashboards-reports/server/routes/utils/dataReportHelpers.ts b/dashboards-reports/server/routes/utils/dataReportHelpers.ts index 23605ae6..7a3b6d19 100644 --- a/dashboards-reports/server/routes/utils/dataReportHelpers.ts +++ b/dashboards-reports/server/routes/utils/dataReportHelpers.ts @@ -178,7 +178,7 @@ export const buildQuery = (report, is_count) => { }; // Fetch the data from OpenSearch -export const getOpenSearchData = (arrayHits, report, params) => { +export const getOpenSearchData = (arrayHits, report, params, dateFormat: string) => { let hits: any = []; for (let valueRes of arrayHits) { for (let data of valueRes.hits) { @@ -186,9 +186,7 @@ export const getOpenSearchData = (arrayHits, report, params) => { // get all the fields of type date and format them to excel format for (let dateType of report._source.dateFields) { if (data._source[dateType]) { - data._source[dateType] = moment(fields[dateType][0]).format( - DATA_REPORT_CONFIG.excelDateFormat - ); + data._source[dateType] = moment(fields[dateType][0]).format(dateFormat); } } delete data['fields']; diff --git a/dashboards-reports/server/routes/utils/savedSearchReportHelper.ts b/dashboards-reports/server/routes/utils/savedSearchReportHelper.ts index ac2802c0..76a5a9cf 100644 --- a/dashboards-reports/server/routes/utils/savedSearchReportHelper.ts +++ b/dashboards-reports/server/routes/utils/savedSearchReportHelper.ts @@ -48,6 +48,7 @@ const scrollTimeout = '1m'; export async function createSavedSearchReport( report: any, client: ILegacyClusterClient | ILegacyScopedClusterClient, + dateFormat: string, isScheduledTask: boolean = true ): Promise { const params = report.report_definition.report_params; @@ -58,6 +59,7 @@ export async function createSavedSearchReport( const data = await generateReportData( client, params.core_params, + dateFormat, isScheduledTask ); @@ -141,6 +143,7 @@ async function populateMetaData( async function generateReportData( client: ILegacyClusterClient | ILegacyScopedClusterClient, params: any, + dateFormat: string, isScheduledTask: boolean ) { let opensearchData: any = {}; @@ -267,7 +270,7 @@ async function generateReportData( for (const dateType of report._source.dateFields) { docvalues.push({ field: dateType, - format: 'date_hour_minute', + format: 'date_hour_minute_second_fraction', }); } @@ -282,7 +285,7 @@ async function generateReportData( // Parse OpenSearch data and convert to CSV async function convertOpenSearchDataToCsv() { const dataset: any = []; - dataset.push(getOpenSearchData(arrayHits, report, params)); + dataset.push(getOpenSearchData(arrayHits, report, params, dateFormat)); return await convertToCSV(dataset); } } diff --git a/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts b/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts index 94e6a166..0e079fdb 100644 --- a/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts +++ b/dashboards-reports/server/routes/utils/visual_report/visualReportHelper.ts @@ -24,7 +24,7 @@ * permissions and limitations under the License. */ -import puppeteer, { SetCookie, Headers } from 'puppeteer-core'; +import puppeteer, { Headers } from 'puppeteer-core'; import createDOMPurify from 'dompurify'; import { JSDOM } from 'jsdom'; import { Logger } from '../../../../../../src/core/server'; @@ -40,13 +40,13 @@ import { getFileName } from '../helpers'; import { CreateReportResultType } from '../types'; import { ReportParamsSchemaType, VisualReportSchemaType } from 'server/model'; import fs from 'fs'; +import _ from 'lodash'; export const createVisualReport = async ( reportParams: ReportParamsSchemaType, queryUrl: string, logger: Logger, - cookie?: SetCookie, - additionalheaders?: Headers, + extraHeaders: Headers, timezone?: string ): Promise => { const { @@ -95,13 +95,9 @@ export const createVisualReport = async ( const page = await browser.newPage(); page.setDefaultNavigationTimeout(0); page.setDefaultTimeout(100000); // use 100s timeout instead of default 30s - if (cookie) { - logger.info('domain enables security, use session cookie to access'); - await page.setCookie(cookie); - } - if (additionalheaders) { - logger.info('domain passed proxy auth headers, passing to backend'); - await page.setExtraHTTPHeaders(additionalheaders); + // Set extra headers that are needed + if (!_.isEmpty(extraHeaders)) { + await page.setExtraHTTPHeaders(extraHeaders); } logger.info(`original queryUrl ${queryUrl}`); await page.goto(queryUrl, { waitUntil: 'networkidle0' }); diff --git a/reports-scheduler/src/main/kotlin/org/opensearch/reportsscheduler/action/ReportInstanceActions.kt b/reports-scheduler/src/main/kotlin/org/opensearch/reportsscheduler/action/ReportInstanceActions.kt index 3030c2df..25220866 100644 --- a/reports-scheduler/src/main/kotlin/org/opensearch/reportsscheduler/action/ReportInstanceActions.kt +++ b/reports-scheduler/src/main/kotlin/org/opensearch/reportsscheduler/action/ReportInstanceActions.kt @@ -124,7 +124,7 @@ internal object ReportInstanceActions { Metrics.REPORT_FROM_DEFINITION_ID_SYSTEM_ERROR.counter.increment() throw OpenSearchStatusException("Report Instance Creation failed", RestStatus.INTERNAL_SERVER_ERROR) } - if (reportDefinitionDetails.reportDefinition.delivery != null) { + if (reportDefinitionDetails.reportDefinition.delivery != null && reportDefinitionDetails.reportDefinition.delivery.configIds.isNotEmpty()) { val reportName = reportInstance.reportDefinitionDetails!!.reportDefinition.name val reportLink = buildReportLink(reportDefinitionDetails.reportDefinition.source.origin, reportInstance.tenant, docId) NotificationsActions.send(reportDefinitionDetails.reportDefinition.delivery, docId, reportLink, reportName)