diff --git a/demos/rna-news/demo.html b/demos/rna-news/demo.html
new file mode 100644
index 0000000..46f4cd7
--- /dev/null
+++ b/demos/rna-news/demo.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+ RNA News Demo - Morningstar Connectors Demos
+
+
+
+
+
+
diff --git a/demos/rna-news/demo.js b/demos/rna-news/demo.js
new file mode 100644
index 0000000..37a38b3
--- /dev/null
+++ b/demos/rna-news/demo.js
@@ -0,0 +1,141 @@
+const board = Dashboards.board('container', {
+ dataPool: {
+ connectors: [{
+ id: 'rna',
+ type: 'MorningstarRNANews',
+ options: {
+ security: {
+ id: 'US0378331005'
+ },
+ postman: {
+ environmentURL: '/tmp/Environment.json'
+ }
+ }
+ },
+ {
+ id: 'rna-type-amount',
+ type: 'JSON',
+ options: {
+ columnNames: ['Type', 'Amount'],
+ data: [
+ [],
+ []
+ ],
+ orientation: 'columns',
+ firstRowAsNames: false
+ }
+ }
+ ]
+ },
+ components: [
+ {
+ renderTo: 'dashboard-col-0',
+ connector: {
+ id: 'rna'
+ },
+ type: 'DataGrid',
+ title: 'News',
+ dataGridOptions: {
+ editable: false,
+ columns: {
+ Day: {
+ cellFormatter: function () {
+ return new Date(this.value)
+ .toISOString()
+ .substring(0, 10);
+ }
+ }
+ }
+ }
+ },
+ {
+ renderTo: 'dashboard-col-1',
+ connector: {
+ id: 'rna-type-amount',
+ columnAssignment: [{
+ seriesId: 'number-per-type',
+ data: ['Type', 'Amount']
+ }]
+ },
+ type: 'Highcharts',
+ chartOptions: {
+ chart: {
+ animation: false,
+ type: 'column'
+ },
+ title: {
+ text: 'Number of items per type'
+ },
+ subtitle: {
+ text: 'Shows number of news items of each kind'
+ },
+ series: [{
+ id: 'number-per-type',
+ name: 'Number per type'
+ }],
+ tooltip: {
+ shared: true,
+ split: true,
+ stickOnContact: true
+ },
+ lang: {
+ accessibility: {
+ chartContainerLabel:
+ 'Shows number of news items of each kind'
+ }
+ },
+ xAxis: {
+ type: 'category',
+ accessibility: {
+ description: 'Kind of news annoucement'
+ }
+ },
+ yAxis: {
+ title: {
+ text: 'Number of announcements'
+ }
+ }
+ }
+ }
+ ]
+});
+
+board.dataPool.getConnectorTable('rna')
+ .then(async table => {
+ const types = table.getColumn('Type');
+ const uniqueTypes = Array.from(new Set(types));
+ const numberPerType = uniqueTypes.map(type =>
+ types.reduce((previous, current) => {
+ if (current === type) {
+ return previous + 1;
+ }
+ return previous;
+ }, 0)
+ );
+
+ board.dataPool.setConnectorOptions({
+ id: 'rna-type-amount',
+ type: 'JSON',
+ options: {
+ columnNames: ['Type', 'Amount'],
+ orientation: 'columns',
+ firstRowAsNames: false,
+ data: [
+ uniqueTypes,
+ numberPerType
+ ]
+ }
+ });
+
+ // Refresh the component after updating the table
+ await board.getComponentByCellId('dashboard-col-1').initConnectors();
+ await board.getComponentByCellId('dashboard-col-1').update({
+ connector: {
+ id: 'rna-type-amount',
+ columnAssignment: [{
+ seriesId: 'number-per-type',
+ data: ['Type', 'Amount']
+ }]
+ }
+ });
+ });
diff --git a/src/RNANews/README.md b/src/RNANews/README.md
new file mode 100644
index 0000000..9eec58f
--- /dev/null
+++ b/src/RNANews/README.md
@@ -0,0 +1,8 @@
+Morningstar RNA News Connector
+===================================================
+
+This Highcharts Dashboards connector provides access to the [Morningstar RNANews API]. The JSON from the API is converted into a DataTable.
+
+
+
+[Morningstar RNANews API]: https://developer.morningstar.com/direct-web-services/documentation/api-reference/time-series/regulatory-news-announcements
\ No newline at end of file
diff --git a/src/RNANews/RNANewsConnector.ts b/src/RNANews/RNANewsConnector.ts
new file mode 100644
index 0000000..a184f54
--- /dev/null
+++ b/src/RNANews/RNANewsConnector.ts
@@ -0,0 +1,202 @@
+/* *
+ *
+ * (c) 2009-2024 Highsoft AS
+ *
+ * License: www.highcharts.com/license
+ *
+ * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
+ *
+ * Authors:
+ * - Eskil Gjerde Sviggum
+ *
+ * */
+
+
+'use strict';
+
+
+/* *
+ *
+ * Imports
+ *
+ * */
+
+import External from '../Shared/External';
+import MorningstarConnector from '../Shared/MorningstarConnector';
+import MorningstarAPI from '../Shared/MorningstarAPI';
+import MorningstarURL from '../Shared/MorningstarURL';
+import RNANewsOptions from './RNANewsOptions';
+import RNANewsConverter from './RNANewsConverter';
+import RNANewsJSON from './RNANewsJSON';
+
+/* *
+ *
+ * Class
+ *
+ * */
+
+class RNANewsConnector extends MorningstarConnector {
+
+ /**
+ * Constructs an instance of RNANewsConnector.
+ * @param {RNANewsOptions} [options]
+ * Options for the connector and converter.
+ */
+ public constructor(
+ options: RNANewsOptions
+ ) {
+ super(options);
+
+ this.converter = new RNANewsConverter(options);
+ this.options = options;
+ }
+
+ /* *
+ *
+ * Properties
+ *
+ * */
+
+
+ public override readonly converter: RNANewsConverter;
+
+
+ public override readonly options: RNANewsOptions;
+
+ /* *
+ *
+ * Functions
+ *
+ * */
+
+ /**
+ * Loads RNANews data from Morningstar.
+ *
+ * @return {Promise}
+ * Same connector instance with modified table.
+ */
+ public override async load(): Promise {
+ const options = this.options;
+ const {
+ security,
+ startDate,
+ endDate,
+ maxStories,
+ localization
+ } = options;
+
+ if (!security) {
+ return this;
+ }
+
+ const url = new MorningstarURL('timeseries/rna-news');
+ const searchParams = url.searchParams;
+ const api = new MorningstarAPI(options.api);
+
+ searchParams.setSecurityOptions([security]);
+
+ if (startDate) {
+ const date = RNANewsConnector.validateAndFormatDate(startDate);
+ searchParams.set('startDate', date);
+ }
+
+ if (endDate) {
+ const date = RNANewsConnector.validateAndFormatDate(endDate);
+ searchParams.set('endDate', date);
+ }
+
+ if (maxStories) {
+ const numericMaxStories = Number(maxStories);
+ if (!Number.isInteger(numericMaxStories)) {
+ throw new Error(`Expected maxStories to be integer, but is instead ${maxStories}`);
+ }
+ searchParams.set('maxStories', '' + maxStories);
+ }
+
+ if (localization) {
+ searchParams.setLocalizationOptions(localization);
+ }
+
+ const response = await api.fetch(url);
+ const json = await response.json() as RNANewsJSON.Response;
+
+ this.converter.parse({ json });
+
+ this.table.deleteColumns();
+ this.table.setColumns(this.converter.getTable().getColumns());
+
+ return this;
+ }
+
+}
+
+/* *
+ *
+ * Class Namespace
+ *
+ * */
+
+namespace RNANewsConnector {
+ /**
+ * If a number is provided, it is treated as a unix timestamp in
+ * milliseconds and converted to format `yyyy-MM-dd`.
+ * If a string is provided, it is validated to conform to the format.
+ * @private
+ * @param {number | string} date date as a timestamp of formatted string
+ * @return {string} date formatted as `yyyy-MM-dd`.
+ */
+ export function validateAndFormatDate(date: number | string): string {
+ let timestamp: number;
+ if (typeof date === 'string') {
+ // Check if string is a number, likely a timestamp
+ if (!Number.isNaN(Number(date))) {
+ timestamp = Number(date);
+ } else {
+ const parsedDate = Date.parse(date);
+ if (Number.isNaN(parsedDate)) {
+ throw new Error(`The date ${date} is not a valid date.`);
+ }
+ timestamp = parsedDate;
+ }
+ } else if (typeof date === 'number') {
+ timestamp = date;
+ } else {
+ throw new Error(
+ 'The provided date is not of type string, nor number.'
+ );
+ }
+
+ return new Date(timestamp)
+ .toISOString()
+ .substring(0, 10);
+ }
+}
+
+/* *
+ *
+ * Registry
+ *
+ * */
+
+
+declare module '@highcharts/dashboards/es-modules/Data/Connectors/DataConnectorType' {
+ interface DataConnectorTypes {
+ MorningstarRNANews: typeof RNANewsConnector;
+ }
+}
+
+
+External.DataConnector.registerType(
+ 'MorningstarRNANews',
+ RNANewsConnector
+);
+
+
+/* *
+ *
+ * Default Export
+ *
+ * */
+
+
+export default RNANewsConnector;
diff --git a/src/RNANews/RNANewsConverter.ts b/src/RNANews/RNANewsConverter.ts
new file mode 100644
index 0000000..f0e4ea9
--- /dev/null
+++ b/src/RNANews/RNANewsConverter.ts
@@ -0,0 +1,152 @@
+/* *
+ *
+ * (c) 2009-2024 Highsoft AS
+ *
+ * License: www.highcharts.com/license
+ *
+ * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
+ *
+ * Authors:
+ * - Eskil Gjerde Sviggum
+ *
+ * */
+
+'use strict';
+
+import * as External from '../Shared/External';
+/* *
+ *
+ * Imports
+ *
+ * */
+
+import MorningstarConverter from '../Shared/MorningstarConverter';
+import RNANewsJSON from './RNANewsJSON';
+import { RNANewsConverterOptions } from './RNANewsOptions';
+
+/* *
+ *
+ * Class
+ *
+ * */
+
+/**
+ * Handles parsing and transformation of
+ * Regulatory News Announcements to a table.
+ *
+ * @private
+ */
+class RNANewsConverter extends MorningstarConverter {
+
+ /* *
+ *
+ * Constructor
+ *
+ * */
+
+ /**
+ * Constructs an instance of the RNANewsConverter.
+ *
+ * @param {RNANewsConverterOptions} [options]
+ * Options for the converter.
+ */
+ constructor(
+ options?: RNANewsConverterOptions
+ ) {
+ super(options);
+
+ this.columns = [];
+ this.header = [];
+ this.options = options as Required;
+ }
+
+ /* *
+ *
+ * Properties
+ *
+ * */
+
+ private columns: (string | number)[][];
+ private header: string[];
+
+ /**
+ * Options for the DataConverter.
+ */
+ public override readonly options: Required;
+
+ /* *
+ *
+ * Functions
+ *
+ * */
+
+ /**
+ * Initiates the parsing of the RNANews
+ *
+ * @param {RNANewsConverterOptions}[options]
+ * Options for the parser
+ *
+ */
+ public parse(
+ options: RNANewsConverterOptions,
+ ): (boolean|undefined) {
+
+ this.header = ['Day', 'Title', 'Source', 'Type'];
+ this.columns = [];
+
+ if (!options.json) {
+ return false;
+ }
+
+ // Validate JSON
+
+ if (!RNANewsJSON.isResponse(options.json)) {
+ throw new Error('Invalid data');
+ }
+
+ // Transform rows
+ const rows: RNANewsJSON.RNANewsItem[] = [];
+ for (const dailyNews of options.json) {
+ const day = Number(dailyNews.day);
+ const rowsForDay = dailyNews.items.map(
+ (newsItem): RNANewsJSON.RNANewsItem => {
+ const [, title, source, type] = newsItem;
+ return [day, title, source, type];
+ });
+ rows.push(...rowsForDay);
+ }
+
+ // Transpose rows into columns.
+ const columns: Array[] = [];
+ for (let column = 0; column < this.header.length; column++) {
+ const columnFields: Array = [];
+ for (let row = 0; row < rows.length; row++) {
+ columnFields.push(rows[row][column]);
+ }
+ columns.push(columnFields);
+ }
+
+ this.columns = columns;
+
+ return true;
+ }
+
+ /**
+ * Handles converting the parsed data to a table.
+ *
+ * @return {DataTable}
+ * Table from the parsed RNANews
+ */
+ public override getTable(): External.DataTable {
+ return MorningstarConverter.getTableFromColumns(this.columns, this.header);
+ }
+
+}
+
+/* *
+ *
+ * Default Export
+ *
+ * */
+
+export default RNANewsConverter;
diff --git a/src/RNANews/RNANewsJSON.ts b/src/RNANews/RNANewsJSON.ts
new file mode 100644
index 0000000..b76b8dd
--- /dev/null
+++ b/src/RNANews/RNANewsJSON.ts
@@ -0,0 +1,112 @@
+/* *
+ *
+ * (c) 2009-2024 Highsoft AS
+ *
+ * License: www.highcharts.com/license
+ *
+ * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
+ *
+ * Authors:
+ * - Eskil Gjerde Sviggum
+ *
+ * */
+
+
+'use strict';
+
+
+/* *
+ *
+ * Namespace
+ *
+ * */
+
+namespace RNANewsJSON {
+
+
+ /* *
+ *
+ * Declarations
+ *
+ * */
+
+ export type RNANewsResponseItem = [id: string, title: string, source: string, type: string];
+
+ export type RNANewsItem = [day: number, title: string, source: string, type: string];
+
+ /**
+ * The response JSON for RNANews from Morningstar.
+ */
+ export interface ResponseItem {
+ /**
+ * A UNIX timestamp in milliseconds specifying the day
+ * at which the news items were published.
+ */
+ day: string;
+
+ /**
+ * List of news items.
+ *
+ * A news-item is a 4-tuple with the following fields:
+ * - Unique identifier
+ * - Title of announcement.
+ * - Source
+ * - Type
+ */
+ items: RNANewsResponseItem[];
+ }
+
+ export type Response = ResponseItem[];
+
+ /* *
+ *
+ * Functions
+ *
+ * */
+
+ export function isResponse(
+ json?: unknown
+ ): json is Response {
+ return (
+ !!json &&
+ Array.isArray(json) &&
+ json.length === 0 ||
+ isResponseItem((json as Array)[0])
+ );
+ }
+
+
+ function isResponseItem(
+ json?: unknown
+ ): json is ResponseItem {
+ return (
+ !!json &&
+ typeof json === 'object' &&
+ typeof (json as ResponseItem).day === 'string' &&
+ typeof (json as ResponseItem).items === 'object' &&
+ isRNANewsResponseItem(typeof (json as ResponseItem).items[0])
+ );
+ }
+
+ function isRNANewsResponseItem(
+ json?: unknown
+ ): json is RNANewsResponseItem {
+ return (
+ !!json &&
+ Array.isArray(json) &&
+ json.length === 4 &&
+ !json.find(entry => typeof entry !== 'string')
+ );
+ }
+
+}
+
+
+/* *
+*
+* Default Export
+*
+* */
+
+
+export default RNANewsJSON;
diff --git a/src/RNANews/RNANewsOptions.ts b/src/RNANews/RNANewsOptions.ts
new file mode 100644
index 0000000..da660c4
--- /dev/null
+++ b/src/RNANews/RNANewsOptions.ts
@@ -0,0 +1,67 @@
+/* *
+ *
+ * (c) 2009-2024 Highsoft AS
+ *
+ * License: www.highcharts.com/license
+ *
+ * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
+ *
+ * Authors:
+ * - Eskil Gjerde Sviggum
+ *
+ * */
+
+
+/* *
+ *
+ * Imports
+ *
+ * */
+import type LocalizationOptions from '../Shared/LocalizationOptions';
+import MorningstarOptions, { MorningstarConverterOptions, MorningstarSecurityOptions } from "../Shared/MorningstarOptions";
+import RNANewsJSON from './RNANewsJSON';
+
+export interface RNANewsOptions extends RNANewsConverterOptions, MorningstarOptions {
+
+ /**
+ * Security to retrieve.
+ */
+ security?: MorningstarSecurityOptions;
+
+ /**
+ * The start date of the time series.
+ * Should be either a UNIX timestamp,
+ * or a string formatted as `yyyy-MM-dd`.
+ */
+ startDate?: number | string;
+
+ /**
+ * The end date of the time series.
+ * Should be either a UNIX timestamp,
+ * or a string formatted as `yyyy-MM-dd`.
+ */
+ endDate?: number | string;
+
+ /**
+ * The maximum number of announcements to load.
+ */
+ maxStories?: number;
+
+ /**
+ * Localization options.
+ */
+ localization?: LocalizationOptions;
+
+}
+
+export interface RNANewsConverterOptions extends MorningstarConverterOptions {
+ json?: RNANewsJSON.Response
+}
+
+/* *
+ *
+ * Default Export
+ *
+ * */
+
+export default RNANewsOptions;
diff --git a/src/RNANews/index.ts b/src/RNANews/index.ts
new file mode 100644
index 0000000..801111b
--- /dev/null
+++ b/src/RNANews/index.ts
@@ -0,0 +1,50 @@
+/* *
+ *
+ * (c) 2009-2024 Highsoft AS
+ *
+ * License: www.highcharts.com/license
+ *
+ * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
+ *
+ * Authors:
+ * - Eskil Gjerde Sviggum
+ *
+ * */
+
+
+'use strict';
+
+
+/* *
+ *
+ * Imports
+ *
+ * */
+
+
+import RNANewsConverter from "./RNANewsConverter";
+import RNANewsConnector from "./RNANewsConnector";
+
+
+/* *
+ *
+ * Exports
+ *
+ * */
+
+
+export * from './RNANewsConverter';
+export * from './RNANewsConnector';
+
+
+/* *
+ *
+ * Default Export
+ *
+ * */
+
+
+export default {
+ RNANewsConverter,
+ RNANewsConnector
+};
diff --git a/src/Shared/LocalizationOptions.ts b/src/Shared/LocalizationOptions.ts
index d31d503..31be8be 100644
--- a/src/Shared/LocalizationOptions.ts
+++ b/src/Shared/LocalizationOptions.ts
@@ -38,10 +38,21 @@ export interface DateOptions {
export interface LocalizationOptions {
+ /**
+ * A two-letter ISO-3166-1 alpha-2 country code.
+ * For example: US, JP
+ */
country: string;
+ /**
+ * The currency to use.
+ */
currency: Currency;
+ /**
+ * A two-letter ISO-639-1 alpha-2 language code.
+ * For example: en, ja
+ */
language: string;
}
diff --git a/src/Shared/MorningstarSearchParams.ts b/src/Shared/MorningstarSearchParams.ts
index d6c450f..86c3894 100644
--- a/src/Shared/MorningstarSearchParams.ts
+++ b/src/Shared/MorningstarSearchParams.ts
@@ -23,6 +23,7 @@
import type { MorningstarSecurityOptions } from './MorningstarOptions';
+import LocalizationOptions from './LocalizationOptions';
/* *
@@ -70,6 +71,30 @@ export class MorningstarSearchParams extends URLSearchParams {
return this;
}
+ /**
+ * Sets `languageId` based on given localization options.
+ *
+ * @param options
+ * Localization options to set.
+ *
+ * @return
+ * The modified search parameters as reference.
+ */
+ public setLocalizationOptions(
+ options: LocalizationOptions
+ ): MorningstarSearchParams {
+
+ const {
+ country,
+ language
+ } = options;
+
+ const languageCultureCode = `${language.toLowerCase()}-${country.toUpperCase()}`;
+ this.set('languageId', languageCultureCode);
+
+ return this;
+ }
+
}
diff --git a/src/index.ts b/src/index.ts
index ae986d7..55eb171 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -24,6 +24,7 @@
import MorningstarAPI from './Shared/MorningstarAPI';
import TimeSeries from './TimeSeries/index';
+import RNANews from './RNANews/index';
/* *
@@ -35,6 +36,7 @@ import TimeSeries from './TimeSeries/index';
export * as Shared from './Shared/index';
export * as TimeSeries from './TimeSeries/index';
+export * as RNANews from './RNANews/index';
/* *
@@ -46,5 +48,6 @@ export * as TimeSeries from './TimeSeries/index';
export default {
MorningstarAPI,
- TimeSeries
+ TimeSeries,
+ RNANews
};