UNPKG

@google/dscc

Version:

`dscc` (Data Studio Community Component) is a library to help with the building of community components for Google Data Studio. It can be used as a standalone library, or as a npm dependency.

410 lines (406 loc) 13.6 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __export(m) { for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; } Object.defineProperty(exports, "__esModule", { value: true }); /*! @license Copyright 2019 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var types_1 = require("./types"); // Make all exported types available to external users. __export(require("./types")); /** * Returns the width (in pixels) of the vis's iframe. * * Usage: * ``` * var myWidth = dscc.getWidth(); * console.log('My width is: ', myWidth); * ``` */ exports.getWidth = function () { return document.body.clientWidth; }; /** * Returns the height (in pixels) of the vis's iframe. * * Usage: * ``` * var myHeight = dscc.getHeight(); * console.log('My height is: ', myHeight); * ``` */ exports.getHeight = function () { return document.documentElement.clientHeight; }; /** * Returns the componentId of the vis. Component ids uniquely identify a vis to * Data Studio. * * Usage: * ``` * var myComponentId = dscc.getComponentId(); * console.log('My componentId is: ', myComponentId); * ``` */ exports.getComponentId = function () { var params = new URLSearchParams(window.location.search); if (params.get('dscId') !== null) { return params.get('dscId'); } else { throw new Error('dscId must be in the query parameters. ' + 'This is a bug in ds-component, please file a bug: ' + 'https://github.com/googledatastudio/ds-component/issues/new'); } }; /** * Returns the fields indexed by their Data Studio id. */ var fieldsById = function (message) { return message.fields.reduce(function (acc, field) { acc[field.id] = field; return acc; }, {}); }; /** * Zips two arrays together into a new array. Uses the length of the shortest * array. * * Usage: * ``` * const a = [1, 2, 3]; * const b = ['a', 'b', 'c', 'd']; * const zipped = zip2(a, b); * expect(zipped).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]); * ``` */ var zip2 = function (t, u) { if (t.length < u.length) { return t.map(function (tEntry, idx) { return [tEntry, u[idx]]; }); } else { return u.map(function (uEntry, idx) { return [t[idx], uEntry]; }); } }; // `.sort` isn't stable, but if you compare items, and when they are equal use // the original index, it is then stable. var stableSort = function (arr, compare) { return arr .map(function (item, index) { return ({ item: item, index: index }); }) .sort(function (a, b) { return compare(a.item, b.item) || a.index - b.index; }) .map(function (_a) { var item = _a.item; return item; }); }; var dimensionOrMetric = function (cde) { return cde.type === types_1.ConfigDataElementType.DIMENSION || cde.type === types_1.ConfigDataElementType.METRIC; }; var toNum = function (cdet) { return cdet === types_1.ConfigDataElementType.DIMENSION ? -1 : 1; }; var flattenConfigIds = function (message) { var dimnsAndMets = []; message.config.data.forEach(function (configData) { configData.elements .filter(dimensionOrMetric) .forEach(function (configDataElement) { dimnsAndMets.push(configDataElement); }); }); var sorted = stableSort(dimnsAndMets, function (a, b) { return toNum(a.type) - toNum(b.type); }); var configIds = []; sorted.forEach(function (configDataElement) { configDataElement.value.forEach(function () { return configIds.push(configDataElement.id); }); }); return configIds; }; /** * Joins a single table row with the matching `ConfigId` */ var joinObjectRow = function (configIds) { return function (row) { var objectRow = {}; zip2(row, configIds).forEach(function (_a) { var rowVal = _a[0], configId = _a[1]; if (objectRow[configId] === undefined) { objectRow[configId] = []; } objectRow[configId].push(rowVal); }, {}); return objectRow; }; }; /** * Formats tables into the `ObjectTables` format. */ var objectFormatTable = function (message) { var _a; var configIds = flattenConfigIds(message); var objectTables = (_a = {}, _a[types_1.TableType.DEFAULT] = [], _a); message.dataResponse.tables.forEach(function (table) { var objectRows = table.rows.map(joinObjectRow(configIds)); if (table.id === types_1.TableType.DEFAULT) { objectTables[table.id] = objectRows; } else { var current = objectTables[table.id]; if (current === undefined) { objectTables[table.id] = []; } objectTables[table.id] = objectTables[table.id].concat(objectRows); } }); return objectTables; }; /** * Formats tables into the `Tables` format. */ var tableFormatTable = function (message) { var _a; var fieldsBy = exports.fieldsByConfigId(message); var configIds = flattenConfigIds(message); var configIdIdx = {}; var headers = configIds.map(function (configId) { if (configIdIdx[configId] === undefined) { configIdIdx[configId] = 0; } else { configIdIdx[configId]++; } var idx = configIdIdx[configId]; var field = fieldsBy[configId][idx]; var heading = __assign(__assign({}, field), { configId: configId }); return heading; }); var tableTables = (_a = {}, _a[types_1.TableType.DEFAULT] = { headers: [], rows: [] }, _a); message.dataResponse.tables.forEach(function (table) { tableTables[table.id] = { headers: headers, rows: table.rows, }; }); return tableTables; }; /** * Returns the fields indexed by their config id. Since many fields can be in * the same `METRIC`/`DIMENSION` selection, `configId` is mapped to `Field[]`. */ exports.fieldsByConfigId = function (message) { var fieldsByDSId = fieldsById(message); var fieldsBy = {}; message.config.data.forEach(function (configData) { configData.elements .filter(dimensionOrMetric) .forEach(function (configDataElement) { fieldsBy[configDataElement.id] = configDataElement.value.map(function (dsId) { return fieldsByDSId[dsId]; }); }); }); return fieldsBy; }; /** * Flattens the style entries into a single object. `styleId`s should be unique. */ var flattenStyle = function (message) { var styleById = {}; (message.config.style || []).forEach(function (styleEntry) { styleEntry.elements.forEach(function (configStyleElement) { if (styleById[configStyleElement.id] !== undefined) { throw new Error("styleIds must be unique. Your styleId: '" + configStyleElement.id + "' is used more than once."); } styleById[configStyleElement.id] = { value: configStyleElement.value, defaultValue: configStyleElement.defaultValue, }; }); }, {}); return styleById; }; var themeStyle = function (message) { return message.config.themeStyle; }; var mapInteractionTypes = function (dsInteraction) { switch (dsInteraction) { case types_1.DSInteractionType.FILTER: return types_1.InteractionType.FILTER; } }; var transformDSInteraction = function (message) { var dsInteractions = message.config.interactions; // TODO - remove once interactions are live. if (dsInteractions === undefined) { return {}; } return dsInteractions.reduce(function (acc, dsInteraction) { var interactions = dsInteraction.supportedActions.map(mapInteractionTypes); var value = { type: mapInteractionTypes(dsInteraction.value.type), data: dsInteraction.value.data, }; acc[dsInteraction.id] = { value: value, supportedActions: interactions, }; return acc; }, {}); }; /** * Transform for date ranges */ var toDateRanges = function (message) { var dateRanges = message.dataResponse.dateRanges || []; var output = {}; return dateRanges.reduce(function (inProgress, currentDSDateRange) { inProgress[currentDSDateRange.id] = { start: currentDSDateRange.start, end: currentDSDateRange.end, }; return inProgress; }, output); }; /* Transform for color maps */ var toColorsByDimension = function (message) { var colors = message.dataResponse.colorMap || {}; return __assign({}, colors); }; /** * The transform to use for data in a Table format. i.e. `[[1, 2, 3], [4, 5, 6]]` */ exports.tableTransform = function (message) { return ({ tables: tableFormatTable(message), dateRanges: toDateRanges(message), fields: exports.fieldsByConfigId(message), style: flattenStyle(message), theme: themeStyle(message), interactions: transformDSInteraction(message), colorMap: toColorsByDimension(message), }); }; /** * The transform to use for data in an object format. i.e. `[{name: 'john', views: 3}, {name: 'suzie', views: 5}]` */ exports.objectTransform = function (message) { return ({ tables: objectFormatTable(message), dateRanges: toDateRanges(message), fields: exports.fieldsByConfigId(message), style: flattenStyle(message), theme: themeStyle(message), interactions: transformDSInteraction(message), colorMap: toColorsByDimension(message), }); }; /** * Check if the transform is likely the identity function * This is not a supported implementation path * Avoid this if at all possible - please use either objectTransform or tableTransform */ var isProbablyIdentityFunction = function (transform) { var isIdentity = false; if (transform('identity') === 'identity') { isIdentity = true; console.warn("This is an unsupported data format. Please use one of the supported transforms:\n dscc.objectFormat or dscc.tableFormat."); } return isIdentity; }; var isValidTransform = function (transform) { var isValid = false; if (transform === exports.tableTransform || transform === exports.objectTransform) { isValid = true; } else if (isProbablyIdentityFunction(transform)) { isValid = true; } return isValid; }; /* * Subscribes to messages from Data Studio. Calls `cb` for every new * [[MessageType.RENDER]] message. Returns a function that will unsubscribe * `callback` from further invocations. * * Usage - tableTransform: * ``` * var unsubscribe = dscc.subscribeToData(function(message) { * console.log(message.tables) * console.log(message.fields) * console.log(message.style) * }, {transform: dscc.tableTransform}); * * setTimeout(function() { * unsubscribe(); * }, 3000) * ``` * Usage - objectTransform: * ``` * var unsubscribe = dscc.subscribeToData(function(message) { * console.log(message.tables) * console.log(message.fields) * console.log(message.style) * }, {transform: dscc.objectTransform}); * * setTimeout(function() { * unsubscribe(); * }, 3000) * ``` */ exports.subscribeToData = function (cb, options) { if (isValidTransform(options.transform)) { var onMessage_1 = function (message) { if (message.data.type === types_1.MessageType.RENDER) { cb(options.transform(message.data)); } else { console.error("MessageType: " + message.data.type + " is not supported by this version of the library."); } }; window.addEventListener('message', onMessage_1); var componentId = exports.getComponentId(); // Tell DataStudio that the viz is ready to get events. var vizReadyMessage = { componentId: componentId, type: types_1.ToDSMessageType.VIZ_READY, }; window.parent.postMessage(vizReadyMessage, '*'); return function () { return window.removeEventListener('message', onMessage_1); }; } else { throw new Error("Only the built in transform functions are supported."); } }; /* * Does the thing that interactions should do. */ exports.sendInteraction = function (actionId, interaction, data) { var componentId = exports.getComponentId(); var interactionMessage = { type: types_1.ToDSMessageType.INTERACTION, id: actionId, data: data, componentId: componentId, }; window.parent.postMessage(interactionMessage, '*'); }; /* * Clears an interaction */ exports.clearInteraction = function (actionId, interaction) { exports.sendInteraction(actionId, interaction, undefined); }; //# sourceMappingURL=index.js.map