From a38f3d6bb17a478d08016e49d6c2a667ac483d4a Mon Sep 17 00:00:00 2001 From: Arul Date: Mon, 15 Jan 2018 11:04:25 -0500 Subject: [PATCH] Introduction of external URLs Introduce external URLs to get AAI UI to show a specific graph Change-Id: Ibc10dab32540f2c8347df1be535e48b88308b9ec Signed-off-by: Arul Issue-ID: AAI-626 --- package.json | 2 +- .../images/launchExternalResource/ann-icon.svg | 35 ++++ resources/scss/_components.scss | 3 + .../_launchExternalResource.scss | 15 ++ scripts/elasticsearch/auditdataConfigSettings.json | 82 +++++++++ scripts/elasticsearch/commands.txt | 205 +++++++++++++++++++++ .../prepareElasticSearchBulkImport.pl | 41 +++++ scripts/elasticsearch/sampleAuditLog5.csv | 12 ++ src/app/MainScreenHeader.jsx | 60 +++++- src/app/MainScreenWrapperReducer.js | 13 ++ src/app/contextHandler/ContextHandlerActions.js | 78 +++++++- src/app/contextHandler/ContextHandlerConstants.js | 15 +- src/app/networking/NetworkCalls.js | 27 ++- src/app/tierSupport/TierSupport.jsx | 3 +- src/app/tierSupport/TierSupportReducer.js | 3 + .../LaunchExternalResource.jsx | 70 +++++++ .../LaunchExternalResourceReducer.js | 76 ++++++++ .../selectedNodeDetails/SelectedNodeDetails.jsx | 4 +- .../SelectedNodeDetailsReducer.js | 4 +- .../graph/ForceDirectedGraph.jsx | 22 ++- 20 files changed, 739 insertions(+), 31 deletions(-) create mode 100644 resources/images/launchExternalResource/ann-icon.svg create mode 100644 resources/scss/components/launchExternalResource/_launchExternalResource.scss create mode 100644 scripts/elasticsearch/auditdataConfigSettings.json create mode 100644 scripts/elasticsearch/commands.txt create mode 100644 scripts/elasticsearch/prepareElasticSearchBulkImport.pl create mode 100644 scripts/elasticsearch/sampleAuditLog5.csv create mode 100644 src/app/tierSupport/launchExternalResource/LaunchExternalResource.jsx create mode 100644 src/app/tierSupport/launchExternalResource/LaunchExternalResourceReducer.js diff --git a/package.json b/package.json index 94e4b03..e93be47 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "collapsible-sliding-panel": "1.0.0", "core-js": "^2.4.0", "crypto-js": "^3.1.9-1", - "d3": "^4.9.1", + "d3": "^4.12.0", "es6-promise": "^3.2.1", "filter-bar-utils": "1.0.0", "jquery": "^2.2.2", diff --git a/resources/images/launchExternalResource/ann-icon.svg b/resources/images/launchExternalResource/ann-icon.svg new file mode 100644 index 0000000..17cb912 --- /dev/null +++ b/resources/images/launchExternalResource/ann-icon.svg @@ -0,0 +1,35 @@ + + + + + + + + + diff --git a/resources/scss/_components.scss b/resources/scss/_components.scss index 882e1cc..38f9189 100644 --- a/resources/scss/_components.scss +++ b/resources/scss/_components.scss @@ -21,6 +21,7 @@ * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + @import "components/validationForm"; @import "components/slidePanel"; @import "components/toggleInput"; @@ -31,6 +32,7 @@ @import "components/titledComponent"; @import "components/containerPanel"; @import "components/filterBar"; +@import "components/launchExternalResource/launchExternalResource"; %noselect { -webkit-touch-callout: none; @@ -73,3 +75,4 @@ } } + diff --git a/resources/scss/components/launchExternalResource/_launchExternalResource.scss b/resources/scss/components/launchExternalResource/_launchExternalResource.scss new file mode 100644 index 0000000..8feeb1f --- /dev/null +++ b/resources/scss/components/launchExternalResource/_launchExternalResource.scss @@ -0,0 +1,15 @@ +.launch-external-resource-button { + color: transparent; + background-color: transparent; + background-image: url($icons-folder-path + '/../launchExternalResource/ann-icon.svg'); + font-size: 20px; + padding: 2px 5px; + margin-left: 10px; + border: none; + vertical-align: text-bottom; + height: 30px; + width: 30px; + background-repeat: no-repeat; +} + +.launch-in-context {} diff --git a/scripts/elasticsearch/auditdataConfigSettings.json b/scripts/elasticsearch/auditdataConfigSettings.json new file mode 100644 index 0000000..912295b --- /dev/null +++ b/scripts/elasticsearch/auditdataConfigSettings.json @@ -0,0 +1,82 @@ +{ + "mappings": { + "default": { + "properties": { + "entityId": { + "type": "nested" + }, + "entityLink": { + "type": "string" + }, + "entityType": { + "type": "string", + "index": "not_analyzed" + }, + "resourceVersion": { + "type": "string" + }, + "validationId": { + "type": "string", + "index": "not_analyzed" + }, + "validationTimestamp": { + "type": "date", + "format": "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy||yyyyMMdd'T'HHmmssZ" + }, + "violations": { + "type": "nested", + "properties": { + "category": { + "type": "string", + "index": "not_analyzed" + }, + "errorMessage": { + "type": "string" + }, + "modelName": { + "type": "string" + }, + "severity": { + "type": "string", + "index": "not_analyzed" + }, + "validationRule": { + "type": "string" + }, + "violationDetails": { + "type": "nested", + "properties": { + "MISSING_REL": { + "type": "string" + }, + "entityId": { + "type": "nested" + }, + "entityType": { + "type": "string", + "index": "not_analyzed" + }, + "modelName": { + "type": "string" + } + } + }, + "violationId": { + "type": "string", + "index": "not_analyzed" + }, + "violationTimestamp": { + "type": "date", + "format": "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy||yyyyMMdd'T'HHmmssZ" + }, + "violationType": { + "type": "string", + "index": "not_analyzed" + } + } + } + } + } + + } +} diff --git a/scripts/elasticsearch/commands.txt b/scripts/elasticsearch/commands.txt new file mode 100644 index 0000000..2e2284a --- /dev/null +++ b/scripts/elasticsearch/commands.txt @@ -0,0 +1,205 @@ + +ElasticSearch Configuration: +============================ + +1. Verify that your ElasticSearch instance configuation contains the following cors parameters in the elasticsearch.yaml file. The CORS + workaround has proven to work for Firefox, Chrome, and Opera. + +http.cors.enabled: true +http.cors.allow-origin: "/.*/" +http.cors.allow-headers: ["X-Requested-With", "Content-Type", "Content-Length"] +http.cors.allow-credentials: true + +2. Start up Elastic Search by running running the elasticsearch.bat in the elasticsearch 2.3.1 bin folder. + +If you want to do a cleanup before running these instructions, then you can execute this +optional command which will destroy the index settings and data. + +curl -XDELETE "http://localhost:9200/auditdata?pretty" + +Expected Result: +{ + "acknowledged" : true +} + + +ElasticSearch Index Setup and Bulk Load Instructions: +===================================================== + +1. Configure Elastic Search Index + +curl -XPUT localhost:9200/auditdata?pretty --data-binary @auditdataConfigSettings.json + +Expected Result: +{ + "acknowledged" : true +} + +2. Prepare elastic search bulk import: + +prepareElasticSearchBulkImport.pl sampleAuditLog5.csv auditBulkLoad.json + +curl -XPUT localhost:9200/_bulk?pretty --data-binary @auditBulkLoad.json + +At the top of the output verify if there any import errors by looking at the errors field. + +Expected Result: +{ + "took" : 103, + "errors" : false, <-------- this field is important. if true you need to look at the output, otherwise you can ignore it + "items" : [ { + "create" : { + "_index" : "auditdata", + "_type" : "everything", + "_id" : "AVXN0g6Ve6sNoEtMKGxy", + "_version" : 1, + "_shards" : { + "total" : 2, + "successful" : 1, + "failed" : 0 + }, + "status" : 201 + } + + + +3. Verify that auditdata index contains data + +curl -XGET http://localhost:9200/_cat/indices?v + +Expected Result: + +health status index pri rep docs.count docs.deleted store.size pri.store.size +yellow open auditdata 5 1 250 0 85.2kb 85.2kb + +4. Verify configuration of elastic search index parameter settings: + +curl -XGET http://localhost:9200/auditdata?pretty + +Expected Result: +{ + "auditdata" : { + "aliases" : { }, + "mappings" : { + "everything" : { + "properties" : { + "date" : { + "type" : "date", + "format" : "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy" + }, + "entityKey" : { + "type" : "string" + }, + "entityType" : { + "type" : "string" + }, + "message" : { + "type" : "string" + }, + "severity" : { + "type" : "string" + }, + "status" : { + "type" : "string" + } + } + }, + "auditdata" : { + "properties" : { + "date" : { + "type" : "date", + "format" : "MMM d y HH:m:s||dd-MM-yyyy HH:mm:ss||yyyy-MM-dd'T'HH:mm:ss.SSSZZ||MM/dd/yyyy" + }, + "entityKey" : { + "type" : "string" + }, + "entityType" : { + "type" : "string" + }, + "message" : { + "type" : "string" + }, + "severity" : { + "type" : "string" + }, + "status" : { + "type" : "string" + } + } + } + }, + "settings" : { + "index" : { + "creation_date" : "1468250773569", + "number_of_shards" : "5", + "number_of_replicas" : "1", + "uuid" : "IgJe5PZyQmSfCLzuxm3Ulw", + "version" : { + "created" : "2030199" + } + } + }, + "warmers" : { } + } +} + +5. Test that you can retrieve data: + +curl -XGET "http://localhost:9200/auditdata/_search/?size=3&pretty" + +Expected Result: +{ + "took" : 8, + "timed_out" : false, + "_shards" : { + "total" : 5, + "successful" : 5, + "failed" : 0 + }, + "hits" : { + "total" : 250, + "max_score" : 1.0, + "hits" : [ { + "_index" : "auditdata", + "_type" : "everything", + "_id" : "AVXakQNNe6sNoEtMKG1y", + "_score" : 1.0, + "_source" : { + "date" : "May 26 2016 15:24:13", + "severity" : "CRITICAL", + "entityType" : "vpls-pe", + "entityKey" : "sfcca303vr1", + "status" : "prov-status=[ACTIVE]", + "message" : "Invalid prov-status value. Must have a value not equal to ACTIVE/active." + } + },{ + "_index" : "auditdata", + "_type" : "everything", + "_id" : "AVXakQNNe6sNoEtMKG13", + "_score" : 1.0, + "_source" : { + "date" : "May 26 2016 15:24:13", + "severity" : "CRITICAL", + "entityType" : "vpe", + "entityKey" : "VPESAT-ashah401me6", + "status" : "prov-status=[NULL]", + "message" : "Invalid prov-status value. Must have a value not equal to ACTIVE/active." + } + },{ + "_index" : "auditdata", + "_type" : "everything", + "_id" : "AVXakQNNe6sNoEtMKG17", + "_score" : 1.0, + "_source" : { + "date" : "May 26 2016 15:24:13", + "severity" : "CRITICAL", + "entityType" : "vpe", + "entityKey" : "VPESAT-eshah401me6", + "status" : "prov-status=[]", + "message" : "Invalid prov-status value. Must have a value not equal to ACTIVE/active." + } + } ] + } +} + + diff --git a/scripts/elasticsearch/prepareElasticSearchBulkImport.pl b/scripts/elasticsearch/prepareElasticSearchBulkImport.pl new file mode 100644 index 0000000..e3fc690 --- /dev/null +++ b/scripts/elasticsearch/prepareElasticSearchBulkImport.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +my $filename = $ARGV[0]; +my $outputfile= $ARGV[1]; + +open my $fh_input, '<', $filename or die "Cannot open $filename: $!"; +open my $fh_output, '>', $outputfile or die "Cannot open $outputfile: $!"; + +while ( my $line = <$fh_input> ) { + chomp ($line); + + if ( $line =~ /(.*)(\".*\")(.*)/ ) { + + # we have seen examples of the status field containing quoted comma-delimited + # strings which is messing up parsing of the record data which is supposed to be + # comma-separated at the field level. This little block converts sections of + # this type of data into a single-quoted-string with a semi-colon delimiter instead. + + my $beforeBadStr = $1; + my $badStr = $2; + my $afterBadStr = $3; + + $badStr =~ s/,/;/g; + $badStr =~ s/"/'/g; + + $line = $beforeBadStr . $badStr . $afterBadStr ; + + } + + my @row = split(",", $line); + print $fh_output "{\"index\":{\"_index\":\"auditdata\",\"_type\":\"default\"}\n"; + print $fh_output "{\"entityType\": \"$row[0]\", \"errorMessage\": \"$row[1]\", \"violations\": [{ \"violationTimestamp\": \"$row[2]\", \"severity\": \"$row[3]\", \"violationType\": \"$row[4]\", \"violationDetails\": { \"MISSING_REL\": \"$row[5]\", \"entityType\": \"$row[6]\", \"entityId\": { \"vdc-id\": \"$row[7]\" } }, \"category\": \"$row[8]\" }, { \"violationTimestamp\": \"$row[9]\", \"severity\": \"$row[10]\", \"violationType\": \"$row[11]\", \"violationDetails\": { \"MISSING_REL\": \"$row[12]\", \"entityType\": \"$row[13]\", \"entityId\": { \"vdc-id\": \"$row[14]\" } }, \"category\": \"$row[15]\" }]}\n"; + +} + +close($fh_input); +close($fh_output); + diff --git a/scripts/elasticsearch/sampleAuditLog5.csv b/scripts/elasticsearch/sampleAuditLog5.csv new file mode 100644 index 0000000..deb4c6d --- /dev/null +++ b/scripts/elasticsearch/sampleAuditLog5.csv @@ -0,0 +1,12 @@ +virtual-data-center,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161219T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],newvce,vnf-id-team-auto-11,INVALID OBJ1,20161219T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],virtual-data-center,vnf-id-team-auto-112,INVALID OBJ1 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161219T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],newvce,vnf-id-team-auto-11,INVALID OBJ2,20161219T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],newvce2,vnf-id-team-auto-112,INVALID OBJ2 +virtual-data-center,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161219T091529Z,MINOR,Rule3,prov-status=[ACTIVE],virtual-data-center,vnf-id-team-auto-11,INVALID OBJ3,20161219T091529Z,MINOR,Rule3,prov-status=[ACTIVE],newvce2,vnf-id-team-auto-112,INVALID OBJ3 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161218T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],vserver,vnf-id-team-auto-11,INVALID OBJ4,20161218T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],vserver2,vnf-id-team-auto-112,INVALID OBJ4 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161218T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],vserver,vnf-id-team-auto-11,INVALID OBJ5,20161218T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],vserver2,vnf-id-team-auto-112,INVALID OBJ5 +virtual-data-center,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161218T091529Z,MINOR,Rule3,prov-status=[ACTIVE],vserver,vnf-id-team-auto-11,INVALID OBJ6,20161218T091529Z,MINOR,Rule3,prov-status=[ACTIVE],vserver2,vnf-id-team-auto-112,INVALID OBJ6 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161217T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],pserver,vnf-id-team-auto-11,INVALID OBJ7,20161217T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ7 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161217T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],virtual-data-center,vnf-id-team-auto-11,INVALID OBJ8,20161217T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ8 +virtual-data-center,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20161217T091529Z,MINOR,Rule3,prov-status=[ACTIVE],pserver,vnf-id-team-auto-11,INVALID OBJ9,20161217T091529Z,MINOR,Rule3,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ9 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20160917T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],pserver,vnf-id-team-auto-11,INVALID OBJ1,20160917T091529Z,CRITICAL,Rule1,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ1 +newvce,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20160217T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],virtual-data-center,vnf-id-team-auto-11,INVALID OBJ2,20160217T091529Z,MAJOR,Rule2,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ2 +virtual-data-center,Invalid prov-status value. Must have a value not equal to ACTIVE/active.,20151017T091529Z,MINOR,Rule3,prov-status=[ACTIVE],pserver,vnf-id-team-auto-11,INVALID OBJ3,20151017T091529Z,MINOR,Rule3,prov-status=[ACTIVE],pserver2,vnf-id-team-auto-112,INVALID OBJ3 diff --git a/src/app/MainScreenHeader.jsx b/src/app/MainScreenHeader.jsx index 313adcd..49952f7 100644 --- a/src/app/MainScreenHeader.jsx +++ b/src/app/MainScreenHeader.jsx @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import React, {Component} from 'react'; import {connect} from 'react-redux'; import FontAwesome from 'react-fontawesome'; @@ -30,7 +31,7 @@ import GlobalAutoCompleteSearchBar from 'app/globalAutoCompleteSearchBar/GlobalA import {postAnalyticsData} from 'app/analytics/AnalyticsActions.js'; import GlobalInlineMessageBar from 'app/globalInlineMessageBar/GlobalInlineMessageBar.jsx'; import {getClearGlobalMessageEvent} from 'app/globalInlineMessageBar/GlobalInlineMessageBarActions.js'; -import {externalUrlRequest, externalMessageRequest} from 'app/contextHandler/ContextHandlerActions.js'; +import {externalUrlRequest, externalMessageRequest, getSubscriptionPayload} from 'app/contextHandler/ContextHandlerActions.js'; import { filterBarActionTypes @@ -63,14 +64,18 @@ const mapStateToProps = ({mainWrapper}) => { showMenu = false, toggleButtonActive = false, externalRequestFound = {}, - secondaryTitle = '' + secondaryTitle = '', + subscriptionPayload = {}, + subscriptionEnabled = false } = mainWrapper; return { showMenu, toggleButtonActive, externalRequestFound, - secondaryTitle + secondaryTitle, + subscriptionPayload, + subscriptionEnabled }; }; @@ -95,6 +100,9 @@ const mapActionsToProps = (dispatch) => { }, onExternalMessageRecieved: (messageJson) => { dispatch(externalMessageRequest(messageJson)); + }, + onGetSubscriptionPayload: () => { + dispatch(getSubscriptionPayload()); } }; }; @@ -104,7 +112,8 @@ class MainScreenHeader extends Component { showMenu: React.PropTypes.bool, toggleButtonActive: React.PropTypes.bool, externalRequestFound: React.PropTypes.object, - secondaryTitle: React.PropTypes.string + secondaryTitle: React.PropTypes.string, + subscriptionPayload: React.PropTypes.object }; navigationLinkAndCurrentPathMatch(location, to) { @@ -134,6 +143,7 @@ class MainScreenHeader extends Component { } } componentWillMount() { + this.props.onGetSubscriptionPayload(); if(this.props.match.params.externalUrl !== undefined && this.isValidExternalURL(this.props.match.params.externalUrl)) { this.props.onExternalUrlRequest(this.props.match.params.externalUrl); @@ -164,9 +174,22 @@ class MainScreenHeader extends Component { nextProps.externalRequestFound !== undefined && nextProps.externalRequestFound.suggestion !== undefined) { changeUrlAddress(nextProps.externalRequestFound.suggestion, nextProps.history); } + + if (nextProps.subscriptionEnabled) { + if (nextProps.subscriptionPayload !== this.props.subscriptionPayload && + Object.keys(nextProps.subscriptionPayload).length > 0) { + var getWindowUrl = function (url) { + var split = url.split('/'); + return split[0] + '//' + split[2]; + }; + window.parent.postMessage( + JSON.stringify(nextProps.subscriptionPayload), + getWindowUrl(document.referrer)); + } + } } - receiveMessage(event) { + receiveMessage(event, $this) { function isJson(str) { try { JSON.parse(str); @@ -175,16 +198,33 @@ class MainScreenHeader extends Component { } return true; } - let messageData = event.data.message; - if(isJson(messageData)) { - this.props.onExternalMessageRecieved(JSON.parse(messageData)); + if(isJson(event.data)) { + let messageData = JSON.parse(event.data); + if(isJson(messageData.message)) { + $this.props.onExternalMessageRecieved(messageData.message); + } } + } componentDidMount() { - window.addEventListener('message', this.receiveMessage, false); + //TODO Move this logic to the component will receive props. + //Check if the event lister is available and if the subscription is + // enabled before registering for it + if(document.referrer) { + var $this = this; + window.addEventListener('message', function (e) { + $this.receiveMessage(e, $this); + }, false); + } } componentWillUnmount() { - window.removeEventListener('message', this.receiveMessage); + if(this.props.subscriptionEnabled) { + var $this = this; + window.removeEventListener('message', function (e) { + $this.receiveMessage(e, $this); + } + ); + } } render() { diff --git a/src/app/MainScreenWrapperReducer.js b/src/app/MainScreenWrapperReducer.js index 8c60330..6dd7dbf 100644 --- a/src/app/MainScreenWrapperReducer.js +++ b/src/app/MainScreenWrapperReducer.js @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import {aaiActionTypes} from './MainScreenWrapperConstants.js'; import { globalAutoCompleteSearchBarActionTypes @@ -61,11 +62,23 @@ export default (state = {}, action) => { ...state, externalRequestFound: action.data }; + case aaiActionTypes.SET_SECONDARY_TITLE: return { ...state, secondaryTitle: action.data }; + case contextHandlerActionTypes.SUBSCRIPTION_PAYLOAD_FOUND: + return { + ...state, + subscriptionPayload: action.data, + subscriptionEnabled: true + }; + case contextHandlerActionTypes.SUBSCRIPTION_PAYLOAD_EMPTY: + return { + ...state, + subscriptionEnabled: false + }; } return state; }; diff --git a/src/app/contextHandler/ContextHandlerActions.js b/src/app/contextHandler/ContextHandlerActions.js index 5738f2a..889020a 100644 --- a/src/app/contextHandler/ContextHandlerActions.js +++ b/src/app/contextHandler/ContextHandlerActions.js @@ -20,14 +20,21 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import { POST, - POST_HEADER + POST_HEADER, + GET } from 'app/networking/NetworkConstants.js'; import networkCall from 'app/networking/NetworkCalls.js'; import {EXTERNAL_REQ_ENTITY_SEARCH_URL, WRONG_EXTERNAL_REQUEST_MESSAGE, - WRONG_RESULT + WRONG_RESULT, + ZERO_RESULT, + MULTIPLE_RESULT, + FAILED_REQUEST, + SUBSCRIPTION_FAILED_MESSAGE, + SUBSCRIPTION_PAYLOAD_URL } from 'app/contextHandler/ContextHandlerConstants'; import { getSetGlobalMessageEvent, @@ -36,7 +43,8 @@ import { import { STATUS_CODE_204_NO_CONTENT, STATUS_CODE_3XX_REDIRECTION, - MESSAGE_LEVEL_DANGER + MESSAGE_LEVEL_DANGER, + MESSAGE_LEVEL_WARNING } from 'utils/GlobalConstants.js'; @@ -56,6 +64,55 @@ function getExternalParamValues(urlParams) { } +function createSubscriptionPayloadEvent(payload) { + return { + type: contextHandlerActionTypes.SUBSCRIPTION_PAYLOAD_FOUND, + data: payload + }; +} + +function createSubscriptionIsEmptyEvent() { + return { + type: contextHandlerActionTypes.SUBSCRIPTION_PAYLOAD_EMPTY, + data: {} + }; +} + +function fetchSubscriptionPayload(fetchRequestCallback) { + return dispatch => { + return fetchRequestCallback().then( + (response) => { + if (response.status >= STATUS_CODE_3XX_REDIRECTION) { + return Promise.reject(new Error(response.status)); + } else { + // assume 200 status + return response; + } + } + ).then( + (results)=> { + dispatch(createSubscriptionPayloadEvent(results)); + + } + ).catch( + (e) => { + if(e.name === 'EmptyResponseException'){ + dispatch(getClearGlobalMessageEvent()); + dispatch(createSubscriptionIsEmptyEvent()); + } else{ + dispatch(getSetGlobalMessageEvent(SUBSCRIPTION_FAILED_MESSAGE , MESSAGE_LEVEL_WARNING)); + } + } + ); + }; +} +export function getSubscriptionPayload() { + let externalfetchRequest = + () => networkCall.getRequest(SUBSCRIPTION_PAYLOAD_URL, GET); + return dispatch => { + dispatch(fetchSubscriptionPayload(externalfetchRequest)); + }; +} function validateExternalParams(externalURLParams) { if(externalURLParams.view && externalURLParams.entityId && externalURLParams.entityType) { return true; @@ -86,16 +143,21 @@ function fetchDataForExternalRequest(fetchRequestCallback) { } ).then( (results)=> { - if (results.suggestions !== undefined && results.suggestions.length === 1) { - dispatch(getClearGlobalMessageEvent()); - dispatch(createSuggestionFoundEvent({suggestion: results.suggestions[0]})); + if (results.suggestions !== undefined) { + if( results.suggestions.length === 1) { + dispatch(getClearGlobalMessageEvent()); + dispatch(createSuggestionFoundEvent({suggestion: results.suggestions[0]})); + } else if(results.totalFound === 0 ) { + dispatch(getSetGlobalMessageEvent(ZERO_RESULT, MESSAGE_LEVEL_DANGER)); + } else { + dispatch(getSetGlobalMessageEvent(MULTIPLE_RESULT, MESSAGE_LEVEL_DANGER)); } } else { - dispatch(getSetGlobalMessageEvent(WRONG_RESULT , MESSAGE_LEVEL_DANGER)); + dispatch(getSetGlobalMessageEvent(WRONG_RESULT, MESSAGE_LEVEL_DANGER)); } } ).catch( () => { - dispatch(getSetGlobalMessageEvent(WRONG_RESULT , MESSAGE_LEVEL_DANGER)); + dispatch(getSetGlobalMessageEvent(FAILED_REQUEST , MESSAGE_LEVEL_DANGER)); } ); }; diff --git a/src/app/contextHandler/ContextHandlerConstants.js b/src/app/contextHandler/ContextHandlerConstants.js index 809d05c..6229968 100644 --- a/src/app/contextHandler/ContextHandlerConstants.js +++ b/src/app/contextHandler/ContextHandlerConstants.js @@ -21,17 +21,26 @@ * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import keyMirror from 'utils/KeyMirror.js'; import {BASE_URL} from 'app/networking/NetworkConstants.js'; export const contextHandlerActionTypes = keyMirror({ SINGLE_SUGGESTION_FOUND: null, - INVALID_SUGGESTION_FOUND: null + INVALID_SUGGESTION_FOUND: null, + SUBSCRIPTION_PAYLOAD_EMPTY: null, + SUBSCRIPTION_PAYLOAD_FOUND: null }); export const EXTERNAL_REQ_ENTITY_SEARCH_URL = BASE_URL + '/rest/search/externalRequestEntitySearch'; -export const WRONG_EXTERNAL_REQUEST_MESSAGE = 'External parameter request is incorrect'; -export const WRONG_RESULT = 'Invalid result for the requested external params.'; +export const WRONG_EXTERNAL_REQUEST_MESSAGE = 'A parameter in the request is incorrect'; +export const WRONG_RESULT = 'Invalid result for the request.'; +export const FAILED_REQUEST = 'Failed to pull result for the request.'; +export const ZERO_RESULT = 'No result has been found for this request.'; +export const MULTIPLE_RESULT = 'Multiple results were found for this request so none got selected.'; +export const SUBSCRIPTION_FAILED_MESSAGE = 'Failed to fetch subscription payload.'; +export const SUBSCRIPTION_PAYLOAD_URL = BASE_URL + '/subscription/getsubscription'; + diff --git a/src/app/networking/NetworkCalls.js b/src/app/networking/NetworkCalls.js index 98021f4..b6c96b7 100644 --- a/src/app/networking/NetworkCalls.js +++ b/src/app/networking/NetworkCalls.js @@ -21,6 +21,9 @@ * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ +function EmptyResponseException(){ + this.name = 'EmptyResponseException'; +} function fetchRequest(URL, POST, POST_HEADER, BODY) { return fetch(URL, { credentials: 'same-origin', @@ -41,9 +44,31 @@ function fetchRequestObj(URL, POST, POST_HEADER, BODY) { }); } +function processResponse(response){ + if(response.status === 204){ + throw new EmptyResponseException(); + } + return response.json(); +} +function getRequest(URL, GET) { + return fetch(URL, { + credentials: 'same-origin', + method: GET + }).then( + (response) => { + try{ + response.json(); + } catch (e){ + response.isValidJson = false; + } + return processResponse(response); + } + ); +} module.exports = { fetchRequest: fetchRequest, - fetchRequestObj: fetchRequestObj + fetchRequestObj: fetchRequestObj, + getRequest: getRequest }; diff --git a/src/app/tierSupport/TierSupport.jsx b/src/app/tierSupport/TierSupport.jsx index c2d1c57..315b6a0 100644 --- a/src/app/tierSupport/TierSupport.jsx +++ b/src/app/tierSupport/TierSupport.jsx @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import React, {Component} from 'react'; import {connect} from 'react-redux'; import SplitPane from 'react-split-pane'; @@ -131,7 +132,7 @@ class TierSupport extends Component { this.props.match.params.viParam) { this.props.onNewVIParam(nextProps.match.params.viParam); } - if(nextProps.match.params.viParam === undefined && nextProps.match.params.viParam !== + if(nextProps.match.params.viParam && nextProps.match.params.viParam !== this.props.match.params.viParam) { this.props.onRequestClearData(); } diff --git a/src/app/tierSupport/TierSupportReducer.js b/src/app/tierSupport/TierSupportReducer.js index 186c4f1..1560427 100644 --- a/src/app/tierSupport/TierSupportReducer.js +++ b/src/app/tierSupport/TierSupportReducer.js @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import {combineReducers} from 'redux'; import ForceDirectedGraph from 'generic-components/graph/ForceDirectedGraph.jsx'; import {aaiActionTypes} from 'app/MainScreenWrapperConstants.js'; @@ -28,6 +29,7 @@ import { } from 'app/tierSupport/TierSupportConstants.js'; import SelectedNodeDetailsReducer from 'app/tierSupport/selectedNodeDetails/SelectedNodeDetailsReducer.js'; import GlobalAutoCompleteSearchBarReducer from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarReducer.js'; +import LaunchInContextReducer from 'app/tierSupport/launchExternalResource/LaunchExternalResourceReducer.js'; import { MESSAGE_LEVEL_DANGER, MESSAGE_LEVEL_WARNING } from 'utils/GlobalConstants.js'; @@ -37,6 +39,7 @@ import { export default combineReducers({ selectedNodeDetails: SelectedNodeDetailsReducer, + launchExternalResourceReducer: LaunchInContextReducer, globalAutoCompleteSearchBar: GlobalAutoCompleteSearchBarReducer, tierSupportReducer: (state = {}, action) => { switch (action.type) { diff --git a/src/app/tierSupport/launchExternalResource/LaunchExternalResource.jsx b/src/app/tierSupport/launchExternalResource/LaunchExternalResource.jsx new file mode 100644 index 0000000..095669b --- /dev/null +++ b/src/app/tierSupport/launchExternalResource/LaunchExternalResource.jsx @@ -0,0 +1,70 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * 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 + * + * http://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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import {connect} from 'react-redux'; +import React, {Component} from 'react'; +import {isEmpty} from 'lodash'; +import Button from 'react-bootstrap/lib/Button.js'; + +let mapStateToProps = ({tierSupport: {launchExternalResourceReducer}}) => { + let {externalResourcePayload = {}} = launchExternalResourceReducer; + + return { + externalResourcePayload + }; +}; + +class LaunchExternalResource extends Component { + static propTypes = { + externalResourcePayload: React.PropTypes.object + }; + + render() { + const {externalResourcePayload} = this.props; + + let launchExternalResourceClass = 'hidden'; + if(!isEmpty(externalResourcePayload) && (externalResourcePayload.message.payload.params.objectName.length > 0)){ + launchExternalResourceClass = ''; + } + + return ( +
+
+ ); + } + handleClick = () => { + var getWindowUrl = function (url) { + var split = url.split('/'); + return split[0] + '//' + split[2]; + }; + if(document.referrer) { + window.parent.postMessage(JSON.stringify(this.props.externalResourcePayload), getWindowUrl(document.referrer)); + } + } +} +export default connect(mapStateToProps)(LaunchExternalResource); diff --git a/src/app/tierSupport/launchExternalResource/LaunchExternalResourceReducer.js b/src/app/tierSupport/launchExternalResource/LaunchExternalResourceReducer.js new file mode 100644 index 0000000..493afaa --- /dev/null +++ b/src/app/tierSupport/launchExternalResource/LaunchExternalResourceReducer.js @@ -0,0 +1,76 @@ +/* + * ============LICENSE_START=================================================== + * SPARKY (AAI UI service) + * ============================================================================ + * Copyright © 2017 AT&T Intellectual Property. + * Copyright © 2017 Amdocs + * All rights reserved. + * ============================================================================ + * 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 + * + * http://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. + * ============LICENSE_END===================================================== + * + * ECOMP and OpenECOMP are trademarks + * and service marks of AT&T Intellectual Property. + */ + +import {tierSupportActionTypes} from 'app/tierSupport/TierSupportConstants.js'; +import { + globalAutoCompleteSearchBarActionTypes +} from 'app/globalAutoCompleteSearchBar/GlobalAutoCompleteSearchBarConstants.js'; +import {isEmpty} from 'lodash'; + +export default (state = {}, action) => { + switch (action.type) { + case tierSupportActionTypes.TS_NODE_SEARCH_RESULTS: + if(!isEmpty(action.data.nodes)){ + for (const node of action.data.nodes) { + if (node.nodeMeta.searchTarget === true) { + let externalResourcePayload = {}; + if(!isEmpty(node.externalResourcePayload)){ + externalResourcePayload = node.externalResourcePayload; + } + return { + ...state, + externalResourcePayload: externalResourcePayload + }; + } + } + } + return { + ...state, + externalResourcePayload: {} + }; + + case tierSupportActionTypes.TS_GRAPH_NODE_SELECTED: + let externalResourcePayload; + if(action.data.externalResourcePayload){ + externalResourcePayload = action.data.externalResourcePayload; + } else { + externalResourcePayload = {}; + } + return { + ...state, + externalResourcePayload: externalResourcePayload + }; + + case globalAutoCompleteSearchBarActionTypes.SEARCH_WARNING_EVENT: + case tierSupportActionTypes.TIER_SUPPORT_NETWORK_ERROR: + case tierSupportActionTypes.TIER_SUPPORT_CLEAR_DATA: + case tierSupportActionTypes.TS_NODE_SEARCH_NO_RESULTS: + return { + ...state, + externalResourcePayload: {} + }; + } + return state; +}; diff --git a/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetails.jsx b/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetails.jsx index 5ff4104..1dbd71b 100644 --- a/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetails.jsx +++ b/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetails.jsx @@ -20,9 +20,11 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import {connect} from 'react-redux'; import React, {Component} from 'react'; import Table from 'react-bootstrap/lib/Table'; +import LaunchInContext from 'app/tierSupport/launchExternalResource/LaunchExternalResource.jsx'; import i18n from 'utils/i18n/i18n'; import { SELECTED_NODE_TITLE, @@ -82,7 +84,7 @@ class SelectedNodeDetails extends Component {

{i18n(SELECTED_NODE_TITLE)}

{nodeType}

- {uid} + {uid} diff --git a/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetailsReducer.js b/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetailsReducer.js index d088e73..17eaed1 100644 --- a/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetailsReducer.js +++ b/src/app/tierSupport/selectedNodeDetails/SelectedNodeDetailsReducer.js @@ -40,7 +40,7 @@ export default (state = {}, action) => { } return { ...state, - nodeData: [], + nodeData: {}, nodeType: '', uid: '' }; @@ -60,7 +60,7 @@ export default (state = {}, action) => { case tierSupportActionTypes.TS_NODE_SEARCH_NO_RESULTS: return { ...state, - nodeData: [], + nodeData: {}, nodeType: '', uid: '' }; diff --git a/src/generic-components/graph/ForceDirectedGraph.jsx b/src/generic-components/graph/ForceDirectedGraph.jsx index 0038f01..0c9f4e7 100644 --- a/src/generic-components/graph/ForceDirectedGraph.jsx +++ b/src/generic-components/graph/ForceDirectedGraph.jsx @@ -20,6 +20,7 @@ * * ECOMP is a trademark and service mark of AT&T Intellectual Property. */ + import {drag} from 'd3-drag'; import {forceSimulation, forceLink, forceManyBody, forceCenter} from 'd3-force'; import {interpolateNumber} from 'd3-interpolate'; @@ -108,8 +109,19 @@ class ForceDirectedGraph extends Component { this.simulation.on('end', this.simulationComplete); this.simulation.stop(); + + function myDelta() { + var deltaY = 2.5; + var deltaMode = 1; + if(currentEvent.deltaY < 0 ) { + deltaY = -2.5; + } + return -deltaY * (deltaMode ? 120 : 1) / 1500; + } + + this.svgZoom = - zoom().scaleExtent([NodeConstants.SCALE_EXTENT_MIN, NodeConstants.SACEL_EXTENT_MAX]); + zoom().scaleExtent([NodeConstants.SCALE_EXTENT_MIN, NodeConstants.SACEL_EXTENT_MAX]).wheelDelta(myDelta);; this.svgZoom.clickDistance(2); this.nodeDrag = drag().clickDistance(2); @@ -120,6 +132,7 @@ class ForceDirectedGraph extends Component { } else { this.hideButton = false; } + if (props.graphData) { if (props.graphData.graphCounter !== -1) { this.startSimulation(props.graphData, props.currentlySelectedNodeView, props.dataOverlayButtons); @@ -140,7 +153,6 @@ class ForceDirectedGraph extends Component { } } - componentDidUpdate(prevProps) { let hasNewGraphDataRendered = (prevProps.graphData.graphCounter === this.props.graphData.graphCounter); @@ -152,6 +164,7 @@ class ForceDirectedGraph extends Component { let nodes = select('.fdgMainSvg').select('.fdgMainG') .selectAll('.aai-entity-node') .data(this.nodeDatum); + nodes.on('click', (d) => { this.nodeSelected(d); }); @@ -246,6 +259,7 @@ class ForceDirectedGraph extends Component { NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME) { this.nodeButtonDatum[0].data = nodeProps.meta; + if(this.nodeButtonDatum.length > 1) { this.nodeButtonDatum[1].data = nodeProps.meta; nodeProps = { @@ -262,6 +276,7 @@ class ForceDirectedGraph extends Component { newNodes.push(this.nodeFactory.buildNode(nodeProps.meta.nodeMeta.className, nodeProps, this.hideButton)); + this.nodeIndexTracker.set(node.id, i); }); @@ -302,6 +317,7 @@ class ForceDirectedGraph extends Component { } startSimulation(graphData, currentView, overlayButtons) { + this.nodeFactory.setNodeMeta(graphData.graphMeta); // Experiment with removing length = 0... might not be needed as new array @@ -323,7 +339,6 @@ class ForceDirectedGraph extends Component { name: NodeConstants.ICON_ELLIPSES, isSelected: isNodeDetailsSelected, overlayName: overlayButtons[0] }); - if(overlayButtons.length > 1 ) { let isSecondButtonSelected = (currentView === overlayButtons[1]); @@ -399,7 +414,6 @@ class ForceDirectedGraph extends Component { return (datum.id === d.id); }); if (!newlySelectedNode.empty()) { - if (newlySelectedNode.datum().nodeMeta.searchTarget) { this.nodeBuffer[newlySelectedNode.datum().index].nodeMeta.className = NodeConstants.SELECTED_SEARCHED_NODE_CLASS_NAME; -- 2.16.6