From b832f50b6d8d78484d46c54302318dc1f88e8270 Mon Sep 17 00:00:00 2001 From: Ian Goldstein Date: Fri, 28 Oct 2016 15:46:55 -0700 Subject: [PATCH] fold changes from temporary /perspectivesBeta back into /perspectives --- view/loadView.js | 6 +- view/perspective/PerspectiveController.js | 2 +- view/perspective/app.js | 259 ++++--- view/perspectiveBeta/CreatePerspective.js | 425 ------------ view/perspectiveBeta/PerspectiveController.js | 96 --- view/perspectiveBeta/app.js | 651 ------------------ view/perspectiveBeta/eventsQueue.js | 125 ---- view/perspectiveBeta/lensUtils.js | 338 --------- view/perspectiveBeta/perspective.pug | 38 - 9 files changed, 165 insertions(+), 1775 deletions(-) delete mode 100644 view/perspectiveBeta/CreatePerspective.js delete mode 100644 view/perspectiveBeta/PerspectiveController.js delete mode 100644 view/perspectiveBeta/app.js delete mode 100644 view/perspectiveBeta/eventsQueue.js delete mode 100644 view/perspectiveBeta/lensUtils.js delete mode 100644 view/perspectiveBeta/perspective.pug diff --git a/view/loadView.js b/view/loadView.js index e495272b88..e27de8f145 100644 --- a/view/loadView.js +++ b/view/loadView.js @@ -36,8 +36,6 @@ const viewmap = { '/samples/:key/edit': 'admin', '/perspectives': 'perspective/perspective', '/perspectives/:key': 'perspective/perspective', - '/perspectivesBeta': 'perspectiveBeta/perspective', - '/perspectivesBeta/:key': 'perspectiveBeta/perspective', }; /** @@ -133,9 +131,7 @@ module.exports = function loadView(app, passport) { // if url contains a query, render perspective detail page with realtime // updates if ((key === '/perspectives' && Object.keys(req.query).length) || - key === '/perspectives/:key' || - (key === '/perspectivesBeta' && Object.keys(req.query).length) || - key === '/perspectivesBeta/:key') { + key === '/perspectives/:key') { res.render(viewmap[key], templateVars); } else { res.render(viewmap[key], trackObj); diff --git a/view/perspective/PerspectiveController.js b/view/perspective/PerspectiveController.js index 939798eab1..65271de46e 100644 --- a/view/perspective/PerspectiveController.js +++ b/view/perspective/PerspectiveController.js @@ -73,7 +73,7 @@ class PerspectiveController extends React.Component { placeholderText='Search Perspectives' options={ persNames } showSearchIcon={ true } - onAddNewButton={ this.openCreatePanel.bind(this) } + onAddNewButton={ values.lenses ? this.openCreatePanel.bind(this) : undefined } onClickItem={ this.goToUrl.bind(this) } newButtonText='New Perspective' /> diff --git a/view/perspective/app.js b/view/perspective/app.js index 57d2b5ce0c..f823fc97d1 100644 --- a/view/perspective/app.js +++ b/view/perspective/app.js @@ -9,15 +9,51 @@ /** * view/perspective/app.js * - * Loads required data for named and unnamed perspectives. + * When this page is loaded, we call "getPerspectiveNames" to load all the + * perspective names to populate the dropdown. + * If there are no perspectives, we just render the perspective overlay over an + * empty page. + * If there are perspectives, we call "whichPerspective" to figure out which + * perspective to load. + * If it's not in the URL path, either use the DEFAULT_PERSPECTIVE from global + * config OR the first perspective from the list of perspective names + * (alphabetical order), and redirect to the URL using *that* perspective name. + * Once we can identify the perspective in the URL path, we call + * "getPerspective" to load the specified perspective. When we get the + * perspective back from the server, we perform all of this async work in + * parallel: + * (1) call "setupSocketIOClient" to initialize the socket.io client + * (2) call "getHierarchy" to request the hierarchy + * (3) call "getLensPromise" to request the lens library + * (4) call "loadPerspective" to start rendering the perspective-picker + * component + * (5) call "loadExtraStuffForCreatePerspective" to start loading all the extra + * data we'll need for the "CreatePerspective" component + * + * If config var "eventThrottleMillis" > 0, we start a timer to flush the + * realtime event queue on that defined interval. + * + * Whenever we get the response back with the lens, we dispatch the lens.load + * event to the lens. + * + * Whenever we get the response back with the hierarchy, we dispatch the + * lens.hierarchyLoad event to the lens. (If we happen to get the hierarchy + * back *before* the lens, hold onto it, wait for the lens, *then* dispatch the + * lens.hierarchyLoad event *after* the lens.load event.) + * + * Whenever we get all the extra data we need for "CreatePerspective", we + * re-render the perspective-picker component. */ - import request from 'superagent'; import React from 'react'; import ReactDOM from 'react-dom'; import PerspectiveController from './PerspectiveController'; const u = require('../utils'); const eventsQueue = require('./eventsQueue'); +let gotLens = false; +const lensLoadEvent = new CustomEvent('refocus.lens.load'); +let hierarchyLoadEvent; +const pcValues = {}; // TODO get rid of this once all the lenses aren't using it require('./lensUtils'); @@ -29,6 +65,7 @@ const DEBUG_REALTIME = window.location.href.split(/[&\?]/) .includes('debug=REALTIME'); const WEBSOCKET_ONLY = window.location.href.split(/[&\?]/) .includes('protocol=websocket'); + const REQ_HEADERS = { Authorization: u.getCookie('Authorization'), 'X-Requested-With': 'XMLHttpRequest', @@ -98,7 +135,7 @@ function setupSocketIOClient(persBody) { /* * if the transprotocol is set, initialize the socketio client with - * the transport protocol options. The transProtocol variable is set in + * the transport protocol options. The transProtocol variable is set in * perspective.pug */ const options = {}; @@ -125,7 +162,7 @@ function setupSocketIOClient(persBody) { } else { socket = io(namespace, options); } - + socket.on(eventsQueue.eventType.INTRNL_SUBJ_ADD, (data) => { handleEvent(data, eventsQueue.eventType.INTRNL_SUBJ_ADD); }); @@ -274,57 +311,59 @@ function getPromiseWithUrl(name, url, callback) { }); } // getHierarchyData -/** - * Passes data on to Controller to pass onto renderers. - * - * @param {Object} values Data returned from AJAX. - * @param {Object} stateObject Data from queryParams. - */ -function loadController(values, stateObject) { - ReactDOM.render( - , - PERSPECTIVE_CONTAINER - ); -} - /** * Given the rootSubject, gets subject hierarchy * and returns a promise to load rootSubject * * @param {String} rootSubject The subject to load the hierarchy of * @param {String} filterString Any filters + * @returns {Promise} which resolves once we receive the hierarchy */ -function getSubjectPromise(rootSubject, filterString) { - let apiPath = `/v1/subjects/${rootSubject}/hierarchy`; - if (filterString) { - apiPath += filterString; - } - - return getPromiseWithUrl('rootSubject', apiPath, (hierarchyRes) => { - const ev = new CustomEvent('refocus.lens.hierarchyLoad', { - detail: hierarchyRes.body, +function getHierarchy(rootSubject, filterString) { + const apiPath = `/v1/subjects/${rootSubject}/hierarchy` + + (filterString || ''); + return getPromiseWithUrl('rootSubject', apiPath, (res) => { + hierarchyLoadEvent = new CustomEvent('refocus.lens.hierarchyLoad', { + detail: res.body, }); - LENS_DIV.dispatchEvent(ev); + + /* + * The order of events matters so only dispatch the hierarchyLoad event if + * we ahve already gotten the lens response back. If hierarchy happens to + * have come back first, then it will be dispatched from getLensPromise. + */ + if (gotLens) { + LENS_DIV.dispatchEvent(hierarchyLoadEvent); + } }); -} +} // getHierarchy /** * @param {String} lensNameOrId + * @returns {Promise} which resolves once we receive the lens */ function getLensPromise(lensNameOrId) { - return getPromiseWithUrl('lens', '/v1/lenses/' + lensNameOrId, (lensRes) => { + const apiPath = `/v1/lenses/${lensNameOrId}`; + return getPromiseWithUrl('lens', apiPath, (res) => { // inject lens library files in perspective view. - handleLibraryFiles(lensRes); + handleLibraryFiles(res); // remove spinner and load lens const spinner = document.getElementById('lens_loading_spinner'); spinner.parentNode.removeChild(spinner); // trigger refocus.lens.load event - LENS_DIV.dispatchEvent(new CustomEvent('refocus.lens.load')); + gotLens = true; + LENS_DIV.dispatchEvent(lensLoadEvent); + + /* + * The order of events matters so if we happened to have gotten the + * hierarchy *before* the lens, then dispatch the lens.hierarchyLoad event + * now. + */ + if (hierarchyLoadEvent) { + LENS_DIV.dispatchEvent(hierarchyLoadEvent); + } }); } // getLensPromise @@ -358,8 +397,7 @@ function getAllParams() { } return responseObject; -} - +} // getAllParams /** * Returns array of objects with tags @@ -391,48 +429,73 @@ function getPublishedObjectsbyField(array, field) { } /** - * @param {Array} promisesArr An array of AJAX GET promises. * @param {Object} perspective An object + */ +function loadPerspective(perspective, params) { + pcValues.name = perspective.name; + const stateObject = Object.assign( + { perspectives: perspective ? perspective.name : '' }, + params + ); + getPromiseWithUrl('perspectives', '/v1/perspectives') + .then((values) => { + pcValues.perspectives = values.res; + loadController(pcValues, stateObject); + }); +} // loadPerspective + +/** + * @param {Array} promisesArr An array of AJAX GET promises. * @param {boolean} getRoot Get all subjects, set first published subject as * rootSubject * @param {boolean} getLens Get all lenses, use the first published lens */ -function loadPerspective(promisesArr, perspective, getRoot, getLens) { - let allResponse = {}; - const arr = [ - { name: 'perspectives', url: '/v1/perspectives' }, - { name: 'aspectFilter', url: '/v1/aspects' }, - ]; +function loadExtraStuffForCreatePerspective(perspective, params, promisesArr, + getRoot, getLens) { + pcValues.name = perspective.name; + const stateObject = Object.assign( + { perspectives: perspective ? perspective.name : '' }, + params + ); + const pArr = promisesArr || []; + const getAllSubjectsPromise = getPromiseWithUrl('subjects', '/v1/subjects'); - const subjectPromise = !getRoot ? getAllSubjectsPromise : - getAllSubjectsPromise.then((val) => { - allResponse.subjects = val.res; + let subjectPromise; + if (getRoot) { + subjectPromise = getAllSubjectsPromise.then((val) => { + pcValues.subjects = val.res; // get the first published subject, sorted in alphabetical order by - // absolutePath - const rootSubject = getPublishedObjectsbyField( - val.res, 'absolutePath' - ).sort()[ZERO]; - return getSubjectPromise(rootSubject); + // absolutePath + const rootSubject = getPublishedObjectsbyField(val.res, 'absolutePath') + .sort()[ZERO]; + return getHierarchy(rootSubject); }); - promisesArr.push(subjectPromise); + } else { + subjectPromise = getAllSubjectsPromise; + } + + pArr.push(subjectPromise); const getAllLensesPromise = getPromiseWithUrl('lenses', '/v1/lenses'); - const lensPromise = !getLens ? getAllLensesPromise : - getAllLensesPromise.then((val) => { - allResponse.lenses = val.res; + let lensPromise; + if (getLens) { + lensPromise = getAllLensesPromise.then((val) => { + pcValues.lenses = val.res; // get the first published lens, sorted in alphabetical order by name const lens = getPublishedObjectsbyField(val.res, 'name').sort()[ZERO]; return getLensPromise(lens); }); - promisesArr.push(lensPromise); - - for (let i = arr.length - ONE; i >= ZERO; i--) { - promisesArr.push(getPromiseWithUrl(arr[i].name, arr[i].url)); + } else { + lensPromise = getAllLensesPromise; } - // change this to GET from API, after its implemented; + pArr.push(lensPromise); + + pArr.push(getPromiseWithUrl('aspectFilter', '/v1/aspects')); + + // TODO change this to GET from API, after its implemented; const statusFilter = [ 'Critical', 'Invalid', @@ -441,27 +504,17 @@ function loadPerspective(promisesArr, perspective, getRoot, getLens) { 'Info', 'OK', ]; - Promise.all(promisesArr).then((values) => { + Promise.all(pArr).then((values) => { for (let i = values.length - ONE; i >= ZERO; i--) { - allResponse[values[i].name] = values[i].res; + pcValues[values[i].name] = values[i].res; } - // for named perspective - if (perspective) { - allResponse = Object.assign(perspective, allResponse); - } - - allResponse.statusFilter = statusFilter; - - // fill in queries from the browser - const stateObject = Object.assign( - { perspectives: perspective ? perspective.name : '' }, getAllParams() - ); - allResponse.aspectTags = getTagsFromResources(allResponse.aspectFilter); - allResponse.subjectTagFilter = getTagsFromResources(allResponse.subjects); - loadController(allResponse, stateObject); + pcValues.statusFilter = statusFilter; + pcValues.aspectTags = getTagsFromResources(pcValues.aspectFilter); + pcValues.subjectTagFilter = getTagsFromResources(pcValues.subjects); + loadController(pcValues, stateObject); }); -} // getPerspectiveNames +} // loadExtraStuffForCreatePerspective function handleUnnamedPerspective() { const promisesArr = []; @@ -472,7 +525,7 @@ function handleUnnamedPerspective() { // if no loadObj.rootSubject, rootSubject is the first subject in GET subject if (rootSubject) { - promisesArr.push(getSubjectPromise(rootSubject)); + promisesArr.push(getHierarchy(rootSubject)); } else if (!rootSubject) { // no queryParam.rootSubject: need to pass it to loadPerspective getRoot = true; } @@ -483,30 +536,28 @@ function handleUnnamedPerspective() { getLens = true; } - loadPerspective(promisesArr, null, getRoot, getLens); -} + const params = getAllParams(); + loadPerspective(null, params); + loadExtraStuffForCreatePerspective(null, params, promisesArr, + getRoot, getLens); +} // handleUnnamedPerspective /** - * GETs the specified perspective - * returns promises to find the perspective's rootSubject and lens - * @param {String} perspNameOrId The perspective to GET - * @returns {Array} Of promises + * Retrieves the specified perspective, initiates loading lens and hierarchy. + * + * @param {String} perspNameOrId - The name or id of the perspective */ function getPerspective(perspNameOrId) { - const perspectivePromise = getPromiseWithUrl( - 'perspective', - '/v1/perspectives/' + perspNameOrId - ); - perspectivePromise.then((val) => { + getPromiseWithUrl('perspective', `/v1/perspectives/${perspNameOrId}`) + .then((val) => { setupSocketIOClient(val.res); const { lensId, name, rootSubject } = val.res; - const filterString = getFilterQuery(val.res); - getLensPromise(lensId) - .then(() => getSubjectPromise(rootSubject, filterString)) - .then(() => { - const promisesArr = []; - return loadPerspective(promisesArr, { name, rootSubject, lensId }); - }); + getHierarchy(rootSubject, getFilterQuery(val.res)); + getLensPromise(lensId); + const p = { name, rootSubject, lensId }; + const params = getAllParams(); + loadPerspective(p, params); + loadExtraStuffForCreatePerspective(p, params); }) .catch((error) => { document.getElementById('errorInfo').innerHTML += error; @@ -582,3 +633,19 @@ window.onload = () => { if (eventThrottleMillis !== ZERO) { eventsQueue.scheduleFlushQueue(LENS_DIV, eventThrottleMillis); } + +/** + * Passes data on to Controller to pass onto renderers. + * + * @param {Object} values Data returned from AJAX. + * @param {Object} stateObject Data from queryParams. + */ +function loadController(values, stateObject) { + ReactDOM.render( + , + PERSPECTIVE_CONTAINER + ); +} diff --git a/view/perspectiveBeta/CreatePerspective.js b/view/perspectiveBeta/CreatePerspective.js deleted file mode 100644 index 9d1ca8167f..0000000000 --- a/view/perspectiveBeta/CreatePerspective.js +++ /dev/null @@ -1,425 +0,0 @@ -/** - * Copyright (c) 2016, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or - * https://opensource.org/licenses/BSD-3-Clause - */ - -/** - * view/perspective/CreatePerspective.js - * - * Shows the perspective's details, and allow user inupt for edit. - */ -import React, { PropTypes } from 'react'; -import Modal from '../admin/components/common/Modal'; -import Pill from '../admin/components/common/Pill'; -import Dropdown from '../admin/components/common/Dropdown'; -import ControlledInput from '../admin/components/common/ControlledInput'; -import ErrorRender from '../admin/components/common/ErrorRender'; -import RadioGroup from '../admin/components/common/RadioGroup'; -const ZERO = 0; -const ONE = 1; -/** - * Given array of objects, returns array of strings or primitives - * of values of the field key - * - * @param {String} field The field of each value to return - * @param {array} arrayOfObjects The array of objects to - * get new array from - * @returns {Array} The array of strings or primitives - */ -function getArray(field, arrayOfObjects) { - let arr = []; - for (let i = arrayOfObjects.length - ONE; i >= ZERO; i--) { - if (arrayOfObjects[i].isPublished) { - arr.push(arrayOfObjects[i][field]); - } - } - return arr; -} - -/** - * Returns the state object without any incidental data. - * @param {Object} stateObject this component's state. - * @ returns {Object} The state with bare minimum data. - */ -function getStateDataOnly(stateObject) { - const stateCopy = JSON.parse(JSON.stringify(stateObject)); - delete stateCopy.dropdownConfig; - delete stateCopy.error; - return stateCopy; -} -/** - * Ie. "thisStringIsGood" --> This String Is Good - * @param {String} string The string to split - * @returns {String} The converted string, includes spaces. - */ -function convertCamelCase(string) { - return string - // insert a space before all caps - .replace(/([A-Z])/g, ' $1') - // uppercase the first character - .replace(/^./, function(string) { return string.toUpperCase(); }); -} - -/** - * @param {DOM_element} el The element to find ancestor with selector from - * @param {String} selector The selector of ancestor - * @returns {DOM_element} The ancestor element that is closest to el - */ -function findCommonAncestor(el, selector) { - let retval = null; - while (el) { - if (el.classList.contains(selector)) { - retval = el; - break; - } - el = el.parentNode; - } - return retval; -} - -class CreatePerspective extends React.Component { - // separate props and status from value prop - constructor(props) { - super(props); - this.appendPill = this.appendPill.bind(this); - this.showError = this.showError.bind(this); - this.deletePill = this.deletePill.bind(this); - this.handleRadioButtonClick = this.handleRadioButtonClick.bind(this); - this.updateDropdownConfig = this.updateDropdownConfig.bind(this); - this.state = { - dropdownConfig: {}, - error: '', - ...props.stateObject, - }; // default values - } - componentDidMount() { - this.updateDropdownConfig(); - } - updateDropdownConfig() { - // attach config to keys, keys to dropdownConfig - const { dropdownConfig, error } = this.state; - let errorMessage = error; - const { values } = this.props; - let stateObject = getStateDataOnly(this.state); - let config = {}; - - for (let key in stateObject) { - const value = this.state[key]; - const convertedText = convertCamelCase(key); - config = { - title: key, - defaultValue: Array.isArray(value) ? value.join('') : value, - placeholderText: 'Select a ' + convertedText, - options: values[key] || [], - showSearchIcon: false, - onClickItem: this.appendPill, - dropDownStyle: { marginTop: 0 }, - showInputWithContent: Array.isArray(value), - }; - if (key === 'subjects') { - config.options = getArray('absolutePath', values[key]); - config.placeholderText = 'Select a Subject...'; - } else if (key === 'lenses') { - config.placeholderText = 'Select a Lens...'; - config.options = getArray('name', values[key]); - } else if (key.slice(-6) === 'Filter') { // if key ends with Filter - config.defaultValue = ''; // should be pills, not text - config.allOptionsLabel = 'All ' + convertedText.replace(' Filter', '') + 's'; - if (key === 'aspectFilter') { - config.options = getArray('name', values[key]); - config.allOptionsLabel = 'All ' + convertedText.replace(' Filter', '') + ' Tags'; - } else if (key === 'statusFilter') { - config.allOptionsLabel = 'All ' + convertedText.replace(' Filter', '') + 'es'; - } - delete config.placeholderText; - // remove value[i] if not in all appropriate values - let notAllowedTags = []; - for (var i = value.length - 1; i >= 0; i--) { - if (!values[key] || values[key].indexOf(value[i]) < ZERO) { - notAllowedTags.push(value[i]); - } - } - if (notAllowedTags.length) { - // remove from state - const newVals = value.filter((item) => { - return notAllowedTags.indexOf(item) < ZERO; - }); - errorMessage += ' ' + convertedText + ' ' + notAllowedTags.join(', ' ) + ' does not exist.'; - const stateRule = { error: errorMessage }; - stateRule[key] = newVals; - this.setState(stateRule); // this won't be called until end of this method. - } - } - dropdownConfig[key] = config; - } - this.setState({ dropdownConfig: dropdownConfig }); - } - handleRadioButtonClick(event) { - const buttonGroup = findCommonAncestor(event.target, 'slds-button-group'); - const filterType = buttonGroup.title; - const stateRule = {}; - stateRule[filterType] = event.target.textContent.toUpperCase(); - this.setState(stateRule); - } - - showError(error) { - let displayError = error; - // if error message is from the API, parse it for content - if (typeof error === 'object') { - displayError = 'status code: ' + - error.status + '. Error: ' + - JSON.parse(error.response.text).errors[ZERO].message; - } - this.setState({ error: displayError }); - } - closeError() { - this.setState({ error: '' }); - } - onInputValueChange(event) { - const value = event.target.value; - // deep copy state object - let stateRule = {}; - stateRule[event.target.name] = value; - this.setState(stateRule); - } - deletePill(event) { - const pillElem = findCommonAncestor(event.target, 'slds-pill'); - const labelContent = pillElem.getElementsByClassName('slds-pill__label')[ZERO].textContent; - const fieldElem = findCommonAncestor(event.target, 'slds-form-element__control'); - const dropdownTitle = fieldElem.title; - const valueInState = this.state[dropdownTitle]; - let newState = this.state; - // if string, delete key, if array, delete from array - if (Array.isArray(valueInState)) { - const index = valueInState.indexOf(labelContent); - valueInState.splice(index, 1); // remove element from array; - newState[dropdownTitle] = valueInState; - } else if (typeof valueInState === 'string') { - newState[dropdownTitle] = ''; - } - - // add selected option to available options in dropdown - newState.dropdownConfig[dropdownTitle].options.push(labelContent); - - // if there's values in dropdown decrement dorpdown margin top. otherwise set margin top to 0 - newState.dropdownConfig[dropdownTitle].dropDownStyle.marginTop = valueInState.length < 1 ? -5 : - this.state.dropdownConfig[dropdownTitle].dropDownStyle.marginTop -= 25; // TODO: get from DOM eleme - this.setState(newState); - } - appendPill(event) { - const valueToAppend = event.target.textContent; - const fieldElem = findCommonAncestor(event.target, 'slds-form-element__control'); - const dropdownTitle = fieldElem.title; - const valueInState = this.state[dropdownTitle]; - let newState = this.state; - // if string, delete key, if array, delete from array - if (Array.isArray(valueInState)) { - newState[dropdownTitle].push(valueToAppend); - } else if (typeof valueInState === 'string') { - newState[dropdownTitle] = valueToAppend; - } - // remove selected option from available options in dropdown - const arr = newState.dropdownConfig[dropdownTitle].options.filter((elem) => { - return elem != valueToAppend; - }); - newState.dropdownConfig[dropdownTitle].options = arr; - - // if there's no pill, use default margin-top - newState.dropdownConfig[dropdownTitle].dropDownStyle.marginTop = valueInState.length < 1 ? -5 : - this.state.dropdownConfig[dropdownTitle].dropDownStyle.marginTop += 25; // TODO: get from DOM eleme - this.setState(newState); - } - doCreate() { - const { values, sendResource } = this.props; - const postObject = getStateDataOnly(this.state); - if (!postObject.lenses.length) { - this.showError('Please enter a valid lens.'); - } else if (!postObject.subjects.length) { - this.showError('Please enter a valid subject.'); - } else if (!postObject.perspectives.length) { - this.showError('Please enter a name for this perspective.'); - } else { - // check if lens field is uid. if not, need to get uid for lens name - const regexpUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; - if (!regexpUUID.test(postObject.lenses)) { - let lens = values.lenses.filter((lens) => { - return lens.name === postObject.lenses; - }); - if (!lens.length) { - this.showError('Please enter a valid lens name. No lens with name ' + postObject.lenses + ' found'); - } - - postObject.lenses = lens[ZERO].id; - } - // for create perspectives, rename key lenses --> lensId, - // and perspectives --> name. Start with deep copy values obj - postObject.lensId = postObject.lenses; - postObject.rootSubject = postObject.subjects; - postObject.name = postObject.perspectives; - delete postObject.lenses; - delete postObject.subjects; - delete postObject.perspectives; - // go to created perspective page - sendResource('POST', postObject, this.showError); - } - } - render() { - const { values, cancelCreate } = this.props; - let dropdownObj = {}; - const { dropdownConfig } = this.state; - const radioGroupConfig = {}; - const accountIcon = - - Account - ; - - for (let key in dropdownConfig) { - // if no default value, no pill - let pillOutput = ''; - const value = this.state[key]; - if (key.slice(-4) === 'Type') { - radioGroupConfig[key] = { - highlightFirst: value === 'INCLUDE', - title: key, - onClick: this.handleRadioButtonClick, - } - } - // // if display value is array, use multi pill - // // else single pill - if (value.length) { - if (Array.isArray(value)) { - pillOutput = ; - - } else if (typeof value === 'string') { - pillOutput = ; - } - } - dropdownObj[key] = ( - - { pillOutput } - - ); - } - const errorMessage = this.state.error ? : - ' '; - return ( - -
-
-
    -
    -
    -
    -
    -
    - - -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    - { dropdownObj.subjects } -
    -
    -
    - -
    - { dropdownObj.lenses } -
    -
    -
    -
    -
    -
    -
    -

    Filters

    -
    -
    -
    -
    - - - { dropdownObj.aspectTagFilter } -
    -
    - - - { dropdownObj.subjectTagFilter } -
    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - - { dropdownObj.aspectFilter } -
    -
    - - - { dropdownObj.statusFilter } -
    -
    -
    -
    -
    -
-
-
-
- ); - } -} - -CreatePerspective.propTypes = { - cancelCreate: PropTypes.func, - sendResource: PropTypes.func, - values: PropTypes.object, - stateObject: PropTypes.object, -}; - -export default CreatePerspective; diff --git a/view/perspectiveBeta/PerspectiveController.js b/view/perspectiveBeta/PerspectiveController.js deleted file mode 100644 index 862be796e5..0000000000 --- a/view/perspectiveBeta/PerspectiveController.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) 2016, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or - * https://opensource.org/licenses/BSD-3-Clause - */ - -/** - * view/perspective/PerspectiveController.js - * - * Manages perspective page state. - * Passes on data to CreatePerspective - */ -import React, { PropTypes } from 'react'; -import CreatePerspective from './CreatePerspective'; -import Dropdown from '../admin/components/common/Dropdown'; -import request from 'superagent'; -const u = require('../utils'); - -class PerspectiveController extends React.Component { - constructor(props) { - super(props); - this.sendResource = this.sendResource.bind(this); - this.state = { - showCreatePanel: false, - showEditPanel: false, - }; - } - sendResource(verb, formObj, errCallback) { - new Promise((resolve, reject) => { - request(verb, '/v1/perspectives') - .set('Content-Type', 'application/json') - .set('Authorization', u.getCookie('Authorization')) - .send(JSON.stringify(formObj)) - .end((error, response) => { - error ? reject(error) : resolve(response.body); - }); - }).then((res) => { - window.location.href = '/perspectivesBeta/' + res.name; - }) - .catch((err) => { - errCallback(err); - }); - } - goToUrl(event) { - window.location.href = '/perspectivesBeta/' + event.target.textContent; - } - openCreatePanel() { - this.setState({ showCreatePanel: true }); - } - cancelForm() { - this.setState({ showCreatePanel: false }); - } - - render() { - const { values, stateObject } = this.props; - let persNames = []; - if (values && values.perspectives) { - persNames = values.perspectives.map((persObject) => { - return persObject.name; - }); - } - // to hide perspective name on createPerspective modal, - // set perspectives key to value empty - const createPerspectiveVal = JSON.parse(JSON.stringify(stateObject)); - createPerspectiveVal.perspectives = ''; - return ( -
- - { this.state.showCreatePanel && } -
- ); - } -} - -PerspectiveController.PropTypes = { - values: PropTypes.object, - stateObject: PropTypes.object, -}; - -export default PerspectiveController; diff --git a/view/perspectiveBeta/app.js b/view/perspectiveBeta/app.js deleted file mode 100644 index d99ff65ba3..0000000000 --- a/view/perspectiveBeta/app.js +++ /dev/null @@ -1,651 +0,0 @@ -/** - * Copyright (c) 2016, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or - * https://opensource.org/licenses/BSD-3-Clause - */ - -/** - * view/perspective/app.js - * - * When this page is loaded, we call "getPerspectiveNames" to load all the - * perspective names to populate the dropdown. - * If there are no perspectives, we just render the perspective overlay over an - * empty page. - * If there are perspectives, we call "whichPerspective" to figure out which - * perspective to load. - * If it's not in the URL path, either use the DEFAULT_PERSPECTIVE from global - * config OR the first perspective from the list of perspective names - * (alphabetical order), and redirect to the URL using *that* perspective name. - * Once we can identify the perspective in the URL path, we call - * "getPerspective" to load the specified perspective. When we get the - * perspective back from the server, we perform all of this async work in - * parallel: - * (1) call "setupSocketIOClient" to initialize the socket.io client - * (2) call "getHierarchy" to request the hierarchy - * (3) call "getLensPromise" to request the lens library - * (4) call "loadPerspective" to start rendering the perspective-picker - * component - * (5) call "loadExtraStuffForCreatePerspective" to start loading all the extra - * data we'll need for the "CreatePerspective" component - * - * If config var "eventThrottleMillis" > 0, we start a timer to flush the - * realtime event queue on that defined interval. - * - * Whenever we get the response back with the lens, we dispatch the lens.load - * event to the lens. - * - * Whenever we get the response back with the hierarchy, we dispatch the - * lens.hierarchyLoad event to the lens. (If we happen to get the hierarchy - * back *before* the lens, hold onto it, wait for the lens, *then* dispatch the - * lens.hierarchyLoad event *after* the lens.load event.) - * - * Whenever we get all the extra data we need for "CreatePerspective", we - * re-render the perspective-picker component. - */ -import request from 'superagent'; -import React from 'react'; -import ReactDOM from 'react-dom'; -import PerspectiveController from './PerspectiveController'; -const u = require('../utils'); -const eventsQueue = require('./eventsQueue'); -let gotLens = false; -const lensLoadEvent = new CustomEvent('refocus.lens.load'); -let hierarchyLoadEvent; -const pcValues = {}; - -// TODO get rid of this once all the lenses aren't using it -require('./lensUtils'); - -const ZERO = 0; -const ONE = 1; - -const DEBUG_REALTIME = window.location.href.split(/[&\?]/) - .includes('debug=REALTIME'); -const WEBSOCKET_ONLY = window.location.href.split(/[&\?]/) - .includes('protocol=websocket'); - -const REQ_HEADERS = { - Authorization: u.getCookie('Authorization'), - 'X-Requested-With': 'XMLHttpRequest', - Expires: '-1', - 'Cache-Control': 'no-cache,no-store,must-revalidate,max-age=-1,private', -}; -const DEFAULT_ERROR_MESSAGE = 'An unexpected error occurred.'; -const LENS_LIBRARY_REX = /(?:\.([^.]+))?$/; - -// Some API endpoints... -const GET_DEFAULT_PERSPECTIVE = '/v1/globalconfig/DEFAULT_PERSPECTIVE'; -const GET_PERSPECTIVE_NAMES = '/v1/perspectives?fields=name'; - -// Some divs on the perspective page... -const LENS_DIV = document.getElementById('lens'); -const ERROR_INFO_DIV = document.getElementById('errorInfo'); -const PERSPECTIVE_CONTAINER = - document.getElementById('refocus_perspective_dropdown_container'); - -// Note: realtimeEventThrottleMilliseconds is defined in perspective.pug -const eventThrottleMillis = realtimeEventThrottleMilliseconds; - -/** - * Add error message to the errorInfo div in the page. - * - * @param {Object} err - The error object - */ -function handleError(err) { - let msg = DEFAULT_ERROR_MESSAGE; - if (err.response.body.errors[ZERO].description) { - msg = err.response.body.errors[ZERO].description; - } - - ERROR_INFO_DIV.innerHTML = msg; -} // handleError - -/** - * Handle event data, push the event data to the event queue. - * - * @param {String} eventData - Data recieved with event - * @param {String} eventTypeName - Event type - */ -function handleEvent(eventData, eventTypeName) { - const j = JSON.parse(eventData); - if (DEBUG_REALTIME) { - console.log({ // eslint-disable-line no-console - handleEventTimestamp: new Date(), - eventData: j, - }); - } - - eventsQueue.enqueueEvent(eventTypeName, j[eventTypeName]); - if (eventThrottleMillis === ZERO) { - eventsQueue.createAndDispatchLensEvent(eventsQueue.queue, LENS_DIV); - eventsQueue.queue.length = ZERO; - } -} // handleEvent - -/** - * Setup the socket.io client to listen to a namespace, where the namespace is - * named for the root subject of the perspective. - * - * @param {Object} persBody - Perspective object - */ -function setupSocketIOClient(persBody) { - const namespace = u.getNamespaceString(persBody); - - /* - * if the transprotocol is set, initialize the socketio client with - * the transport protocol options. The transProtocol variable is set in - * perspective.pug - */ - const options = {}; - const clientProtocol = transProtocol; - if (clientProtocol) { - /* - * options is used here to set the transport type. For example to only use - * websockets as the transport protocol the options object will be - * { transports: ['websocket'] }. The regex is used to trim the white spaces - * and since clientProtocol is a string of comma seperated values, - * the split function is used to split them out by comma and convert - * it to an array. - */ - options.transports = clientProtocol.replace(/\s*,\s*/g, ',').split(','); - } - - /* - * Note: The "io" variable is defined by the "/socket.io.js" script included - * in perspective.pug. - */ - let socket; - if (WEBSOCKET_ONLY) { - socket = io(namespace, { transports: ['websocket'] }); - } else { - socket = io(namespace, options); - } - - socket.on(eventsQueue.eventType.INTRNL_SUBJ_ADD, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SUBJ_ADD); - }); - socket.on(eventsQueue.eventType.INTRNL_SUBJ_DEL, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SUBJ_DEL); - }); - socket.on(eventsQueue.eventType.INTRNL_SUBJ_UPD, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SUBJ_UPD); - }); - socket.on(eventsQueue.eventType.INTRNL_SMPL_ADD, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SMPL_ADD); - }); - socket.on(eventsQueue.eventType.INTRNL_SMPL_DEL, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SMPL_DEL); - }); - socket.on(eventsQueue.eventType.INTRNL_SMPL_UPD, (data) => { - handleEvent(data, eventsQueue.eventType.INTRNL_SMPL_UPD); - }); -} // setupSocketIOClient - -/** - * Create style tag for lens css file. - * @param {Object} lensResponse Response from lens api - * @param {String} filename name of file in lens library - */ -function injectStyleTag(lensResponse, filename) { - const style = document.createElement('style'); - style.type = 'text/css'; - - const t = document.createTextNode(lensResponse.body.library[filename]); - style.appendChild(t); - const head = document.head || - document.getElementsByTagName('head')[ZERO]; - - if (style.styleSheet) { - style.styleSheet.cssText = lensResponse.body.library[filename]; - } else { - style.appendChild( - document.createTextNode(lensResponse.body.library[filename]) - ); - } - - head.appendChild(style); -} // injectStyleTag - -/** - * Create DOM elements for each of the files in the lens library. - * - * @param {Object} res - Response from lens api call - */ -function handleLibraryFiles(res) { - const lib = res.body.library; - const lensScript = document.createElement('script'); - for (const filename in lib) { - const ext = (LENS_LIBRARY_REX.exec(filename)[ONE] || '').toLowerCase(); - if (filename === 'lens.js') { - lensScript.appendChild(document.createTextNode(lib[filename])); - } else if (ext === 'css') { - injectStyleTag(res, filename); - } else if (ext === 'png' || ext === 'jpg' || ext === 'jpeg') { - const image = new Image(); - image.src = 'data:image/' + ext + ';base64,' + lib[filename]; - document.body.appendChild(image); - } else if (ext === 'js') { - const s = document.createElement('script'); - s.appendChild(document.createTextNode(lib[filename])); - document.body.appendChild(s); - } - } - - /* - * Note: this 'lens.js' script should always get added as the LAST script - * since it may reference things defined in the other scripts. - */ - document.body.appendChild(lensScript); -} // handleLibraryFiles - -/** - * Generate the filter string for the hierarchy API GET. - * - * @param {Object} p - The perspective object - * @returns {String} - The query string created generated based on the - * perspective filters - */ -function getFilterQuery(p) { - let q = '?'; - if (p.aspectFilter && p.aspectFilter.length) { - const sign = p.aspectFilterType === 'INCLUDE' ? '=' : '=-'; - q += 'aspect' + sign + p.aspectFilter.join(); - } - - if (p.aspectTagFilter && p.aspectTagFilter.length) { - if (!q.endsWith('?')) { - q += '&'; - } - - const sign = p.aspectTagFilterType === 'INCLUDE' ? '=' : '=-'; - q += 'aspectTags' + sign + p.aspectTagFilter.join(); - } - - if (p.subjectTagFilter && p.subjectTagFilter.length) { - if (!q.endsWith('?')) { - q += '&'; - } - - const sign = p.subjectTagFilterType === 'INCLUDE' ? '=' : '=-'; - q += 'subjectTags' + sign + p.subjectTagFilter.join(); - } - - if (p.statusFilter) { - if (!q.endsWith('?')) { - q += '&'; - } - - const sign = p.statusFilterType === 'INCLUDE' ? '=' : '=-'; - q += 'status' + sign + p.statusFilter.join(); - } - - return q; -} // getFilterQuery - -/** - * @param {String} name The key to the returned response object - * @param {String} url The url to get from - * param {Function} callback Additional processing with result - * @returns {Promise} For use in chaining. - */ -function getPromiseWithUrl(name, url, callback) { - return new Promise((resolve, reject) => { - request.get(url) - .set(REQ_HEADERS) - .end((error, response) => { - // reject if error is present, otherwise resolve request - if (error) { - document.getElementById('errorInfo').innerHTML += 'Failed to GET ' + - url + '. Make sure the path is valid and the resource is published.'; - reject(error); - } else { - if (callback) { - callback(response); // pass in the complete result - } - const obj = name ? { name: name, res: response.body } : response.body; - resolve(obj); - } - }); - }); -} // getHierarchyData - -/** - * Given the rootSubject, gets subject hierarchy - * and returns a promise to load rootSubject - * - * @param {String} rootSubject The subject to load the hierarchy of - * @param {String} filterString Any filters - * @returns {Promise} which resolves once we receive the hierarchy - */ -function getHierarchy(rootSubject, filterString) { - const apiPath = `/v1/subjects/${rootSubject}/hierarchy` + - (filterString || ''); - return getPromiseWithUrl('rootSubject', apiPath, (res) => { - hierarchyLoadEvent = new CustomEvent('refocus.lens.hierarchyLoad', { - detail: res.body, - }); - - /* - * The order of events matters so only dispatch the hierarchyLoad event if - * we ahve already gotten the lens response back. If hierarchy happens to - * have come back first, then it will be dispatched from getLensPromise. - */ - if (gotLens) { - LENS_DIV.dispatchEvent(hierarchyLoadEvent); - } - }); -} // getHierarchy - -/** - * @param {String} lensNameOrId - * @returns {Promise} which resolves once we receive the lens - */ -function getLensPromise(lensNameOrId) { - const apiPath = `/v1/lenses/${lensNameOrId}`; - return getPromiseWithUrl('lens', apiPath, (res) => { - // inject lens library files in perspective view. - handleLibraryFiles(res); - - // remove spinner and load lens - const spinner = document.getElementById('lens_loading_spinner'); - spinner.parentNode.removeChild(spinner); - - // trigger refocus.lens.load event - gotLens = true; - LENS_DIV.dispatchEvent(lensLoadEvent); - - /* - * The order of events matters so if we happened to have gotten the - * hierarchy *before* the lens, then dispatch the lens.hierarchyLoad event - * now. - */ - if (hierarchyLoadEvent) { - LENS_DIV.dispatchEvent(hierarchyLoadEvent); - } - }); -} // getLensPromise - -/** - * Any last additions to show on create and detail view - * @returns {Object} The object with data from queryParam - */ -function getAllParams() { - const responseObject = {}; - const { rootSubject, lens } = queryParams; // defined in pug file - responseObject.subjects = rootSubject || ''; // single - responseObject.lenses = lens || ''; // single - // multiples, may start with -. If so, use exclude filter - const filterA = [ - 'aspectFilter', 'aspectTagFilter', 'subjectTagFilter', 'statusFilter', - ]; - for (let i = filterA.length - ONE; i >= ZERO; i--) { - const currentVal = queryParams[filterA[i]]; - if (currentVal) { - if (currentVal.slice(ZERO, ONE) === '-') { - responseObject[filterA[i] + 'Type'] = 'EXCLUDE'; - responseObject[filterA[i]] = currentVal.slice(ONE).split(','); - } else { // filter exists, and is an include - responseObject[filterA[i] + 'Type'] = 'INCLUDE'; - responseObject[filterA[i]] = currentVal.split(','); - } - } else { // filter is empty or is not in params - responseObject[filterA[i] + 'Type'] = 'INCLUDE'; - responseObject[filterA[i]] = []; - } - } - - return responseObject; -} // getAllParams - -/** - * Returns array of objects with tags - * @param {Array} array The array of reosurces to get tags from. - * @returns {Object} array of tags - */ -function getTagsFromResources(array) { - // get all tags - const allTags = []; - array.map((obj) => { - if (obj.tags.length) { - allTags.push(...obj.tags); - } - }); - const tagNames = []; - - // get through tags, get all names - allTags.map((tagObj) => { - if (tagNames.indexOf(tagObj.toLowerCase()) === -1) { - tagNames.push(tagObj); - } - }); - return tagNames; -} - -function getPublishedObjectsbyField(array, field) { - return array.filter((obj) => - obj.isPublished).map((obj) => obj[field]) -} - -/** - * @param {Object} perspective An object - */ -function loadPerspective(perspective, params) { - pcValues.name = perspective.name; - const stateObject = Object.assign( - { perspectives: perspective ? perspective.name : '' }, - params - ); - getPromiseWithUrl('perspectives', '/v1/perspectives') - .then((values) => { - pcValues.perspectives = values.res; - loadController(pcValues, stateObject); - }); -} // loadPerspective - -/** - * @param {Array} promisesArr An array of AJAX GET promises. - * @param {boolean} getRoot Get all subjects, set first published subject as - * rootSubject - * @param {boolean} getLens Get all lenses, use the first published lens - */ -function loadExtraStuffForCreatePerspective(perspective, params, promisesArr, - getRoot, getLens) { - pcValues.name = perspective.name; - const stateObject = Object.assign( - { perspectives: perspective ? perspective.name : '' }, - params - ); - const pArr = promisesArr || []; - - const getAllSubjectsPromise = getPromiseWithUrl('subjects', '/v1/subjects'); - let subjectPromise; - if (getRoot) { - subjectPromise = getAllSubjectsPromise.then((val) => { - pcValues.subjects = val.res; - - // get the first published subject, sorted in alphabetical order by - // absolutePath - const rootSubject = getPublishedObjectsbyField(val.res, 'absolutePath') - .sort()[ZERO]; - return getHierarchy(rootSubject); - }); - } else { - subjectPromise = getAllSubjectsPromise; - } - - pArr.push(subjectPromise); - - const getAllLensesPromise = getPromiseWithUrl('lenses', '/v1/lenses'); - let lensPromise; - if (getLens) { - lensPromise = getAllLensesPromise.then((val) => { - pcValues.lenses = val.res; - - // get the first published lens, sorted in alphabetical order by name - const lens = getPublishedObjectsbyField(val.res, 'name').sort()[ZERO]; - return getLensPromise(lens); - }); - } else { - lensPromise = getAllLensesPromise; - } - - pArr.push(lensPromise); - - pArr.push(getPromiseWithUrl('aspectFilter', '/v1/aspects')); - - // TODO change this to GET from API, after its implemented; - const statusFilter = [ - 'Critical', - 'Invalid', - 'Timeout', - 'Warning', - 'Info', - 'OK', - ]; - Promise.all(pArr).then((values) => { - for (let i = values.length - ONE; i >= ZERO; i--) { - pcValues[values[i].name] = values[i].res; - } - - pcValues.statusFilter = statusFilter; - pcValues.aspectTags = getTagsFromResources(pcValues.aspectFilter); - pcValues.subjectTagFilter = getTagsFromResources(pcValues.subjects); - loadController(pcValues, stateObject); - }); -} // loadExtraStuffForCreatePerspective - -function handleUnnamedPerspective() { - const promisesArr = []; - // fill in missing info from params - const { rootSubject, lens } = queryParams; - let getRoot = false; - let getLens = false; - - // if no loadObj.rootSubject, rootSubject is the first subject in GET subject - if (rootSubject) { - promisesArr.push(getHierarchy(rootSubject)); - } else if (!rootSubject) { // no queryParam.rootSubject: need to pass it to loadPerspective - getRoot = true; - } - - if (lens) { - promisesArr.push(getLensPromise(lens)); - } else if (!lens) { - getLens = true; - } - - const params = getAllParams(); - loadPerspective(null, params); - loadExtraStuffForCreatePerspective(null, params, promisesArr, - getRoot, getLens); -} // handleUnnamedPerspective - -/** - * Retrieves the specified perspective, initiates loading lens and hierarchy. - * - * @param {String} perspNameOrId - The name or id of the perspective - */ -function getPerspective(perspNameOrId) { - getPromiseWithUrl('perspective', `/v1/perspectives/${perspNameOrId}`) - .then((val) => { - setupSocketIOClient(val.res); - const { lensId, name, rootSubject } = val.res; - getHierarchy(rootSubject, getFilterQuery(val.res)); - getLensPromise(lensId); - const p = { name, rootSubject, lensId }; - const params = getAllParams(); - loadPerspective(p, params); - loadExtraStuffForCreatePerspective(p, params); - }) - .catch((error) => { - document.getElementById('errorInfo').innerHTML += error; - }); -} - -/** - * Figure out which perspective to load. If it's in the URL path, load that - * one. - * If it's not in the URL path, either use the DEFAULT_PERSPECTIVE from global - * config OR the first perspective from the list of perspective names - * (alphabetical order), and redirect to the URL using that perspective name. - * - * @param {Array} pnames - Array of perspective names - */ -function whichPerspective(pnames) { - let h = window.location.href; - if (!h.endsWith('/')) { - h += '/'; - } - - let hsplit = h.split('/'); - hsplit.pop(); - let p = hsplit.pop(); - if (p && p !== 'perspectivesBeta') { - getPerspective(p); - } else { - request.get(GET_DEFAULT_PERSPECTIVE) - .set(REQ_HEADERS) - .end((err, res) => { - if (err) { - p = pnames.shift(); // Grab the first one from the list - } else { - p = res.body.value; - } - - // Add the perspective name to the URL and redirect. - window.location.href = h + p; - }); - } -} // whichPerspective - -/** - * Load all the perspective names to populate the dropdown. If there are no - * perspectives, just render the perspective overlay over an empty page. - */ -function getPerspectiveNames() { - request.get(GET_PERSPECTIVE_NAMES) - .set(REQ_HEADERS) - .end((err, res) => { - const pnames = []; - if (err) { - handleError(err); - } else { - if (res.body.length === 0) { - loadController({}, {}); - } else { - for (let i = 0; i < res.body.length; i++) { - pnames.push(res.body[i].name); - } - - pnames.sort(); - whichPerspective(pnames); - } - } - }); -} // getPerspectiveNames - -window.onload = () => { - getPerspectiveNames(); -}; - -if (eventThrottleMillis !== ZERO) { - eventsQueue.scheduleFlushQueue(LENS_DIV, eventThrottleMillis); -} - -/** - * Passes data on to Controller to pass onto renderers. - * - * @param {Object} values Data returned from AJAX. - * @param {Object} stateObject Data from queryParams. - */ -function loadController(values, stateObject) { - ReactDOM.render( - , - PERSPECTIVE_CONTAINER - ); -} diff --git a/view/perspectiveBeta/eventsQueue.js b/view/perspectiveBeta/eventsQueue.js deleted file mode 100644 index 3482a83908..0000000000 --- a/view/perspectiveBeta/eventsQueue.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2016, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or - * https://opensource.org/licenses/BSD-3-Clause - */ - - -'use strict'; -const ZERO = 0; - -let queue = []; - -const eventType = { - INTRNL_SUBJ_ADD: 'refocus.internal.realtime.subject.add', - INTRNL_SUBJ_DEL: 'refocus.internal.realtime.subject.remove', - INTRNL_SUBJ_UPD: 'refocus.internal.realtime.subject.update', - INTRNL_SMPL_ADD: 'refocus.internal.realtime.sample.add', - INTRNL_SMPL_DEL: 'refocus.internal.realtime.sample.remove', - INTRNL_SMPL_UPD: 'refocus.internal.realtime.sample.update', - LENS_CHANGE: 'refocus.lens.realtime.change', -}; - -/** - * Push event data to corresponding event queue. - * @param {String} eventName - Event type name. - * @param {Object} eventData - JSON Data recieved with event. - */ -function enqueueEvent(eventName, eventData) { - if (eventName === eventType.INTRNL_SUBJ_ADD) { - queue.push({ 'subject.add': eventData }); - } else if (eventName === eventType.INTRNL_SUBJ_DEL) { - queue.push({ 'subject.remove': eventData }); - } else if (eventName === eventType.INTRNL_SUBJ_UPD) { - queue.push({ 'subject.update': eventData }); - } else if (eventName === eventType.INTRNL_SMPL_ADD) { - queue.push({ 'sample.add': eventData }); - } else if (eventName === eventType.INTRNL_SMPL_DEL) { - queue.push({ 'sample.remove': eventData }); - } else if (eventName === eventType.INTRNL_SMPL_UPD) { - queue.push({ 'sample.update': eventData }); - } -} - -/** - * Create and dispatch custom event from event queue and empty the same for - * another batch of events. - * @param {Object} queueToFlush - event queue to flush - */ -function createAndDispatchLensEvent(queueToFlush, lensElement) { - if (queueToFlush !== undefined && queueToFlush.length > ZERO) { - const evt = new CustomEvent( - eventType.LENS_CHANGE, { detail: queueToFlush } - ); - lensElement.dispatchEvent(evt); - } -} - -/** - * Clone object to handle race conditions. - * @param {Object} obj - Object to copy - * @returns {Object} copy - New copied object - */ -function clone(obj) { - let copy; - - // Handle simple types, and null or undefined - if (obj === null || typeof obj !== 'object') { - return obj; - } - - // Handle Date - if (obj instanceof Date) { - copy = new Date(); - copy.setTime(obj.getTime()); - return copy; - } - - // Handle Array - if (obj instanceof Array) { - copy = []; - for (let i = 0, len = obj.length; i < len; i++) { - copy[i] = clone(obj[i]); - } - - return copy; - } - - // Handle Object - if (obj instanceof Object) { - copy = {}; - for (const attr in obj) { - if (obj.hasOwnProperty(attr)) { - copy[attr] = clone(obj[attr]); - } - } - - return copy; - } - - throw new Error("Unable to copy obj! Its type isn't supported."); -} - -/** - * schedule flushing of queue after given time interval. - * @param {Object} lensElement - document lens element - */ -function scheduleFlushQueue(lensElement, realtimeEventThrottleMilliseconds) { - // clone events queue, initialize events queue, flush events queue copy. - setInterval(() => { - const queueCopy = clone(queue); - queue.length = 0; - createAndDispatchLensEvent(queueCopy, lensElement); - }, realtimeEventThrottleMilliseconds); -} - -module.exports = { - enqueueEvent, - scheduleFlushQueue, - eventType, - queue, - clone, - createAndDispatchLensEvent, -}; diff --git a/view/perspectiveBeta/lensUtils.js b/view/perspectiveBeta/lensUtils.js deleted file mode 100644 index 29ef16db07..0000000000 --- a/view/perspectiveBeta/lensUtils.js +++ /dev/null @@ -1,338 +0,0 @@ -/** - * Copyright (c) 2016, salesforce.com, inc. - * All rights reserved. - * Licensed under the BSD 3-Clause license. - * For full license text, see LICENSE.txt file in the repo root or - * https://opensource.org/licenses/BSD-3-Clause - */ - -/** - * DO NOT MODIFY THIS FILE. WE CANNOT GUARANTEE THAT YOUR LENS WILL WORK ONCE - * IT IS INSTALLED INTO A REFOCUS INSTALLATION. - */ -const lensUtils = { - badStatusArrayWorstToBest: - ['Critical', 'Invalid', 'Timeout', 'Warning', 'Info'], - statusArrayWorstToBest: - ['Critical', 'Invalid', 'Timeout', 'Warning', 'Info', 'OK'], - statuses: { - Critical: 'Critical', - Invalid: 'Invalid', - Timeout: 'Timeout', - Warning: 'Warning', - Info: 'Info', - OK: 'OK', - }, - copyObject(obj) { - return JSON.parse(JSON.stringify(obj)); - }, - /* - * Derive Functions (use with lensUtils.transform) - */ - derive: { - subject: { - /* - * Add a "statusChangedAt" attribute to a subject with samples, - * assigning its value to be the most recent "statusChangedAt" time of - * all its samples. - */ - mostRecentStatusChangedAt(subject) { - if (subject.samples && Object.keys(subject.samples).length) { - subject.statusChangedAt = Object.keys(subject.samples) - .map((s) => subject.samples[s]) - .sort(lensUtils.sort.sample.statusChangedAtDescending) - [0].statusChangedAt; - } - }, - /* - * Add a "statusCounts" attribute to a subject with samples, counting - * the number of samples by status. - */ - statusCounts(subject) { - if (subject.samples && Object.keys(subject.samples).length) { - subject.statusCounts = {}; - lensUtils.statusArrayWorstToBest.forEach((s) => subject.statusCounts[s] = 0); - Object.keys(subject.samples).forEach((s) => { - const sam = subject.samples[s]; - subject.statusCounts[sam.status]++; - }); - } - }, - /* - * Add a "status" attribute to a subject with samples, assigning its - * value to be the worst status of all its samples. - */ - worstStatus(subject) { - if (subject.samples && Object.keys(subject.samples).length) { - subject.status = Object.keys(subject.samples) - .map((s) => subject.samples[s]) - .sort(lensUtils.sort.sample.statusWorstToBestNameAscending) - [0].status; - } - }, - /* - * Add a "status" attribute to a subject with samples, assigning its - * value to be the worst status of all its samples, and add a - * "descendentStatus" attribute to a subject with children, assigning - * its value to be the worst status of all its children. - */ - worstStatusAndDescendentStatus(subject) { - if (subject.samples && Object.keys(subject.samples).length) { - subject.status = Object.keys(subject.samples) - .map((s) => subject.samples[s]) - .sort(lensUtils.sort.sample.statusWorstToBestNameAscending) - [0].status; - } - if (subject.children) { - const descendents = subject.children.sort((a, b) => { - const asorter = lensUtils.statusArrayWorstToBest - .indexOf(a.status || a.descendentStatus) + '#' + a.absolutePath; - const bsorter = lensUtils.statusArrayWorstToBest - .indexOf(b.status || b.descendentStatus) + '#' + b.absolutePath; - return lensUtils.sort.ascending(asorter, bsorter); - }); - subject.descendentStatus = descendents[0].status; - } - }, - }, - }, - /* - * Constants to help shorten your references to the refocus.lens.* events. - */ - evt: { - hLoad: 'refocus.lens.hierarchyLoad', - load: 'refocus.lens.load', - sampAdd: 'refocus.lens.realtime.sample.add', - sampRem: 'refocus.lens.realtime.sample.remove', - sampUpd: 'refocus.lens.realtime.sample.update', - subjAdd: 'refocus.lens.realtime.subject.add', - subjRem: 'refocus.lens.realtime.subject.remove', - subjUpd: 'refocus.lens.realtime.subject.update', - }, - /* - * Filter Functions - */ - filter: { - /* - * Sample Filter Functions - */ - sample: { - /* - * Filters a sample array returning only those sample with status != - * "OK". - */ - notOK(sample, idx, arr) { - return sample.status !== statuses.OK; - }, - /* - * Filters a sample array returning only those sample with status = - * "OK". - */ - onlyOK(sample, idx, arr) { - return sample.status === statuses.OK; - }, - }, - /* - * Subjet Filter Functions - */ - subject: { - /* - * Filters a subject array returning only those subjects which have one - * or more samples, where at least one of the subjects' samples has a - * status != "OK". - */ - notOK(subject, idx, arr) { - const keys = Object.keys(subject.samples); - if (keys.length === 0) { - return false; - } - - keys.forEach((key) => { - const st = subject.samples[key].status; - if (badStatusArrayWorstToBest.indexOf(st) >= 0) { - return true; - } - }); - - return false; - }, - /* - * Filters a subject array returning only those subjects which have one - * or more samples, where *all* of the subjects' samples have status = - * "OK". - */ - allOK(subject, idx, arr) { - const keys = Object.keys(subject.samples); - if (keys.length === 0) { - return false; - } - - keys.forEach((key) => { - if (subject.samples[key].status !== statuses.OK) { - return false; - } - }); - - return true; - }, - }, - }, - /* - * Return a compact description of the time elapsed over the number of - * milliseconds provided. The result always starts with an integer which - * is followed by a single letter representing the unit: "s" for second, "m" - * for minute, "h" for hour, "d" for day and "w" for week. - * - * @param {Integer} ms - The number of milliseconds - * @returns {String} - A compact description of the time elapsed - */ - formatElapsed(ms) { - const ONE_SECOND_IN_MILLIS = 1000; - const ONE_MINUTE_IN_SECONDS = 60; - const ONE_HOUR_IN_MINUTES = 60; - const ONE_DAY_IN_HOURS = 24; - const ONE_WEEK_IN_DAYS = 7; - const seconds = Math.floor(ms / ONE_SECOND_IN_MILLIS); - - if (seconds < ONE_MINUTE_IN_SECONDS) { - return `${seconds}s`; - } - - const minutes = Math.floor(seconds / ONE_MINUTE_IN_SECONDS); - if (minutes < ONE_HOUR_IN_MINUTES) { - return `${minutes}m`; - } - - const hours = Math.floor(minutes / ONE_HOUR_IN_MINUTES); - if (hours < ONE_DAY_IN_HOURS) { - return `${hours}h`; - } - - const days = Math.floor(hours / ONE_DAY_IN_HOURS); - if (days < ONE_WEEK_IN_DAYS) { - return `${days}d`; - } - - const weeks = Math.floor(days / ONE_WEEK_IN_DAYS); - return `${weeks}w`; - }, - /* - * Comparator Functions - */ - sort: { - ascending(a, b) { - if (a > b) { - return 1; - } - - if (a < b) { - return -1; - } - - return 0; - }, - descending(a, b) { - if (a > b) { - return -1; - } - - if (a < b) { - return 1; - } - - return 0; - }, - /* - * Sample Comparator Functions - */ - sample: { - /* - * Sorts an array of samples in ascending order by name. - */ - nameAscending(a, b) { - return lensUtils.sort.ascending(a.name, b.name); - }, - /* - * Sorts an array of samples in by status (worst to best) then within - * status in ascending order by sample name. - */ - statusWorstToBestNameAscending(a, b) { - const asorter = - lensUtils.statusArrayWorstToBest.indexOf(a.status) + '#' + a.name; - const bsorter = - lensUtils.statusArrayWorstToBest.indexOf(b.status) + '#' + b.name; - return lensUtils.sort.ascending(asorter, bsorter); - }, - /* - * Sorts an array of samples in descending order by statusChangedAt. - */ - statusChangedAtDescending(a, b) { - return lensUtils.sort.descending( - a.statusChangedAt, b.statusChangedAt); - }, - }, - /* - * Subject Comparator Functions - */ - subject: { - /* - * Sorts an array of subjects in ascending order by absolutePath. - */ - absolutePathAscending(a, b) { - return lensUtils.sort.ascending(a.absolutePath, b.absolutePath); - }, - }, - /* - * Tag Comparator Functions - */ - tag: { - nameAscending(a, b) { - return lensUtils.sort.ascending(a.name, b.name); - }, - } - }, - /* - * Returns a copy of the hierarchy with new derived fields based on the - * attribute derivation callback functions provided. - */ - transform(subj) { - function recurse(subject) { - if (subject.children) { - subject.children.forEach((child) => { - recurse(child); - }); - } - deriveFns.forEach((fn) => { - fn(subject); - }); - } - - const h = lensUtils.copyObject(subj); - let deriveFns = []; - for (let i = 1; i < arguments.length; i++) { - deriveFns.push(arguments[i]); - } - - recurse(h); - return h; - }, - /* - * Recursively traverse the hierarchy (depth-first traversal) and execute - * the callback function you provide for every subject in the hierarchy. If - * you do not provide a comparator function argument, subject siblings will - * be sorted using the sort function - * lensUtils.sort.subject.absolutePathAscending. The callback function - * argument takes one argument, a reference to the current subject. - * Best practice: do not modify the subject from your callback function. - */ - traverseSubjects(subj, callback, comparator) { - callback(subj); - if (subj.children) { - subj.children - .sort(comparator || lensUtils.sort.subject.absolutePathAscending) - .forEach((child) => { - lensUtils.traverseSubjects(child, callback, comparator); - }); - } - }, -}; diff --git a/view/perspectiveBeta/perspective.pug b/view/perspectiveBeta/perspective.pug deleted file mode 100644 index e19a8cc445..0000000000 --- a/view/perspectiveBeta/perspective.pug +++ /dev/null @@ -1,38 +0,0 @@ -// - Copyright (c) 2016, salesforce.com, inc. - All rights reserved. - Licensed under the BSD 3-Clause license. - For full license text, see LICENSE.txt file in the repo root or - https://opensource.org/licenses/BSD-3-Clause - - -doctype html -html(lang = 'en') - head - link(rel='shortcut icon', href='favicon.ico') - link(rel='stylesheet', type='text/css', href='../static/css/salesforce-lightning-design-system.min.css') - link(rel='stylesheet' type='text/css' href='../static/css/perspective.css') - - body - #refocus_perspective_dropdown_container - #create_perspective_container - label#errorInfo - div#lens - div#lens_loading_spinner - .slds-spinner_container - .slds-spinner--brand.slds-spinner.slds-spinner--medium(role='alert') - span.slds-assistive-text Loading - | - .slds-spinner__dot-a - | - .slds-spinner__dot-b - - script(src='/static/socket.io.js') - script. - var realtimeEventThrottleMilliseconds = #{eventThrottle}; - script. - var transProtocol = '#{transportProtocol}'; - script - if queryParams - | var queryParams = !{queryParams} - script(src='/static/perspectiveBeta/app.js')