changing react-scripts version / create-react-app ver
This commit is contained in:
Родитель
4f9d84310f
Коммит
e59e02a8d1
|
@ -1,2 +0,0 @@
|
|||
[config]
|
||||
command = bash ./scripts/deployment/azure-deploy.sh
|
|
@ -1,40 +1,18 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
# production
|
||||
/build
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
|
||||
node_modules
|
||||
node_modules_old
|
||||
bower_components
|
||||
dist
|
||||
build
|
||||
tmp
|
||||
*.pem
|
||||
*.config.json
|
||||
|
||||
# Localhost config file
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editors
|
||||
.vscode/*
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// Place your settings in this file to overwrite default and user settings.
|
||||
{
|
||||
"files.exclude": {
|
||||
"**/*.css": { "when": "$(basename).scss"}
|
||||
}
|
||||
}
|
22
LICENSE
22
LICENSE
|
@ -1,22 +0,0 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Catalyst Code
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
1382
README.md
1382
README.md
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
|
||||
"hostingPlanName": {
|
||||
"value": "changeme"
|
||||
},
|
||||
"webSiteName": {
|
||||
"value": "changeme"
|
||||
},
|
||||
"siteTitle": {
|
||||
"value": "Change Me"
|
||||
},
|
||||
"logoURl": {
|
||||
"value": "none"
|
||||
},
|
||||
|
||||
"repoURL": {
|
||||
"value": "https://github.com/CatalystCode/bot-fmk-dashboard ==> fork & copy new url"
|
||||
},
|
||||
"branch": {
|
||||
"value": "master"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
|
||||
"hostingPlanName": {
|
||||
"value": "mrsh-bots-dash"
|
||||
},
|
||||
"webSiteName": {
|
||||
"value": "mrsh-bots-dash"
|
||||
},
|
||||
"siteTitle": {
|
||||
"value": "mrsh-bots-dash"
|
||||
},
|
||||
"logoURl": {
|
||||
"value": "none"
|
||||
},
|
||||
|
||||
"repoURL": {
|
||||
"value": "https://github.com/CatalystCode/bot-fmk-dashboard"
|
||||
},
|
||||
"branch": {
|
||||
"value": "master"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"hostingPlanName": { "type": "string" },
|
||||
"webSiteName": { "type": "string" },
|
||||
"siteTitle": { "type": "string" },
|
||||
"logoURl": { "type": "string" },
|
||||
"hostingPlanSKU": {
|
||||
"type": "string",
|
||||
"allowedValues": [
|
||||
"Free",
|
||||
"Shared",
|
||||
"Basic",
|
||||
"Standard"
|
||||
],
|
||||
"defaultValue": "Basic",
|
||||
"metadata": {
|
||||
"description": "SKU value"
|
||||
}
|
||||
},
|
||||
"hostingPlanWorkerSize": {
|
||||
"type": "string",
|
||||
"allowedValues": [
|
||||
"0",
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"defaultValue": "1",
|
||||
"metadata": {
|
||||
"description": "Worker Size( 0=Small, 1=Medium, 2=Large )"
|
||||
}
|
||||
},
|
||||
"repoURL": {
|
||||
"type": "string",
|
||||
"defaultValue": "https://github.com/CatalystCode/bot-fmk-dashboard.git",
|
||||
"metadata": {
|
||||
"description": "Fork that repo than use the new url in [parameters file:repoUrl:value]."
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"defaultValue": "master",
|
||||
"metadata": {
|
||||
"description": "The branch of the GitHub repository to use."
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"applicationInsightName": "[concat(parameters('webSiteName'), '-ai')]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-04-01",
|
||||
"name": "[parameters('hostingPlanName')]",
|
||||
"type": "Microsoft.Web/serverfarms",
|
||||
"location": "[resourceGroup().location]",
|
||||
"properties": {
|
||||
"name": "[parameters('hostingPlanName')]",
|
||||
"sku": "[parameters('hostingPlanSKU')]",
|
||||
"workerSize": "[parameters('hostingPlanWorkerSize')]",
|
||||
"numberOfWorkers": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "[parameters('webSiteName')]",
|
||||
"type": "Microsoft.Web/sites",
|
||||
"location": "[resourceGroup().location]",
|
||||
"apiVersion": "2015-08-01",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
|
||||
],
|
||||
"tags": {
|
||||
"[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName')))]": "Resource",
|
||||
"displayName": "DashboardWebSite"
|
||||
},
|
||||
"properties": {
|
||||
"name": "[parameters('webSiteName')]",
|
||||
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-08-01",
|
||||
"name": "web",
|
||||
"type": "config",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"alwaysOn": true,
|
||||
"virtualApplications": [
|
||||
{
|
||||
"virtualPath": "/",
|
||||
"physicalPath": "site\\wwwroot"
|
||||
},
|
||||
{
|
||||
"virtualPath": "/MyApp",
|
||||
"physicalPath": "site\\wwwroot"
|
||||
}
|
||||
],
|
||||
"defaultDocuments": [
|
||||
"index.html",
|
||||
"hostingstart.html"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "appsettings",
|
||||
"type": "config",
|
||||
"apiVersion": "2015-08-01",
|
||||
"dependsOn": [
|
||||
"[resourceId('Microsoft.Web/sites', parameters('webSiteName'))]",
|
||||
"[resourceId('Microsoft.Insights/components', variables('applicationInsightName'))]"
|
||||
],
|
||||
"tags": {
|
||||
"displayName": "ApplicationSettings"
|
||||
},
|
||||
"properties": {
|
||||
"WEBSITE_NODE_DEFAULT_VERSION": "6.9.1",
|
||||
"SCM_COMMAND_IDLE_TIMEOUT": "7200",
|
||||
"REACT_APP_SITE_LOGO": "[parameters('logoURl')]",
|
||||
"REACT_APP_SITE_TITLE ": "[parameters('siteTitle')]",
|
||||
"REACT_APP_APP_INSIGHTS_APPID": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightName')), '2014-04-01').AppId]",
|
||||
"REACT_APP_APP_INSIGHTS_APIKEY": "TODO: AppInsights ==> Settings ==> API Access ==> Create API Key ==> Generate ==> [Copy Key here]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"apiVersion": "2015-08-01",
|
||||
"name": "web",
|
||||
"type": "sourcecontrols",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]",
|
||||
"[resourceId('Microsoft.Web/sites/config', parameters('webSiteName'), 'appsettings')]"
|
||||
],
|
||||
"properties": {
|
||||
"RepoUrl": "[parameters('repoURL')]",
|
||||
"branch": "[parameters('branch')]",
|
||||
"IsManualIntegration": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "[variables('applicationInsightName')]",
|
||||
"type": "Microsoft.Insights/components",
|
||||
"location": "[resourceGroup().location]",
|
||||
"apiVersion": "2014-04-01",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Web/sites/', parameters('webSiteName'))]"
|
||||
],
|
||||
"tags": {
|
||||
"displayName": "Component ApplicationInsight"
|
||||
},
|
||||
"properties": {
|
||||
"applicationId": "[resourceId('Microsoft.Web/sites', parameters('webSiteName'))]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-05-01",
|
||||
"name": "dashboard-keys",
|
||||
"type": "apikeys",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Insights/components/', variables('applicationInsightName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"linkedReadProperties": "[concat(resourceId('Microsoft.insights/components', variables('applicationInsightName'))), '/api']"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"appInsightsInstrumentationKey": {
|
||||
"type": "string",
|
||||
"value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightName')), '2014-04-01').InstrumentationKey]"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
azure group create <resource group name> -l <location> -f dashboard.template.json -e dashboard.parameters.private.json
|
|
@ -1,79 +0,0 @@
|
|||
{
|
||||
"$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {
|
||||
"hostingPlanName": { "type": "string" },
|
||||
"webSiteName": { "type": "string" },
|
||||
"siteTitle": { "type": "string" },
|
||||
"logoURl": { "type": "string" },
|
||||
"hostingPlanSKU": {
|
||||
"type": "string",
|
||||
"allowedValues": [
|
||||
"Free",
|
||||
"Shared",
|
||||
"Basic",
|
||||
"Standard"
|
||||
],
|
||||
"defaultValue": "Basic",
|
||||
"metadata": {
|
||||
"description": "SKU value"
|
||||
}
|
||||
},
|
||||
"hostingPlanWorkerSize": {
|
||||
"type": "string",
|
||||
"allowedValues": [
|
||||
"0",
|
||||
"1",
|
||||
"2"
|
||||
],
|
||||
"defaultValue": "1",
|
||||
"metadata": {
|
||||
"description": "Worker Size( 0=Small, 1=Medium, 2=Large )"
|
||||
}
|
||||
},
|
||||
"repoURL": {
|
||||
"type": "string",
|
||||
"defaultValue": "https://github.com/CatalystCode/bot-fmk-dashboard.git",
|
||||
"metadata": {
|
||||
"description": "Fork that repo than use the new url in [parameters file:repoUrl:value]."
|
||||
}
|
||||
},
|
||||
"branch": {
|
||||
"type": "string",
|
||||
"defaultValue": "master",
|
||||
"metadata": {
|
||||
"description": "The branch of the GitHub repository to use."
|
||||
}
|
||||
}
|
||||
},
|
||||
"variables": {
|
||||
"applicationInsightName": "[concat(parameters('webSiteName'), '-ai')]"
|
||||
},
|
||||
"resources": [
|
||||
{
|
||||
"name": "[variables('applicationInsightName')]",
|
||||
"type": "Microsoft.Insights/components",
|
||||
"location": "[resourceGroup().location]",
|
||||
"apiVersion": "2014-04-01",
|
||||
"resources": [
|
||||
{
|
||||
"apiVersion": "2015-05-01",
|
||||
"name": "dashboard-keys",
|
||||
"type": "apikeys",
|
||||
"dependsOn": [
|
||||
"[concat('Microsoft.Insights/components/', variables('applicationInsightName'))]"
|
||||
],
|
||||
"properties": {
|
||||
"linkedReadProperties": "[concat(resourceId('Microsoft.insights/components', variables('applicationInsightName')), '/api')]"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": {
|
||||
"appInsightsInstrumentationKey": {
|
||||
"type": "string",
|
||||
"value": "[reference(resourceId('Microsoft.Insights/components', variables('applicationInsightName')), '2014-04-01').InstrumentationKey]"
|
||||
}
|
||||
}
|
||||
}
|
Двоичные данные
docs/bot-framedash-msgs.png
Двоичные данные
docs/bot-framedash-msgs.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 76 KiB |
Двоичные данные
docs/bot-framedash.png
Двоичные данные
docs/bot-framedash.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 91 KiB |
76
package.json
76
package.json
|
@ -1,63 +1,43 @@
|
|||
{
|
||||
"name": "bot-fmk-dashboard",
|
||||
"description": "This repository holds all client / front-end related code for the Microsoft/OCHA Hackfest. .",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"main": "./dist/js/index.min.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/CatalystCode/bot-fmk-dashboard.git"
|
||||
},
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "mor shemesh",
|
||||
"email": "mor.shemesh@gmail.com"
|
||||
}
|
||||
],
|
||||
"name": "my-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@types/alt": "^0.16.31",
|
||||
"@types/lodash": "^4.14.48",
|
||||
"@types/alt": "^0.16.32",
|
||||
"@types/jest": "^19.2.2",
|
||||
"@types/jquery": "^2.0.41",
|
||||
"@types/lodash": "^4.14.55",
|
||||
"@types/node": "^7.0.8",
|
||||
"@types/react-dom": "^0.14.20",
|
||||
"mocha": "^3.1.2",
|
||||
"@types/react": "^15.0.16",
|
||||
"@types/react-dom": "^0.14.23",
|
||||
"@types/react-router": "^3.0.8",
|
||||
"node-sass": "^4.5.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"react-scripts": "^0.6.0"
|
||||
"react-scripts-ts": "1.1.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"alt": "^0.18.6",
|
||||
"alt-utils": "^1.0.0",
|
||||
"async": "^2.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"jquery": "^3.1.1",
|
||||
"lodash": "^4.17.2",
|
||||
"material-ui": "^0.16.5",
|
||||
"moment": "^2.17.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "^15.4.1",
|
||||
"react-addons-create-fragment": "^15.4.1",
|
||||
"jquery": "^3.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"material-colors": "^1.2.5",
|
||||
"moment": "^2.18.0",
|
||||
"react": "^15.4.2",
|
||||
"react-addons-css-transition-group": "^15.4.2",
|
||||
"react-dom": "^15.4.1",
|
||||
"react-grid-layout": "^0.13.9",
|
||||
"react-infinite": "^0.10.0",
|
||||
"react-list-view": "^1.0.0",
|
||||
"react-md": "^1.0.7",
|
||||
"react-router": "^3.0.0",
|
||||
"react-tap-event-plugin": "^2.0.1",
|
||||
"recharts": "^0.19.1",
|
||||
"request": "^2.78.0"
|
||||
"react-addons-transition-group": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"react-grid-layout": "^0.14.4",
|
||||
"react-md": "^1.0.10",
|
||||
"react-router": "3.0.0",
|
||||
"recharts": "^0.21.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build-css": "node-sass ts-src/ -o src/",
|
||||
"watch-css": "npm run build-css && node-sass ts-src/ -o src/ --watch --recursive",
|
||||
"start-js": "react-scripts start",
|
||||
"build-css": "node-sass src/ -o src/",
|
||||
"watch-css": "yarn run build-css && node-sass src/ -o src/ --watch --recursive",
|
||||
"start-js": "react-scripts-ts start",
|
||||
"start": "npm-run-all -p watch-css start-js",
|
||||
"build": "npm run build-css && react-scripts build",
|
||||
"react-test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"test": "mocha"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./node_modules/react-scripts/config/eslint.js"
|
||||
"build": "yarn run build-css && react-scripts-ts build",
|
||||
"test": "react-scripts-ts test --env=jsdom",
|
||||
"eject": "react-scripts-ts eject"
|
||||
}
|
||||
}
|
||||
|
|
Двоичные данные
public/images/OCHA_Logo.png
Двоичные данные
public/images/OCHA_Logo.png
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 6.1 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
До Ширина: | Высота: | Размер: 28 KiB |
|
@ -1,32 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Bot Analytics Dashboard</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.0.0/js/adal.min.js"></script>
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Roboto:300,400,500' />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
<script>
|
||||
String.prototype.format = function(){
|
||||
var content = this;
|
||||
for (var i=0; i < arguments.length; i++)
|
||||
{
|
||||
var replacement = '{' + i + '}';
|
||||
content = content.replace(replacement, arguments[i]);
|
||||
}
|
||||
return content;
|
||||
};
|
||||
</script>
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
</div>
|
||||
<style>
|
||||
#map { height: 100%; }
|
||||
</style>
|
||||
<div id='map'></div>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
</body>
|
||||
<script src="https://use.fontawesome.com/ae5201ad7c.js"></script>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
This configuration file is required if iisnode is used to run node processes behind
|
||||
IIS or IIS Express. For more information, visit:
|
||||
|
||||
https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
|
||||
-->
|
||||
|
||||
<configuration>
|
||||
<system.webServer>
|
||||
<handlers>
|
||||
<clear />
|
||||
<add
|
||||
name="StaticFile"
|
||||
path="*" verb="*"
|
||||
modules="StaticFileModule,DefaultDocumentModule,DirectoryListingModule"
|
||||
resourceType="Either"
|
||||
requireAccess="Read" />
|
||||
</handlers>
|
||||
<staticContent>
|
||||
<mimeMap fileExtension=".*" mimeType="application/octet-stream" />
|
||||
</staticContent>
|
||||
</system.webServer>
|
||||
</configuration>
|
|
@ -1,148 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# ----------------------
|
||||
# KUDU Deployment Script
|
||||
# Version: 0.2.2
|
||||
# ----------------------
|
||||
|
||||
# Helpers
|
||||
# -------
|
||||
|
||||
exitWithMessageOnError () {
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "An error has occurred during web site deployment."
|
||||
echo $1
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Prerequisites
|
||||
# -------------
|
||||
|
||||
# Verify node.js installed
|
||||
hash node 2>/dev/null
|
||||
exitWithMessageOnError "Missing node.js executable, please install node.js, if already installed make sure it can be reached from current environment."
|
||||
|
||||
# Setup
|
||||
# -----
|
||||
|
||||
SCRIPT_DIR="${BASH_SOURCE[0]%\\*}"
|
||||
SCRIPT_DIR="${SCRIPT_DIR%/*}"
|
||||
ARTIFACTS=$SCRIPT_DIR/../artifacts
|
||||
KUDU_SYNC_CMD=${KUDU_SYNC_CMD//\"}
|
||||
|
||||
|
||||
if [[ ! -n "$DEPLOYMENT_SOURCE" ]]; then
|
||||
DEPLOYMENT_SOURCE=$SCRIPT_DIR
|
||||
fi
|
||||
|
||||
if [[ ! -n "$NEXT_MANIFEST_PATH" ]]; then
|
||||
NEXT_MANIFEST_PATH=$ARTIFACTS/manifest
|
||||
|
||||
if [[ ! -n "$PREVIOUS_MANIFEST_PATH" ]]; then
|
||||
PREVIOUS_MANIFEST_PATH=$NEXT_MANIFEST_PATH
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -n "$DEPLOYMENT_TARGET" ]]; then
|
||||
DEPLOYMENT_TARGET=$ARTIFACTS/wwwroot
|
||||
else
|
||||
KUDU_SERVICE=true
|
||||
fi
|
||||
|
||||
WEB_CONFIG=$DEPLOYMENT_SOURCE/scripts/deployment/Web.config
|
||||
|
||||
if [[ ! -n "$KUDU_SYNC_CMD" ]]; then
|
||||
# Install kudu sync
|
||||
echo Installing Kudu Sync
|
||||
npm install kudusync -g --silent
|
||||
exitWithMessageOnError "npm failed"
|
||||
|
||||
if [[ ! -n "$KUDU_SERVICE" ]]; then
|
||||
# In case we are running locally this is the correct location of kuduSync
|
||||
KUDU_SYNC_CMD=kuduSync
|
||||
else
|
||||
# In case we are running on kudu service this is the correct location of kuduSync
|
||||
KUDU_SYNC_CMD=$APPDATA/npm/node_modules/kuduSync/bin/kuduSync
|
||||
fi
|
||||
fi
|
||||
|
||||
# Node Helpers
|
||||
# ------------
|
||||
|
||||
selectNodeVersion () {
|
||||
echo Selecting Node Version
|
||||
if [[ -n "$KUDU_SELECT_NODE_VERSION_CMD" ]]; then
|
||||
SELECT_NODE_VERSION="$KUDU_SELECT_NODE_VERSION_CMD \"$DEPLOYMENT_SOURCE\" \"$DEPLOYMENT_TARGET\" \"$DEPLOYMENT_TEMP\""
|
||||
eval $SELECT_NODE_VERSION
|
||||
exitWithMessageOnError "select node version failed"
|
||||
|
||||
if [[ -e "$DEPLOYMENT_TEMP/__nodeVersion.tmp" ]]; then
|
||||
NODE_EXE=`cat "$DEPLOYMENT_TEMP/__nodeVersion.tmp"`
|
||||
exitWithMessageOnError "getting node version failed"
|
||||
fi
|
||||
|
||||
if [[ -e "$DEPLOYMENT_TEMP/.tmp" ]]; then
|
||||
NPM_JS_PATH=`cat "$DEPLOYMENT_TEMP/__npmVersion.tmp"`
|
||||
exitWithMessageOnError "getting npm version failed"
|
||||
fi
|
||||
|
||||
if [[ ! -n "$NODE_EXE" ]]; then
|
||||
NODE_EXE=node
|
||||
fi
|
||||
|
||||
NPM_CMD="\"$NODE_EXE\" \"$NPM_JS_PATH\""
|
||||
else
|
||||
NPM_CMD=npm
|
||||
NODE_EXE=node
|
||||
fi
|
||||
}
|
||||
|
||||
##################################################################################################################################
|
||||
# Deployment
|
||||
# ----------
|
||||
|
||||
echo Handling custom node.js deployment.
|
||||
|
||||
touch server.js
|
||||
|
||||
selectNodeVersion
|
||||
|
||||
if [ -e "$DEPLOYMENT_SOURCE/package.json" ]; then
|
||||
echo Installing Create React App Package
|
||||
eval $NPM_CMD install -g create-react-app
|
||||
exitWithMessageOnError "create react app install failed"
|
||||
|
||||
echo Installing NPM Packages
|
||||
eval $NPM_CMD install
|
||||
exitWithMessageOnError "npm failed"
|
||||
|
||||
echo Building React App
|
||||
eval $NPM_CMD run build
|
||||
exitWithMessageOnError "react build failed"
|
||||
|
||||
cd - > /dev/null
|
||||
fi
|
||||
|
||||
if [ -e "$WEB_CONFIG" ]; then
|
||||
echo Copying $WEB_CONFIG over to the build folder
|
||||
cp $WEB_CONFIG build/
|
||||
exitWithMessageOnError "Unable to copy $WEB_CONFIG over to build"
|
||||
fi
|
||||
|
||||
if [[ "$IN_PLACE_DEPLOYMENT" -ne "1" ]]; then
|
||||
echo Syncing Files
|
||||
"$KUDU_SYNC_CMD" -v 50 -f "$DEPLOYMENT_SOURCE/build" -t "$DEPLOYMENT_TARGET" -n "$NEXT_MANIFEST_PATH" -p "$PREVIOUS_MANIFEST_PATH" -i ".git;.hg;.deployment;deploy.sh"
|
||||
exitWithMessageOnError "Kudu Sync failed"
|
||||
fi
|
||||
|
||||
##################################################################################################################################
|
||||
# Post deployment stub
|
||||
if [[ -n "$POST_DEPLOYMENT_ACTION" ]]; then
|
||||
POST_DEPLOYMENT_ACTION=${POST_DEPLOYMENT_ACTION//\"}
|
||||
cd "${POST_DEPLOYMENT_ACTION_DIR%\\*}"
|
||||
"$POST_DEPLOYMENT_ACTION"
|
||||
exitWithMessageOnError "post deployment action failed"
|
||||
fi
|
||||
|
||||
echo "Finished successfully."
|
|
@ -0,0 +1,24 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #222;
|
||||
height: 150px;
|
||||
padding: 20px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-intro {
|
||||
font-size: large;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import * as React from 'react';
|
||||
import './App.css';
|
||||
|
||||
const logo = require('./logo.svg');
|
||||
|
||||
class App extends React.Component<null, null> {
|
||||
render() {
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<h2>Welcome to React</h2>
|
||||
</div>
|
||||
<p className="App-intro">
|
||||
To get started, edit <code>src/App.tsx</code> and save to reload.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default App;
|
|
@ -1,38 +0,0 @@
|
|||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
class ConversionActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail'
|
||||
);
|
||||
}
|
||||
|
||||
refresh(timespan) {
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
var query = ` customEvents` +
|
||||
` | extend successful=customDimensions.successful` +
|
||||
` | where name startswith 'message.convert'` +
|
||||
` | summarize event_count=count() by name, tostring(successful)`;
|
||||
var mappings = [
|
||||
{ key: 'name' },
|
||||
{ key: 'successful', val: (val) => val === 'true' },
|
||||
{ key: 'event_count', def: 0 }
|
||||
];
|
||||
|
||||
common.fetchQuery({ timespan, query, mappings }, (error, conversions) => {
|
||||
if (error) {
|
||||
return this.refreshFail(error)
|
||||
}
|
||||
|
||||
return dispatch({ conversions, timespan });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(ConversionActions);
|
|
@ -1,88 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
var appInsightsAppId = common.appInsightsAppId;
|
||||
var appInsightsApiKey = common.appInsightsApiKey;
|
||||
|
||||
class ErrorsActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail',
|
||||
'updateSearchTerm',
|
||||
'selectHandler',
|
||||
'searchResults',
|
||||
'searchFail',
|
||||
'selectError'
|
||||
);
|
||||
}
|
||||
|
||||
refresh(timespan) {
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
var query = ` exceptions` +
|
||||
` | summarize count_error=count() by handledAt, innermostMessage` +
|
||||
` | order by count_error desc `
|
||||
var mappings = [
|
||||
{ key: 'handledAt', def: 'Unknown' },
|
||||
{ key: 'message', def: '' },
|
||||
{ key: 'count', def: '' }
|
||||
];
|
||||
|
||||
common.fetchQuery({ timespan, query, mappings }, (error, results) => {
|
||||
if (error) {
|
||||
return this.refreshFail(error)
|
||||
}
|
||||
|
||||
var errors = results;
|
||||
var handlers = {};
|
||||
errors.forEach(error => {
|
||||
if (!handlers[error.handledAt]) handlers[error.handledAt] = {
|
||||
name: error.handledAt,
|
||||
count: 0
|
||||
};
|
||||
handlers[error.handledAt].count += error.count;
|
||||
});
|
||||
|
||||
return dispatch({
|
||||
errors,
|
||||
handlers: _.values(handlers),
|
||||
timespan
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
queryExceptions(handledAt, timespan, searchQuery, top, skip) {
|
||||
|
||||
top = top || 100;
|
||||
skip = skip || 0;
|
||||
|
||||
var queryspan = timespan === '24 hours' ? 'PT24H' : timespan === '1 week' ? 'P7D' : 'P30D';
|
||||
var search = searchQuery ? `&$search=${encodeURIComponent(searchQuery)}` : '';
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/events/exceptions?timespan=${queryspan}` +
|
||||
search +
|
||||
`&&$orderby=timestamp&$top=${top}&$skip=${skip}`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": appInsightsApiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
return this.searchResults(json.value);
|
||||
})
|
||||
.fail((err) => {
|
||||
this.searchFail(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(ErrorsActions);
|
|
@ -1,84 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
var appInsightsAppId = common.appInsightsAppId;
|
||||
var appInsightsApiKey = common.appInsightsApiKey;
|
||||
|
||||
class GeneralActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail',
|
||||
'initialize'
|
||||
);
|
||||
}
|
||||
|
||||
refresh(timespan) {
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
var query = ` exceptions` +
|
||||
` | summarize count_error=count() by handledAt, innermostMessage` +
|
||||
` | order by count_error desc `
|
||||
var mappings = [
|
||||
{ key: 'handledAt', def: 'Unknown' },
|
||||
{ key: 'message', def: '' },
|
||||
{ key: 'count', def: '' }
|
||||
];
|
||||
|
||||
common.fetchQuery({ timespan, query, mappings }, (error, results) => {
|
||||
if (error) {
|
||||
return this.refreshFail(error)
|
||||
}
|
||||
|
||||
var errors = results;
|
||||
var handlers = {};
|
||||
errors.forEach(error => {
|
||||
if (!handlers[error.handledAt]) handlers[error.handledAt] = {
|
||||
name: error.handledAt,
|
||||
count: 0
|
||||
};
|
||||
handlers[error.handledAt].count += error.count;
|
||||
});
|
||||
|
||||
return dispatch({
|
||||
errors,
|
||||
handlers: _.values(handlers),
|
||||
timespan
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
queryExceptions(handledAt, timespan, searchQuery, top, skip) {
|
||||
|
||||
top = top || 100;
|
||||
skip = skip || 0;
|
||||
|
||||
var queryspan = timespan == '24 hours' ? 'PT24H' : timespan == '1 week' ? 'P7D' : 'P30D';
|
||||
var search = searchQuery ? `&$search=${encodeURIComponent(searchQuery)}` : '';
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/events/exceptions?timespan=${queryspan}` +
|
||||
search +
|
||||
`&&$orderby=timestamp&$top=${top}&$skip=${skip}`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": appInsightsApiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
return this.searchResults(json.value);
|
||||
})
|
||||
.fail((err) => {
|
||||
this.searchFail(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(GeneralActions);
|
|
@ -1,126 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
var appInsightsAppId = common.appInsightsAppId;
|
||||
var appInsightsApiKey = common.appInsightsApiKey;
|
||||
|
||||
class IntentActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail',
|
||||
'selectIntent',
|
||||
'selectConversation'
|
||||
);
|
||||
}
|
||||
|
||||
refresh (timespan) {
|
||||
|
||||
var queryspan = common.timespanToQueryspan(timespan);
|
||||
var query = ` customEvents` +
|
||||
` | extend cslen = customDimensions.callstack_length, intent=customDimensions.intent` +
|
||||
` | where name startswith "message.intent" and (cslen == 0 or strlen(cslen) == 0) and strlen(intent) > 0` +
|
||||
` | summarize event_count=count() by tostring(intent)`;
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/query?timespan=${queryspan}&query=${encodeURIComponent(query)}`;
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: { "x-api-key": appInsightsApiKey }
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var _intents = [];
|
||||
json.Tables[0].Rows.forEach(row => {
|
||||
var intent = row[0] || 'Unknown';
|
||||
var count = row[1] || 0;
|
||||
|
||||
_intents.push({ intent, count });
|
||||
});
|
||||
|
||||
return dispatch({ intents: _intents, timespan });
|
||||
})
|
||||
.fail((err) => {
|
||||
this.refreshFail(err);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
fetchIntentConversations(intent, timespan) {
|
||||
|
||||
var queryspan = 'P30D';
|
||||
var query = ` customEvents` +
|
||||
` | extend conversation = customDimensions.conversationId, intent=customDimensions.intent` +
|
||||
` | where name startswith "message.intent" and intent =~ '${intent}'` +
|
||||
` | summarize event_count=count() by tostring(conversation)`;
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/query?timespan=${queryspan}&query=${encodeURIComponent(query)}`;
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: { "x-api-key": appInsightsApiKey }
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var _conversations = [];
|
||||
json.Tables[0].Rows.forEach(row => {
|
||||
var conversation = row[0] || 'Unknown';
|
||||
var count = row[1] || 0;
|
||||
|
||||
_conversations.push({ conversation, count });
|
||||
});
|
||||
|
||||
return dispatch(_conversations);
|
||||
})
|
||||
.fail((err) => {
|
||||
this.refreshFail(err);
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
fetchConversationMessages(conversation, timespan) {
|
||||
|
||||
var queryspan = 'P30D';
|
||||
var query = ` customEvents` +
|
||||
` | extend conversation = customDimensions.conversationId, intent=customDimensions.intent` +
|
||||
` | where name in ("message.send", "message.received") and conversation == '${conversation}'` +
|
||||
` | order by timestamp asc` +
|
||||
` | project timestamp, name, customDimensions.text, customDimensions.userName, customDimensions.userId`;
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/query?timespan=${queryspan}&query=${encodeURIComponent(query)}`;
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: { "x-api-key": appInsightsApiKey }
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var messages = json.Tables[0].Rows.map(row => {
|
||||
var timestamp = row[0];
|
||||
var eventName = row[1];
|
||||
var message = row[2];
|
||||
var userName = row[3];
|
||||
var userId = row[4];
|
||||
|
||||
return { timestamp, eventName, message, userName, userId };
|
||||
});
|
||||
|
||||
return dispatch(messages);
|
||||
})
|
||||
.fail((err) => {
|
||||
this.refreshFail(err);
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(IntentActions);
|
|
@ -1,36 +0,0 @@
|
|||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
class SentimentActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail'
|
||||
);
|
||||
}
|
||||
|
||||
refresh(timespan) {
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
var query = ` customEvents` +
|
||||
` | extend score=customDimensions.score, text=customDimensions.text` +
|
||||
` | where name startswith 'message.sentiment'` +
|
||||
` | summarize sentiment=avg(todouble(score))`;
|
||||
var mappings = [
|
||||
{ key: 'sentiment' }
|
||||
];
|
||||
|
||||
common.fetchQuery({ timespan, query, mappings }, (error, sentiments) => {
|
||||
if (error) {
|
||||
return this.refreshFail(error)
|
||||
}
|
||||
|
||||
return dispatch({ sentiments, timespan });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(SentimentActions)
|
|
@ -1,86 +0,0 @@
|
|||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
import TimelineStore from '../stores/TimelineStore';
|
||||
|
||||
var appInsightsAppId = common.appInsightsAppId;
|
||||
var appInsightsApiKey = common.appInsightsApiKey;
|
||||
|
||||
class TimelineActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail',
|
||||
'dismissError',
|
||||
'updateMode'
|
||||
);
|
||||
}
|
||||
|
||||
refresh (timespan) {
|
||||
|
||||
var state = TimelineStore.getState();
|
||||
var queryspan = common.timespanToQueryspan(timespan);
|
||||
var granularity = common.timespanToGranularity(timespan);
|
||||
|
||||
// Query messages per time
|
||||
var query1 = ` customEvents` +
|
||||
` | where name == 'Activity'` +
|
||||
` | summarize event_count=count() by bin(timestamp, ${granularity}), name, tostring(customDimensions.channel)` +
|
||||
` | order by timestamp asc `;
|
||||
|
||||
// Query users per time
|
||||
var query2 = ` customEvents` +
|
||||
` | where name == 'Activity'` +
|
||||
` | summarize event_count=dcount(tostring(customDimensions.from)) by bin(timestamp, ${granularity}), name, tostring(customDimensions.channel)` +
|
||||
` | order by timestamp asc`;
|
||||
|
||||
var query = state.mode == 'users' ? query2 : query1;
|
||||
var url = `${common.appInsights.uri}/${appInsightsAppId}/query?timespan=${queryspan}&query=${encodeURIComponent(query)}`;
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: { "x-api-key": appInsightsApiKey }
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var now = moment();
|
||||
|
||||
var _timeline = {};
|
||||
var _channels = {};
|
||||
json.Tables[0].Rows.forEach(row => {
|
||||
var channel = row[2] || 'unknown';
|
||||
var time = (new Date(row[0])).getTime();
|
||||
var count = row[3];
|
||||
|
||||
if (!_timeline[time]) _timeline[time] = { time: (new Date(row[0])).toUTCString() };
|
||||
if (!_channels[channel]) _channels[channel] = { name: channel, value: 0 };
|
||||
|
||||
_timeline[time][channel] = count;
|
||||
_channels[channel].value += count;
|
||||
});
|
||||
|
||||
var channels = Object.keys(_channels);
|
||||
var channelUsage = _.values(_channels);
|
||||
var timeline = _.map(_timeline, value => {
|
||||
channels.forEach(channel => {
|
||||
if (!value[channel]) value[channel] = 0;
|
||||
});
|
||||
return value;
|
||||
});
|
||||
|
||||
return dispatch({ data: timeline, channelUsage, channels, timespan });
|
||||
})
|
||||
.fail((err) => {
|
||||
this.refreshFail(err);
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(TimelineActions);
|
|
@ -1,16 +0,0 @@
|
|||
import alt from '../alt';
|
||||
|
||||
class TimespanActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'update24Hours',
|
||||
'update1Week',
|
||||
'update1Month',
|
||||
'toggleChannel',
|
||||
'toggleAllChannel');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default alt.createActions(TimespanActions);
|
|
@ -1,40 +0,0 @@
|
|||
import moment from 'moment';
|
||||
import alt from '../alt';
|
||||
|
||||
import common from './actions-common';
|
||||
|
||||
class UsersActions {
|
||||
|
||||
constructor() {
|
||||
this.generateActions(
|
||||
'refreshFail'
|
||||
);
|
||||
}
|
||||
|
||||
refresh(timespan) {
|
||||
|
||||
return (dispatch) => {
|
||||
|
||||
var query = ` customEvents` +
|
||||
` | where name == 'Activity' and customDimensions.activitytype == 'conversationUpdate' and timestamp > ago(365d)` +
|
||||
` | summarize min_timestamp=min(timestamp), max(timestamp), count() by tostring(customDimensions.from)` +
|
||||
` | order by min_timestamp desc `;
|
||||
var mappings = [
|
||||
{ key: 'address', def: 'Unknown' },
|
||||
{ key: 'minTimestamp', val: moment },
|
||||
{ key: 'maxTimestamp', val: moment },
|
||||
{ key: 'count', def: 0 }
|
||||
];
|
||||
|
||||
common.fetchQuery({ timespan, query, mappings }, (error, users) => {
|
||||
if (error) {
|
||||
return this.refreshFail(error)
|
||||
}
|
||||
|
||||
return dispatch({ users, timespan });
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default alt.createActions(UsersActions);
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
/**
|
||||
* @class Constants
|
||||
*/
|
||||
|
||||
class Constants {
|
||||
|
||||
/** Default dimentions available for updates */
|
||||
DefaultDimentions = {
|
||||
Timespan: 'timespan'
|
||||
}
|
||||
}
|
||||
|
||||
export default new Constants();
|
|
@ -1,146 +0,0 @@
|
|||
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import Constants from '../constants';
|
||||
import {DataSourcePlugin, IDataSourceOptions} from './DataSourcePlugin';
|
||||
|
||||
var appInsightsUri = 'https://api.applicationinsights.io/beta/apps';
|
||||
var appId = process.env.REACT_APP_APP_INSIGHTS_APPID;
|
||||
var apiKey = process.env.REACT_APP_APP_INSIGHTS_APIKEY;
|
||||
|
||||
class AIHelper {
|
||||
|
||||
/**
|
||||
* Execute a query with the application insights query API.
|
||||
* @param {QueryConfig} config - Configuration for the query
|
||||
* @param {function} callback
|
||||
*/
|
||||
fetchQuery(config, callback) {
|
||||
|
||||
var mappings = this._props.mappings;
|
||||
var queryspan = ActionsCommon.timespanToQueryspan(timespan);
|
||||
var url = `${appInsightsUri}/${appId}/query?timespan=${queryspan}&query=${this._props.params.query}`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": apiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var resultRows = json.Tables[0].Rows;
|
||||
if (!mappings || mappings.length === 0) {
|
||||
return callback(null, ActionsCommon.prepareResult(resultRows));
|
||||
}
|
||||
|
||||
var rows = resultRows.map(row => {
|
||||
var item = {};
|
||||
mappings.forEach((mapping, index) => {
|
||||
var key = typeof mapping === 'string' ? mapping : mapping.key;
|
||||
var idx = mapping.idx ? mapping.idx : index;
|
||||
var def = mapping.def ? mapping.def : null;
|
||||
item[key] = mapping.val && row[idx] && mapping.val(row[index]) || row[idx] || def;
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
return callback(null, this._prepareResult(rows));
|
||||
})
|
||||
.fail((err) => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query with the application insights events API.
|
||||
* @param {EventsQueryConfig} config - Configuration for the query
|
||||
* @param {function} callback
|
||||
*/
|
||||
fetchEvents(config, callback) {
|
||||
|
||||
var {
|
||||
timespan,
|
||||
search,
|
||||
top,
|
||||
skip
|
||||
} = config || new EventsQueryConfig();
|
||||
|
||||
var queryspan = ActionsCommon.timespanToQueryspan(timespan);
|
||||
var url = `${appInsights.uri}/${ActionsCommon.appInsightsAppId}/events/exceptions?timespan=${queryspan}` +
|
||||
search ? `&$search=${encodeURIComponent(search)}` : '' +
|
||||
`&&$orderby=timestamp` +
|
||||
top ? `&$top=${top}` : '' +
|
||||
skip ? `&$skip=${skip}` : '';
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": ActionsCommon.appInsightsApiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
return callback(null, this._helper.prepareResult(json.value));
|
||||
})
|
||||
.fail((err) => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class QueryConfig {
|
||||
/** @type {string} */
|
||||
timespan;
|
||||
}
|
||||
|
||||
interface IApplicationInsightsOptions extends IDataSourceOptions {
|
||||
/** @type {string} */
|
||||
query;
|
||||
/** @type {(string|object)[]} mappings */
|
||||
mappings;
|
||||
}
|
||||
|
||||
export class ApplicationInsightsData extends DataSourcePlugin {
|
||||
|
||||
type = 'application-insights';
|
||||
_helper = new AIHelper();
|
||||
|
||||
/**
|
||||
* @param {ApplicationInsightsDataOptions} options - Options object
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
|
||||
var props = this._props;
|
||||
if (!props.params.query || !props.dependencies || !props.dependencies.length) {
|
||||
throw new Error('AIAnalyticsEvents requires a query to run and dependencies that trigger updates.');
|
||||
}
|
||||
|
||||
// Bind helper method to 'this'
|
||||
this._helper.timespanToQueryspan = this._helper.timespanToQueryspan.bind(this);
|
||||
this._helper.prepareResult = this._helper.prepareResult.bind(this);
|
||||
this._helper.fetchQuery = this._helper.fetchQuery.bind(this);
|
||||
this._helper.fetchEvents = this._helper.fetchEvents.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* update - called when dependencies are created
|
||||
* @param {object} dependencies
|
||||
* @param {function} callback
|
||||
*/
|
||||
updateDependencies(dependencies, callback) {
|
||||
|
||||
var { timespan } = dependencies;
|
||||
|
||||
// TODO: insert dependencies into query [format]or[function]
|
||||
|
||||
if (this._props.type == 'generic') {
|
||||
this._helper.fetchQuery({timespan}, callback);
|
||||
} else {
|
||||
this._helper.fetchEvents(dependencies, callback);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
|
||||
import $ from 'jquery';
|
||||
import _ from 'lodash';
|
||||
import Constants from '../constants';
|
||||
import common from '../actions-common';
|
||||
|
||||
var appInsightsUri = 'https://api.applicationinsights.io/beta/apps';
|
||||
var appId = process.env.REACT_APP_APP_INSIGHTS_APPID;
|
||||
var apiKey = process.env.REACT_APP_APP_INSIGHTS_APIKEY;
|
||||
|
||||
class QueryConfig {
|
||||
/** @type {string} */
|
||||
timespan;
|
||||
}
|
||||
|
||||
class EventsQueryConfig {
|
||||
/** @type {string} */
|
||||
timespan;
|
||||
/** @type {string} */
|
||||
search;
|
||||
/** @type {number} */
|
||||
top;
|
||||
/** @type {number} */
|
||||
skip;
|
||||
}
|
||||
|
||||
class ApplicationInsightsDataOptions {
|
||||
/** @type {string} */
|
||||
query;
|
||||
/** @type {string} */
|
||||
type;
|
||||
/** @type {(string|object)[]} */
|
||||
dependencies;
|
||||
/** @type {(string|object)[]} */
|
||||
mappings;
|
||||
/** @type {string} outputResultsName - This would be variable storing the results */
|
||||
outputResultsName;
|
||||
}
|
||||
|
||||
export class ApplicationInsightsData {
|
||||
|
||||
_props = {
|
||||
type: 'generic',
|
||||
dependencies: [Constants.DefaultDimentions.Timespan],
|
||||
output: null,
|
||||
mappings: [],
|
||||
actions: [],
|
||||
listeners: [],
|
||||
params: {
|
||||
query: ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ApplicationInsightsDataOptions} options - Options object
|
||||
*/
|
||||
constructor(options) {
|
||||
|
||||
if (!options.params.query || !options.dependencies || !options.dependencies.length) {
|
||||
throw new Error('AIAnalyticsEvents requires a query to run and dependencies that trigger updates.');
|
||||
}
|
||||
|
||||
var props = this._props;
|
||||
props.params.query = options.params.query;
|
||||
props.dependencies = props.dependencies.concat(options.dependencies);
|
||||
props.mappings = options.mappings;
|
||||
props.type = options.type === 'table' ? 'table' : 'generic';
|
||||
props.output = options.outputResultsName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]} Array of dependencies
|
||||
*/
|
||||
getDependencies() {
|
||||
return this._props.dependencies;
|
||||
}
|
||||
|
||||
getDependables() {
|
||||
return this._props.outputParameter;
|
||||
}
|
||||
|
||||
getActions() {
|
||||
return this._props.actions;
|
||||
}
|
||||
|
||||
getParams() {
|
||||
return Object.keys(this._props);
|
||||
}
|
||||
|
||||
listen(listener) {
|
||||
if (!this._props.listeners.find(func => func === listener)) {
|
||||
this._props.listeners.push(listener);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* update - called when dependencies are created
|
||||
* @param {object} dependencies
|
||||
* @param {function} callback
|
||||
*/
|
||||
updateDependencies(dependencies, callback) {
|
||||
|
||||
var { timespan } = dependencies;
|
||||
|
||||
// TODO: insert dependencies into query [format]or[function]
|
||||
|
||||
if (this._props.type == 'generic') {
|
||||
this._fetchQuery({timespan}, callback);
|
||||
} else {
|
||||
this._fetchEvents(dependencies, callback);
|
||||
}
|
||||
}
|
||||
|
||||
_timespanToQueryspan(timespan) {
|
||||
return timespan === '24 hours' ? 'PT24H' : timespan === '1 week' ? 'P7D' : 'P30D';
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare results to ship via callback
|
||||
* @param {Array} results
|
||||
* @returns {object} object to be returned
|
||||
*/
|
||||
_prepareResult(results) {
|
||||
var obj = {};
|
||||
obj[this._props.output] = results;
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query with the application insights query API.
|
||||
* @param {QueryConfig} config - Configuration for the query
|
||||
* @param {function} callback
|
||||
*/
|
||||
_fetchQuery(config, callback) {
|
||||
|
||||
var mappings = this._props.mappings;
|
||||
var queryspan = this._timespanToQueryspan(timespan);
|
||||
var url = `${appInsightsUri}/${appId}/query?timespan=${queryspan}&query=${this._props.params.query}`;
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": apiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
var resultRows = json.Tables[0].Rows;
|
||||
if (!mappings || mappings.length === 0) {
|
||||
return callback(null, this._prepareResult(resultRows));
|
||||
}
|
||||
|
||||
var rows = resultRows.map(row => {
|
||||
var item = {};
|
||||
mappings.forEach((mapping, index) => {
|
||||
var key = typeof mapping === 'string' ? mapping : mapping.key;
|
||||
var idx = mapping.idx ? mapping.idx : index;
|
||||
var def = mapping.def ? mapping.def : null;
|
||||
item[key] = mapping.val && row[idx] && mapping.val(row[index]) || row[idx] || def;
|
||||
});
|
||||
return item;
|
||||
});
|
||||
|
||||
return callback(null, this._prepareResult(rows));
|
||||
})
|
||||
.fail((err) => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query with the application insights events API.
|
||||
* @param {EventsQueryConfig} config - Configuration for the query
|
||||
* @param {function} callback
|
||||
*/
|
||||
_fetchEvents(config, callback) {
|
||||
|
||||
var {
|
||||
timespan,
|
||||
search,
|
||||
top,
|
||||
skip
|
||||
} = config || new EventsQueryConfig();
|
||||
|
||||
var queryspan = ActionsCommon.timespanToQueryspan(timespan);
|
||||
var url = `${appInsights.uri}/${ActionsCommon.appInsightsAppId}/events/exceptions?timespan=${queryspan}` +
|
||||
search ? `&$search=${encodeURIComponent(search)}` : '' +
|
||||
`&&$orderby=timestamp` +
|
||||
top ? `&$top=${top}` : '' +
|
||||
skip ? `&$skip=${skip}` : '';
|
||||
|
||||
$.ajax({
|
||||
url,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-api-key": ActionsCommon.appInsightsApiKey
|
||||
}
|
||||
})
|
||||
.then(json => {
|
||||
|
||||
return callback(null, this._prepareResult(json.value));
|
||||
})
|
||||
.fail((err) => {
|
||||
return callback(err);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
module.exports = {
|
||||
dataSources: [
|
||||
{
|
||||
id: 'timespan',
|
||||
type: 'constant',
|
||||
values: ['24 hours', '1 week', '1 month'],
|
||||
defaultValue: '24 hours'
|
||||
},
|
||||
{
|
||||
id: 'conversionRate',
|
||||
type: 'app-insights',
|
||||
dependencies: ['timespan'],
|
||||
params: {
|
||||
query: ` customEvents` +
|
||||
` | extend successful=customDimensions.successful` +
|
||||
` | where name startswith 'message.convert'` +
|
||||
` | summarize event_count=count() by name, tostring(successful)`,
|
||||
mappings: [
|
||||
{ key: 'name' },
|
||||
{ key: 'successful', val: (val) => val === 'true' },
|
||||
{ key: 'event_count', def: 0 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'timelineMessages',
|
||||
type: 'app-insights',
|
||||
dependencies: ['timespan'],
|
||||
query: (state, timespan) => {
|
||||
|
||||
var granularity = common.timespanToGranularity(timespan);
|
||||
return ` customEvents` +
|
||||
` | where name == 'Activity'` +
|
||||
` | summarize event_count=count() by bin(timestamp, ${granularity}), name, tostring(customDimensions.channel)` +
|
||||
` | order by timestamp asc `
|
||||
},
|
||||
mappings: [
|
||||
{ key: 'timestamp' },
|
||||
{ key: 'name' },
|
||||
{ key: 'channel' },
|
||||
{ key: 'event_count', def: 0 }
|
||||
]
|
||||
},
|
||||
]
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
import { ApplicationInsightsData } from './application-insights-data';
|
||||
|
||||
// Example of using with conversion rate graph
|
||||
var a = new ApplicationInsightsData({
|
||||
query: ` customEvents` +
|
||||
` | extend successful=customDimensions.successful` +
|
||||
` | where name startswith 'message.convert'` +
|
||||
` | summarize event_count=count() by name, tostring(successful)`,
|
||||
dependencies: [ 'timespan' ],
|
||||
mappings: [
|
||||
{ key: 'name' },
|
||||
{ key: 'successful', val: (val) => val === 'true' },
|
||||
{ key: 'event_count', def: 0 }
|
||||
]
|
||||
})
|
||||
|
15
src/alt.js
15
src/alt.js
|
@ -1,15 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Alt = require("alt");
|
||||
exports.default = new Alt();
|
||||
/**
|
||||
*
|
||||
* Declarations for inheritance purposes
|
||||
*
|
||||
*/
|
||||
class AbstractActions {
|
||||
constructor(alt) { }
|
||||
}
|
||||
exports.AbstractActions = AbstractActions;
|
||||
class AbstractStoreModel {
|
||||
}
|
||||
exports.AbstractStoreModel = AbstractStoreModel;
|
|
@ -1,14 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const Navbar_1 = require("./Navbar");
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
var { children } = this.props;
|
||||
return (
|
||||
// <Navbar history={this.props.history}>
|
||||
<Navbar_1.default history={this.props.history}>
|
||||
{children}
|
||||
</Navbar_1.default>);
|
||||
}
|
||||
}
|
||||
exports.default = App;
|
|
@ -9,7 +9,7 @@ class App extends React.Component<any, any> {
|
|||
|
||||
return (
|
||||
// <Navbar history={this.props.history}>
|
||||
<Navbar history={this.props.history}>
|
||||
<Navbar>
|
||||
{ children }
|
||||
</Navbar>
|
||||
);
|
|
@ -1,10 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const Media_1 = require("react-md/lib/Media");
|
||||
const Cards_1 = require("react-md/lib/Cards");
|
||||
exports.default = ({ children = null, title = '', subtitle = '' }) => <Cards_1.Card>
|
||||
<Cards_1.CardTitle title={title} subtitle={subtitle}/>
|
||||
<Media_1.Media>
|
||||
{children}
|
||||
</Media_1.Media>
|
||||
</Cards_1.Card>;
|
|
@ -1,60 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
|
||||
import {List, ListItem} from 'material-ui/List';
|
||||
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
|
||||
import ErrorsStore from '../../stores/ErrorsStore';
|
||||
import ErrorsActions from '../../actions/ErrorsActions';
|
||||
|
||||
class ErrorDetails extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [ErrorsStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return ErrorsStore.getState();
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
ErrorsActions.selectError(null);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedError } = this.props;
|
||||
|
||||
// Display tabs for all messages or grouped by type
|
||||
var dialogActions = [
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.handleClose}
|
||||
/>,
|
||||
];
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={`Error Details`}
|
||||
modal={false}
|
||||
open={!!selectedError}
|
||||
actions={dialogActions}
|
||||
autoScrollBodyContent={true}
|
||||
onRequestClose={this.handleClose}>
|
||||
<List>
|
||||
{Object.keys(selectedError || {}).map( (key, index) => (
|
||||
<ListItem key={index}>
|
||||
{key}:{(!selectedError[key] && '') || (typeof selectedError[key] == 'string' && selectedError[key]) || JSON.stringify(selectedError[key])}
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(ErrorDetails);
|
|
@ -1,181 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
|
||||
import TextField from 'material-ui/TextField';
|
||||
import {Tabs, Tab} from 'material-ui'
|
||||
import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
|
||||
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import ActionOpen from 'material-ui/svg-icons/action/open-in-new';
|
||||
|
||||
import ErrorsStore from '../../stores/ErrorsStore';
|
||||
import ErrorsActions from '../../actions/ErrorsActions';
|
||||
|
||||
var styles = {
|
||||
item: {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
timestamp: {
|
||||
width: '30%'
|
||||
},
|
||||
count: {
|
||||
width: '10%',
|
||||
},
|
||||
message: {
|
||||
width: '50%',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
action: {
|
||||
width: '10%'
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorHandlers extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [ErrorsStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return ErrorsStore.getState();
|
||||
}
|
||||
|
||||
constructor (props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedTab: 0
|
||||
};
|
||||
|
||||
this.updateSearchTerm = this.updateSearchTerm.bind(this);
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
ErrorsActions.selectHandler(null);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
const { selectedHandler, timespan, searchTerm } = this.props;
|
||||
ErrorsActions.queryExceptions(selectedHandler, timespan, searchTerm);
|
||||
}
|
||||
|
||||
updateSearchTerm(event) {
|
||||
const { selectedHandler, timespan } = this.props;
|
||||
ErrorsActions.updateSearchTerm(event.target.value);
|
||||
ErrorsActions.queryExceptions(selectedHandler, timespan, event.target.value);
|
||||
}
|
||||
|
||||
escapeRegExp(str) {
|
||||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
|
||||
}
|
||||
|
||||
selectTab(tab) {
|
||||
this.setState({ selectedTab: tab });
|
||||
}
|
||||
|
||||
handleClickError(error) {
|
||||
ErrorsActions.selectError(error);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedHandler, errors, searchTerm, searchResults, selectedError } = this.props;
|
||||
|
||||
// Filter grouped messages by search term - does not affect 'all messages' tab
|
||||
var errorItems = errors;
|
||||
if (searchTerm !== null && searchTerm !== '') {
|
||||
var search = new RegExp(this.escapeRegExp(searchTerm), "i");
|
||||
errorItems = errorItems.filter(error => {
|
||||
return error.message.search(search) >= 0;
|
||||
});
|
||||
}
|
||||
|
||||
// Display tabs for all messages or grouped by type
|
||||
var dialogActions = [
|
||||
<Tabs value={this.state.selectedTab}>
|
||||
<Tab value={0} onClick={this.selectTab.bind(this, 0)} label='Message Count' />
|
||||
<Tab value={1} onClick={this.selectTab.bind(this, 1)} label='All Messages' />
|
||||
</Tabs>,
|
||||
<TextField onChange={this.updateSearchTerm} hintText="Search for an error..." />,
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.handleClose}
|
||||
/>,
|
||||
]
|
||||
|
||||
var dialogContent = null;
|
||||
if (this.state.selectedTab === 0) {
|
||||
dialogContent = (
|
||||
<Table selectable={false} multiSelectable={false}>
|
||||
<TableHeader
|
||||
displaySelectAll={false}
|
||||
adjustForCheckbox={false}
|
||||
enableSelectAll={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.message}>Message</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.count}>Count</TableHeaderColumn>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false} >
|
||||
{errorItems.map( (error, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableRowColumn style={styles.message}>{error.message}</TableRowColumn>
|
||||
<TableRowColumn style={styles.count}>{error.count}</TableRowColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
} else {
|
||||
|
||||
// All messages queried directly on API
|
||||
dialogContent = (
|
||||
<Table selectable={false} multiSelectable={false}>
|
||||
<TableHeader
|
||||
displaySelectAll={false}
|
||||
adjustForCheckbox={false}
|
||||
enableSelectAll={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.timestamp}>Timestamp</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.count}>Count</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.message}>Message</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.action}></TableHeaderColumn>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false} >
|
||||
{searchResults.map( (error, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableRowColumn style={styles.timestamp}>{error.timestamp}</TableRowColumn>
|
||||
<TableRowColumn style={styles.count}>{error.count}</TableRowColumn>
|
||||
<TableRowColumn style={styles.message}>{error.exception && error.exception.innermostMessage}</TableRowColumn>
|
||||
<TableHeaderColumn style={styles.action}>
|
||||
<IconButton onClick={this.handleClickError.bind(this, error)}><ActionOpen />></IconButton>
|
||||
</TableHeaderColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={`Handled At: ${selectedHandler}`}
|
||||
modal={false}
|
||||
open={!!selectedHandler && !selectedError}
|
||||
actions={dialogActions}
|
||||
autoScrollBodyContent={true}
|
||||
onRequestClose={this.handleClose}>
|
||||
{dialogContent}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(ErrorHandlers);
|
|
@ -1,71 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
import {List, ListItem} from 'material-ui/List';
|
||||
import ReportProblem from 'material-ui/svg-icons/action/report-problem';
|
||||
import Divider from 'material-ui/Divider';
|
||||
|
||||
import colors from './colors';
|
||||
import styles from './styles';
|
||||
|
||||
import ErrorHandlers from './ErrorHandlers';
|
||||
import ErrorDetails from './ErrorDetails';
|
||||
import ErrorsStore from '../../stores/ErrorsStore';
|
||||
import ErrorsActions from '../../actions/ErrorsActions';
|
||||
|
||||
class Errors extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [ErrorsStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return ErrorsStore.getState();
|
||||
}
|
||||
|
||||
selectHandler(handledAt) {
|
||||
ErrorsActions.selectHandler(handledAt);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { handlers } = this.props;
|
||||
|
||||
var errorsCount = 0;
|
||||
var errorItems = handlers.map((handledAt, idx) => {
|
||||
errorsCount += handledAt.count;
|
||||
return <ListItem
|
||||
key={idx}
|
||||
primaryText={handledAt.count}
|
||||
secondaryText={handledAt.name}
|
||||
onClick={this.selectHandler.bind(this, handledAt.name)}
|
||||
leftIcon={<ReportProblem color={colors.DangerColor} />} />
|
||||
});
|
||||
|
||||
//if (!errorItems.length) return null;
|
||||
|
||||
return (
|
||||
<Card className='dash-card dash-card-list'>
|
||||
<CardHeader
|
||||
style={styles.cardHeaderStyle}
|
||||
title="Errors"
|
||||
subtitle="Click errors" />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<List className='list-compact'>
|
||||
{errorItems}
|
||||
</List>
|
||||
<Divider />
|
||||
<List className='list-compact'>
|
||||
<ListItem key={0} primaryText={errorsCount} secondaryText="Total errors" leftIcon={<ReportProblem color={colors.DangerColor} />}/>
|
||||
</List>
|
||||
</CardMedia>
|
||||
<ErrorHandlers />
|
||||
<ErrorDetails />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(Errors);
|
|
@ -1,139 +0,0 @@
|
|||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { PieChart, Pie, Sector, Cell, Tooltip, Legend } from 'recharts';
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
|
||||
import colors from '../colors';
|
||||
import styles from '../styles';
|
||||
var {ThemeColors} = colors;
|
||||
|
||||
import TimelineStore from '../../../stores/TimelineStore';
|
||||
|
||||
class ChannelsPie extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [TimelineStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return TimelineStore.getState();
|
||||
}
|
||||
|
||||
state = {
|
||||
activeIndex: 0
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.onPieEnter = this.onPieEnter.bind(this);
|
||||
}
|
||||
|
||||
onPieEnter(data, index) {
|
||||
this.setState({ activeIndex: index});
|
||||
}
|
||||
|
||||
renderActiveShape = (props) => {
|
||||
const { mode } = this.props;
|
||||
var type = mode === 'users' ? 'Users' : 'Messages';
|
||||
|
||||
const RADIAN = Math.PI / 180;
|
||||
const { name, cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle,
|
||||
fill, payload, percent, value } = props;
|
||||
const sin = Math.sin(-RADIAN * midAngle);
|
||||
const cos = Math.cos(-RADIAN * midAngle);
|
||||
const sx = cx + (outerRadius + 10) * cos;
|
||||
const sy = cy + (outerRadius + 10) * sin;
|
||||
const mx = cx + (outerRadius + 30) * cos;
|
||||
const my = cy + (outerRadius + 30) * sin;
|
||||
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
const ey = my;
|
||||
const textAnchor = cos >= 0 ? 'start' : 'end';
|
||||
|
||||
var c = {};
|
||||
c.midAngle = 54.11764705882353;
|
||||
c.sin = Math.sin(-RADIAN * c.midAngle);
|
||||
c.cos = Math.cos(-RADIAN * c.midAngle);
|
||||
c.cx = cx;
|
||||
c.cy = cy;
|
||||
c.sx = cx + (outerRadius + 10) * c.cos;
|
||||
c.sy = cy + (outerRadius + 10) * c.sin;
|
||||
c.mx = cx + (outerRadius + 30) * c.cos;
|
||||
c.my = cy + (outerRadius + 30) * c.sin;
|
||||
c.ex = c.mx + (c.cos >= 0 ? 1 : -1) * 22;
|
||||
c.ey = c.my;
|
||||
c.textAnchor = 'start'
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={c.cx}
|
||||
cy={c.cy}
|
||||
startAngle={300}
|
||||
endAngle={60}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
<path d={`M${c.sx},${c.sy}L${c.mx},${c.my}L${c.ex},${c.ey}`} stroke={fill} fill="none"/>
|
||||
<circle cx={c.ex} cy={c.ey} r={2} fill={fill} stroke="none"/>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} textAnchor={c.textAnchor} fill="#333">{`${value} ${type.toLowerCase()}`}</text>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} dy={18} textAnchor={c.textAnchor} fill="#999">
|
||||
{`(Rate ${(percent * 100).toFixed(2)}%)`}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { channelUsage, mode } = this.props;
|
||||
var type = mode === 'users' ? 'Users' : 'Messages';
|
||||
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (
|
||||
<Card className='dash-card'>
|
||||
<CardHeader
|
||||
style={styles.cardHeaderStyle}
|
||||
title="Channel Usage"
|
||||
subtitle={`${type} per channel`} />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<PieChart width={400} height={240}>
|
||||
<Pie
|
||||
data={channelUsage}
|
||||
onMouseEnter={this.onPieEnter}
|
||||
cx={150}
|
||||
cy={120}
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
paddingAngle={5}
|
||||
activeIndex={this.state.activeIndex}
|
||||
activeShape={this.renderActiveShape.bind(this)}
|
||||
>
|
||||
{
|
||||
channelUsage.map((entry, index) => <Cell key={index} fill={ThemeColors[index % ThemeColors.length]}/>)
|
||||
}
|
||||
<Tooltip/>
|
||||
<Legend />
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</CardMedia>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(ChannelsPie);
|
|
@ -1,8 +0,0 @@
|
|||
.message_send {
|
||||
text-align: right !important;
|
||||
background-color: aliceblue !important;
|
||||
}
|
||||
|
||||
.message_received {
|
||||
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import $ from 'jquery';
|
||||
import moment from 'moment';
|
||||
|
||||
import {Table, TableBody, TableFooter, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
|
||||
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
|
||||
import IntenStore from '../../../stores/IntentStore';
|
||||
import IntentActions from '../../../actions/IntentActions';
|
||||
|
||||
import './ConversationMessages.css'
|
||||
var styles = {
|
||||
item: {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
timestamp: {
|
||||
width: '30%'
|
||||
},
|
||||
message: {
|
||||
width: '70%',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
}
|
||||
|
||||
class ConversationMessages extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [IntenStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return IntenStore.getState();
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
IntentActions.selectConversation(null);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
var $container = $('.dialog-messages').find('h3').first().next();
|
||||
var $content = $container.find('>div');
|
||||
$container.animate({ scrollTop: $content.height() }, 'slow');
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedConversation, conversationMessages } = this.props;
|
||||
|
||||
// Display tabs for all messages or grouped by type
|
||||
var dialogActions = [
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.handleClose}
|
||||
/>,
|
||||
]
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
className='dialog-messages'
|
||||
title={`Messages for ${selectedConversation}`}
|
||||
modal={false}
|
||||
open={!!selectedConversation}
|
||||
actions={dialogActions}
|
||||
autoScrollBodyContent={true}
|
||||
onRequestClose={this.handleClose}>
|
||||
<Table selectable={false} multiSelectable={false}>
|
||||
<TableHeader
|
||||
displaySelectAll={false}
|
||||
adjustForCheckbox={false}
|
||||
enableSelectAll={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.timestamp}>Timestamp</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.message}>Message</TableHeaderColumn>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false}>
|
||||
{conversationMessages.map( (msg, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableRowColumn style={styles.timestamp}>
|
||||
{moment(msg.timestamp).format('MMM-DD HH:mm:ss')}
|
||||
</TableRowColumn>
|
||||
<TableRowColumn className={msg.eventName.replace(/\./g, '_')} style={styles.message}>{msg.message}</TableRowColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(ConversationMessages);
|
|
@ -1,130 +0,0 @@
|
|||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { PieChart, Pie, Sector, Cell, Legend } from 'recharts';
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
|
||||
import colors from '../colors';
|
||||
import styles from '../styles';
|
||||
|
||||
import ConversionStore from '../../../stores/ConversionStore';
|
||||
|
||||
class ConversionsPie extends Component {
|
||||
|
||||
static getStores() {
|
||||
return [ConversionStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return ConversionStore.getState();
|
||||
}
|
||||
|
||||
|
||||
renderActiveShape = (props) => {
|
||||
const { mode } = this.props;
|
||||
var type = mode === 'users' ? 'Users' : 'Messages';
|
||||
|
||||
const RADIAN = Math.PI / 180;
|
||||
const { name, cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle,
|
||||
fill, payload, percent, value } = props;
|
||||
const sin = Math.sin(-RADIAN * midAngle);
|
||||
const cos = Math.cos(-RADIAN * midAngle);
|
||||
const sx = cx + (outerRadius + 10) * cos;
|
||||
const sy = cy + (outerRadius + 10) * sin;
|
||||
const mx = cx + (outerRadius + 30) * cos;
|
||||
const my = cy + (outerRadius + 30) * sin;
|
||||
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
const ey = my;
|
||||
const textAnchor = cos >= 0 ? 'start' : 'end';
|
||||
|
||||
var c = {};
|
||||
c.midAngle = 54.11764705882353;
|
||||
c.sin = Math.sin(-RADIAN * c.midAngle);
|
||||
c.cos = Math.cos(-RADIAN * c.midAngle);
|
||||
c.cx = cx;
|
||||
c.cy = cy;
|
||||
c.sx = cx + (outerRadius + 10) * c.cos;
|
||||
c.sy = cy + (outerRadius + 10) * c.sin;
|
||||
c.mx = cx + (outerRadius + 30) * c.cos;
|
||||
c.my = cy + (outerRadius + 30) * c.sin;
|
||||
c.ex = c.mx + (c.cos >= 0 ? 1 : -1) * 22;
|
||||
c.ey = c.my;
|
||||
c.textAnchor = 'start'
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={c.cx}
|
||||
cy={c.cy}
|
||||
startAngle={300}
|
||||
endAngle={60}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
<path d={`M${c.sx},${c.sy}L${c.mx},${c.my}L${c.ex},${c.ey}`} stroke={fill} fill="none"/>
|
||||
<circle cx={c.ex} cy={c.ey} r={2} fill={fill} stroke="none"/>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} textAnchor={c.textAnchor} fill="#333">{`${value} ${type.toLowerCase()}`}</text>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} dy={18} textAnchor={c.textAnchor} fill="#999">
|
||||
{`(Rate ${(percent * 100).toFixed(2)}%)`}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { conversions } = this.props;
|
||||
|
||||
var total = _.find(conversions, { name: 'message.convert.start' });
|
||||
var successful = _.find(conversions, { name: 'message.convert.end', successful: true }) || { event_count: 0 };
|
||||
|
||||
if (!total) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var values = [
|
||||
{ name: 'Successful', value: successful.event_count },
|
||||
{ name: 'Failed', value: total.event_count - successful.event_count },
|
||||
];
|
||||
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (
|
||||
<Card className='dash-card'>
|
||||
<CardHeader
|
||||
className='card-header'
|
||||
title="Conversion Usage"
|
||||
subtitle={`Conversion Rate`} />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<PieChart width={500} height={240}>
|
||||
<Pie
|
||||
data={values}
|
||||
cx={270}
|
||||
cy={120}
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
activeIndex={0}
|
||||
activeShape={this.renderActiveShape.bind(this)}
|
||||
paddingAngle={0}>
|
||||
<Cell key={0} fill={colors.GoodColor}/>
|
||||
<Cell key={1} fill={colors.BadColor}/>
|
||||
</Pie>
|
||||
<Legend wrapperStyle={{ marginLeft: 70 }} />
|
||||
</PieChart>
|
||||
</CardMedia>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(ConversionsPie);
|
|
@ -1,104 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
|
||||
import TextField from 'material-ui/TextField';
|
||||
import {Tabs, Tab} from 'material-ui'
|
||||
import {Table, TableBody, TableFooter, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
|
||||
|
||||
import ReportProblem from 'material-ui/svg-icons/action/report-problem';
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import ActionQuestionAnswer from 'material-ui/svg-icons/action/question-answer';
|
||||
|
||||
import IntenStore from '../../../stores/IntentStore';
|
||||
import IntentActions from '../../../actions/IntentActions';
|
||||
|
||||
|
||||
var styles = {
|
||||
item: {
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
},
|
||||
timestamp: {
|
||||
width: '30%'
|
||||
},
|
||||
count: {
|
||||
width: '10%',
|
||||
},
|
||||
message: {
|
||||
width: '60%',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap'
|
||||
}
|
||||
}
|
||||
|
||||
class IntentConversations extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [IntenStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return IntenStore.getState();
|
||||
}
|
||||
|
||||
handleClose() {
|
||||
IntentActions.selectIntent(null);
|
||||
}
|
||||
|
||||
handleCoversationClick(conversation) {
|
||||
IntentActions.selectConversation(conversation.conversation);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectedIntent, intentConversations, selectedConversation } = this.props;
|
||||
|
||||
// Display tabs for all messages or grouped by type
|
||||
var dialogActions = [
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.handleClose}
|
||||
/>,
|
||||
]
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
title={`Conversations for ${selectedIntent}`}
|
||||
modal={false}
|
||||
open={!!selectedIntent && !selectedConversation}
|
||||
actions={dialogActions}
|
||||
autoScrollBodyContent={true}
|
||||
onRequestClose={this.handleClose}>
|
||||
<Table selectable={false} multiSelectable={false}>
|
||||
<TableHeader
|
||||
displaySelectAll={false}
|
||||
adjustForCheckbox={false}
|
||||
enableSelectAll={false}>
|
||||
<TableRow>
|
||||
<TableHeaderColumn style={styles.message}>Conversations</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.count}>Count</TableHeaderColumn>
|
||||
<TableHeaderColumn style={styles.timestamp}></TableHeaderColumn>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody displayRowCheckbox={false}>
|
||||
{intentConversations.map( (conversation, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableRowColumn style={styles.message}>{conversation.conversation}</TableRowColumn>
|
||||
<TableRowColumn style={styles.count}>{conversation.count}</TableRowColumn>
|
||||
<TableRowColumn style={styles.timestamp}>
|
||||
<IconButton onClick={this.handleCoversationClick.bind(this, conversation)}><ActionQuestionAnswer /></IconButton>
|
||||
</TableRowColumn>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(IntentConversations);
|
|
@ -1,67 +0,0 @@
|
|||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import {BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
|
||||
import colors from '../colors';
|
||||
import styles from '../styles';
|
||||
|
||||
import ConversationMessages from './ConversationMessages';
|
||||
import IntentConversations from './IntentConversations';
|
||||
import IntentStore from '../../../stores/IntentStore';
|
||||
import IntentActions from '../../../actions/IntentActions';
|
||||
|
||||
class IntentsGraph extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [IntentStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return IntentStore.getState();
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(data, index) {
|
||||
const { intents } = this.props;
|
||||
IntentActions.selectIntent(intents[index].intent);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intents } = this.props;
|
||||
|
||||
if (intents.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Card className='dash-card'>
|
||||
<CardHeader
|
||||
style={styles.cardHeaderStyle}
|
||||
title="Intents"
|
||||
subtitle="Which intents are used" />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<ResponsiveContainer>
|
||||
<BarChart width={520} height={240} data={intents}
|
||||
margin={{top: 5, right: 30, left: 20, bottom: 5}}>
|
||||
<XAxis dataKey="intent"/>
|
||||
<YAxis/>
|
||||
<CartesianGrid strokeDasharray="3 3"/>
|
||||
<Tooltip/>
|
||||
<Bar dataKey="count" fill={colors.IntentsColor} onClick={this.handleClick}/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</CardMedia>
|
||||
<IntentConversations/>
|
||||
<ConversationMessages />
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(IntentsGraph);
|
|
@ -1,122 +0,0 @@
|
|||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import React, { Component } from 'react';
|
||||
import _ from 'lodash';
|
||||
import { PieChart, Pie, Sector, Cell, Legend } from 'recharts';
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
|
||||
import colors from '../colors';
|
||||
import styles from '../styles';
|
||||
|
||||
import SentimentStore from '../../../stores/SentimentStore';
|
||||
|
||||
class SentimentPie extends Component {
|
||||
|
||||
static getStores() {
|
||||
return [SentimentStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return SentimentStore.getState();
|
||||
}
|
||||
|
||||
renderActiveShape = (props) => {
|
||||
const { mode } = this.props;
|
||||
|
||||
const RADIAN = Math.PI / 180;
|
||||
const { name, cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle,
|
||||
fill, payload, percent, value } = props;
|
||||
const sin = Math.sin(-RADIAN * midAngle);
|
||||
const cos = Math.cos(-RADIAN * midAngle);
|
||||
const sx = cx + (outerRadius + 10) * cos;
|
||||
const sy = cy + (outerRadius + 10) * sin;
|
||||
const mx = cx + (outerRadius + 30) * cos;
|
||||
const my = cy + (outerRadius + 30) * sin;
|
||||
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
const ey = my;
|
||||
const textAnchor = cos >= 0 ? 'start' : 'end';
|
||||
|
||||
var c = {};
|
||||
c.midAngle = 54.11764705882353;
|
||||
c.sin = Math.sin(-RADIAN * c.midAngle);
|
||||
c.cos = Math.cos(-RADIAN * c.midAngle);
|
||||
c.cx = cx;
|
||||
c.cy = cy;
|
||||
c.sx = cx + (outerRadius + 10) * c.cos;
|
||||
c.sy = cy + (outerRadius + 10) * c.sin;
|
||||
c.mx = cx + (outerRadius + 30) * c.cos;
|
||||
c.my = cy + (outerRadius + 30) * c.sin;
|
||||
c.ex = c.mx + (c.cos >= 0 ? 1 : -1) * 22;
|
||||
c.ey = c.my;
|
||||
c.textAnchor = 'start'
|
||||
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius}
|
||||
outerRadius={outerRadius}
|
||||
startAngle={startAngle}
|
||||
endAngle={endAngle}
|
||||
fill={fill}
|
||||
/>
|
||||
<Sector
|
||||
cx={c.cx}
|
||||
cy={c.cy}
|
||||
startAngle={300}
|
||||
endAngle={60}
|
||||
innerRadius={outerRadius + 6}
|
||||
outerRadius={outerRadius + 10}
|
||||
fill={fill}
|
||||
/>
|
||||
<path d={`M${c.sx},${c.sy}L${c.mx},${c.my}L${c.ex},${c.ey}`} stroke={fill} fill="none"/>
|
||||
<circle cx={c.ex} cy={c.ey} r={2} fill={fill} stroke="none"/>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} textAnchor={c.textAnchor} fill="#333">{`${value}%`}</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { sentiments } = this.props;
|
||||
|
||||
if (!sentiments || !sentiments.length || isNaN(sentiments[0].sentiment)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var values = [
|
||||
{ name: 'Positive', value: Math.round(sentiments[0].sentiment * 100) },
|
||||
{ name: 'Negative', value: Math.round((1 - sentiments[0].sentiment) * 100) },
|
||||
];
|
||||
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (
|
||||
<Card className='dash-card'>
|
||||
<CardHeader
|
||||
className='card-header'
|
||||
title="Sentiment"
|
||||
subtitle={`Sentiment analysis`} />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<PieChart width={500} height={240}>
|
||||
<Pie
|
||||
data={values}
|
||||
cx={270}
|
||||
cy={120}
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
fill="#8884d8"
|
||||
activeIndex={0}
|
||||
activeShape={this.renderActiveShape.bind(this)}
|
||||
paddingAngle={0}>
|
||||
<Cell key={0} fill={colors.PositiveColor}/>
|
||||
<Cell key={1} fill={colors.NeutralColor}/>
|
||||
</Pie>
|
||||
<Legend wrapperStyle={{ marginLeft: 70 }} />
|
||||
</PieChart>
|
||||
</CardMedia>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(SentimentPie);
|
|
@ -1,129 +0,0 @@
|
|||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import React, { Component } from 'react';
|
||||
import moment from 'moment';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
import {LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer} from 'recharts';
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
|
||||
import Dialog from 'material-ui/Dialog';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import PersonIcon from 'material-ui/svg-icons/social/person';
|
||||
import ActionQuestionAnswer from 'material-ui/svg-icons/action/question-answer';
|
||||
|
||||
import colors from '../colors';
|
||||
import styles from '../styles';
|
||||
var { ThemeColors } = colors;
|
||||
|
||||
import TimelineStore from '../../../stores/TimelineStore';
|
||||
import TimelineActions from '../../../actions/TimelineActions';
|
||||
|
||||
class Timeline extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [TimelineStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return TimelineStore.getState();
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
handleClick(data, index) {
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
TimelineActions.dismissError();
|
||||
}
|
||||
|
||||
dateFormat (time) {
|
||||
return moment(time).format('MMM-DD');
|
||||
}
|
||||
|
||||
hourFormat (time) {
|
||||
return moment(time).format('HH:mm');
|
||||
}
|
||||
|
||||
channelChecked(channel) {
|
||||
TimelineActions.toggleChannel(channel);
|
||||
}
|
||||
|
||||
changeMode(mode) {
|
||||
TimelineActions.updateMode(mode);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data, timespan, channels, excluded, mode } = this.props;
|
||||
|
||||
const actions = [
|
||||
<FlatButton
|
||||
label="Cancel"
|
||||
primary={true}
|
||||
onTouchTap={this.handleClose}
|
||||
/>
|
||||
];
|
||||
|
||||
var format = timespan === "24 hours" ? this.hourFormat : this.dateFormat;
|
||||
|
||||
var lines = [];
|
||||
if (data && data.length) {
|
||||
lines = _.without(channels, ...excluded).map((channel, idx) => {
|
||||
return <Line key={idx} type="monotone" dataKey={channel} stroke={ThemeColors[idx]} dot={false} ticksCount={5}/>
|
||||
})
|
||||
}
|
||||
|
||||
var titleControls = (
|
||||
<div>
|
||||
<span key={0}>Channel Usage</span>,
|
||||
<IconButton
|
||||
key={1}
|
||||
tooltip='Show data by messages'
|
||||
className={classnames(mode == 'messages' && 'active')}
|
||||
onClick={this.changeMode.bind(this, 'messages')}><ActionQuestionAnswer /></IconButton>,
|
||||
<IconButton
|
||||
key={2}
|
||||
tooltip='Show data by users'
|
||||
className={classnames(mode == 'users' && 'active')}
|
||||
onClick={this.changeMode.bind(this, 'users')}><PersonIcon /></IconButton>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card className='dash-card'>
|
||||
<CardHeader
|
||||
className='card-header'
|
||||
title={titleControls}
|
||||
subtitle="How many messages were send in each channel" />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<ResponsiveContainer>
|
||||
<LineChart data={data} margin={{top: 5, right: 30, left: 20, bottom: 5}}
|
||||
onClick={this.handleClick}>
|
||||
<XAxis dataKey="time" tickFormatter={format} minTickGap={20}/>
|
||||
<YAxis type="number" domain={['dataMin', 'dataMax']}/>
|
||||
<CartesianGrid strokeDasharray="3 3"/>
|
||||
<Tooltip />
|
||||
<Legend/>
|
||||
{lines}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
<Dialog
|
||||
actions={actions}
|
||||
modal={false}
|
||||
open={!!this.props.error}
|
||||
onRequestClose={this.handleClose}>
|
||||
{this.props.error}
|
||||
</Dialog>
|
||||
</CardMedia>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(Timeline);
|
|
@ -1,97 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import FlatButton from 'material-ui/FlatButton';
|
||||
import Checkbox from 'material-ui/Checkbox';
|
||||
import IconButton from 'material-ui/IconButton';
|
||||
import SelectAllIcon from 'material-ui/svg-icons/toggle/check-box';
|
||||
import SelectNoneIcon from 'material-ui/svg-icons/toggle/check-box-outline-blank';
|
||||
import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';
|
||||
|
||||
import TimespanStore from '../../stores/TimespanStore';
|
||||
import TimespanActions from '../../actions/TimespanActions';
|
||||
|
||||
import './style.css';
|
||||
|
||||
const styles = {
|
||||
channels: {
|
||||
width: 'auto',
|
||||
display: 'inline-block',
|
||||
marginLeft: '20px'
|
||||
}
|
||||
}
|
||||
|
||||
class Timespan extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static getStores() {
|
||||
return [TimespanStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return TimespanStore.getState();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
TimespanActions.update24Hours();
|
||||
}
|
||||
|
||||
onTimespanChange(timespan) {
|
||||
switch(timespan) {
|
||||
case "1 week":
|
||||
return TimespanActions.update1Week();
|
||||
case "1 month":
|
||||
return TimespanActions.update1Month();
|
||||
default:
|
||||
return TimespanActions.update24Hours();
|
||||
}
|
||||
}
|
||||
|
||||
channelChecked(channel) {
|
||||
TimespanActions.toggleChannel(channel);
|
||||
}
|
||||
|
||||
toggleAllChannel(on) {
|
||||
TimespanActions.toggleAllChannel(on);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { timespan, channels, excluded } = this.props;
|
||||
|
||||
var buttons = ["24 hours", "1 week", "1 month"].map((time, idx) => {
|
||||
return <FlatButton key={idx} label={time} primary={time === timespan} onClick={this.onTimespanChange.bind(null, time)} />
|
||||
})
|
||||
|
||||
var channelBoxes = channels.map(channel => {
|
||||
return <Checkbox
|
||||
key={channel}
|
||||
style={styles.channels}
|
||||
label={channel}
|
||||
checked={!excluded.includes(channel)}
|
||||
onCheck={this.channelChecked.bind(null, channel)} />
|
||||
});
|
||||
|
||||
var channelActions = channels.length === 0 ? [] : [
|
||||
<IconButton key='sel-all' tooltip="Select all channels" onClick={this.toggleAllChannel.bind(null, true)}>
|
||||
`<SelectAllIcon color='#555' />
|
||||
</IconButton>,
|
||||
<IconButton key='sel-non' tooltip="Select no channels" onClick={this.toggleAllChannel.bind(null, false)}>
|
||||
<SelectNoneIcon color='#555' />
|
||||
</IconButton>
|
||||
];
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
<ToolbarGroup firstChild={true}>
|
||||
{buttons}
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
{channelActions}
|
||||
{channelBoxes}
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(Timespan);
|
|
@ -1,67 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import connectToStores from 'alt-utils/lib/connectToStores';
|
||||
import moment from 'moment';
|
||||
|
||||
import {Card, CardHeader, CardMedia} from 'material-ui/Card';
|
||||
import {List, ListItem} from 'material-ui/List';
|
||||
import PersonIcon from 'material-ui/svg-icons/social/person';
|
||||
import Divider from 'material-ui/Divider';
|
||||
|
||||
import colors from './colors';
|
||||
import styles from './styles';
|
||||
|
||||
import UsersStore from '../../stores/UsersStore';
|
||||
import commonActions from '../../actions/actions-common';
|
||||
|
||||
class Users extends Component {
|
||||
|
||||
static getStores() {
|
||||
return [UsersStore];
|
||||
}
|
||||
|
||||
static getPropsFromStores() {
|
||||
return UsersStore.getState();
|
||||
}
|
||||
|
||||
median(array, key) {
|
||||
if (!array.length) {return 0};
|
||||
var numbers = array.slice(0).sort((a,b) =>a[key] - b[key]);
|
||||
var middle = Math.floor(numbers.length / 2);
|
||||
var isEven = numbers.length % 2 === 0;
|
||||
return isEven ? (numbers[middle][key] + numbers[middle - 1][key]) / 2 : numbers[middle][key];
|
||||
}
|
||||
|
||||
render() {
|
||||
const { users, timespan } = this.props;
|
||||
|
||||
var startDate = moment(commonActions.timespanStartDate(timespan));
|
||||
var active = users.filter(user => user.maxTimestamp.isAfter(startDate));
|
||||
|
||||
var newUsers = active.filter(user => user.count === 1);
|
||||
var totalUsers = active.length;
|
||||
var countMedian = this.median(active, 'count');
|
||||
|
||||
//if (!errorItems.length) return null;
|
||||
|
||||
return (
|
||||
<Card className='dash-card dash-card-list'>
|
||||
<CardHeader
|
||||
className='card-header'
|
||||
title='Users'
|
||||
subtitle='Active and new' />
|
||||
<CardMedia style={styles.cardMediaStyle}>
|
||||
<List className='list-compact'>
|
||||
<ListItem primaryText={`${newUsers.length} Users`} secondaryText="New users" leftIcon={<PersonIcon color={colors.PersonColor} />}/>
|
||||
<ListItem primaryText={`${totalUsers} Users`} secondaryText="Total users" leftIcon={<PersonIcon color={colors.PersonColor} />}/>
|
||||
</List>
|
||||
<Divider />
|
||||
<List className='list-compact'>
|
||||
<ListItem primaryText={`${countMedian} Users`} secondaryText="MSG/USR median" leftIcon={<PersonIcon color={colors.PersonColor} />}/>
|
||||
</List>
|
||||
</CardMedia>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connectToStores(Users);
|
|
@ -1,31 +0,0 @@
|
|||
import * as colors from 'material-ui/styles/colors';
|
||||
|
||||
var ThemeColors = [
|
||||
colors.pink800,
|
||||
colors.purple800,
|
||||
colors.cyan800,
|
||||
colors.red800,
|
||||
colors.blue800,
|
||||
colors.lightBlue800,
|
||||
colors.deepPurple800,
|
||||
colors.lime800,
|
||||
colors.teal800
|
||||
];
|
||||
|
||||
export default {
|
||||
ThemeColors,
|
||||
|
||||
DangerColor: colors.red500,
|
||||
PersonColor: colors.teal700,
|
||||
IntentsColor: colors.tealA700,
|
||||
|
||||
GoodColor: colors.lightBlue700,
|
||||
BadColor: colors.red700,
|
||||
|
||||
PositiveColor: colors.lightBlue700,
|
||||
NeutralColor: colors.grey500,
|
||||
|
||||
getColor: (idx) => {
|
||||
return ThemeColors[idx];
|
||||
}
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import _ from 'lodash';
|
||||
|
||||
//import {Container, Grid, Breakpoint, Span} from 'react-responsive-grid'
|
||||
import ReactGridLayout from 'react-grid-layout';
|
||||
var ResponsiveReactGridLayout = ReactGridLayout.Responsive;
|
||||
var WidthProvider = ReactGridLayout.WidthProvider;
|
||||
ResponsiveReactGridLayout = WidthProvider(ResponsiveReactGridLayout);
|
||||
|
||||
import Timeline from './Graphs/Timeline';
|
||||
import ChannelsPie from './Graphs/ChannelsPie';
|
||||
import IntentsGraph from './Graphs/IntentsGraph';
|
||||
import ConversionPie from './Graphs/ConversionPie';
|
||||
import SentimentPie from './Graphs/SentimentPie';
|
||||
import Timespan from './Timespan';
|
||||
import Errors from './Errors';
|
||||
import Users from './Users';
|
||||
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css'
|
||||
import './style.css';
|
||||
|
||||
function generateLayouts() {
|
||||
|
||||
return {
|
||||
lg: [
|
||||
{ "i": "timeline", "x": 0, "y": 8, "w": 5, "h": 8 },
|
||||
{ "i": "channels", "x": 5, "y": 8, "w": 3, "h": 8 },
|
||||
{ "i": "errors", "x": 8, "y": 8, "w": 2, "h": 8 },
|
||||
{ "i": "users", "x": 10, "y": 8, "w": 2, "h": 8 },
|
||||
{ "i": "intents", "x": 0, "y": 16, "w": 4, "h": 8 },
|
||||
{ "i": "conversions", "x": 4, "y": 16, "w": 4, "h": 8 },
|
||||
{ "i": "sentiments", "x": 8, "y": 16, "w": 4, "h": 8 }
|
||||
],
|
||||
md: [
|
||||
{ "x": 0, "y": 8, "w": 5, "h": 8, "i": "0" },
|
||||
{ "x": 5, "y": 8, "w": 5, "h": 8, "i": "1" },
|
||||
{ "x": 10, "y": 8, "w": 2, "h": 8, "i": "2" },
|
||||
{ "x": 0, "y": 16, "w": 5, "h": 8, "i": "3" }
|
||||
],
|
||||
sm: [
|
||||
{ "x": 0, "y": 8, "w": 5, "h": 8, "i": "0" },
|
||||
{ "x": 5, "y": 8, "w": 5, "h": 8, "i": "1" },
|
||||
{ "x": 10, "y": 8, "w": 2, "h": 8, "i": "2" },
|
||||
{ "x": 0, "y": 16, "w": 5, "h": 8, "i": "3" }
|
||||
],
|
||||
xs: [
|
||||
{ "x": 0, "y": 8, "w": 5, "h": 8, "i": "0" },
|
||||
{ "x": 5, "y": 8, "w": 5, "h": 8, "i": "1" },
|
||||
{ "x": 10, "y": 8, "w": 2, "h": 8, "i": "2" },
|
||||
{ "x": 0, "y": 16, "w": 5, "h": 8, "i": "3" }
|
||||
],
|
||||
xxs: [
|
||||
{ "x": 0, "y": 8, "w": 5, "h": 8, "i": "0" },
|
||||
{ "x": 5, "y": 8, "w": 5, "h": 8, "i": "1" },
|
||||
{ "x": 10, "y": 8, "w": 2, "h": 8, "i": "2" },
|
||||
{ "x": 0, "y": 16, "w": 5, "h": 8, "i": "3" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default class Dashboard extends Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
|
||||
static defaultProps = {
|
||||
grid: {
|
||||
className: "layout",
|
||||
rowHeight: 30,
|
||||
cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
|
||||
breakpoints: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0},
|
||||
layouts: generateLayouts()
|
||||
}
|
||||
};
|
||||
|
||||
state = {
|
||||
currentBreakpoint: 'lg',
|
||||
mounted: false,
|
||||
layouts: {lg: this.props.initialLayout},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({mounted: true});
|
||||
}
|
||||
|
||||
onBreakpointChange = (breakpoint) => {
|
||||
this.setState({
|
||||
currentBreakpoint: breakpoint
|
||||
});
|
||||
};
|
||||
|
||||
onLayoutChange = (layout, layouts) => {
|
||||
//this.props.onLayoutChange(layout, layouts);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classnames('Dashboard', className)}>
|
||||
<Timespan />
|
||||
<ResponsiveReactGridLayout
|
||||
{...this.props.grid}
|
||||
onBreakpointChange={this.onBreakpointChange}
|
||||
onLayoutChange={this.onLayoutChange}
|
||||
// WidthProvider option
|
||||
measureBeforeMount={false}
|
||||
// I like to have it animate on mount. If you don't, delete `useCSSTransforms` (it's default `true`)
|
||||
// and set `measureBeforeMount={true}`.
|
||||
useCSSTransforms={this.state.mounted}>
|
||||
<div key='timeline'><Timeline /></div>
|
||||
<div key='channels'><ChannelsPie /></div>
|
||||
<div key='errors'><Errors /></div>
|
||||
<div key='users'><Users /></div>
|
||||
<div key='intents'><IntentsGraph /></div>
|
||||
<div key='conversions'><ConversionPie /></div>
|
||||
<div key='sentiments'><SentimentPie /></div>
|
||||
</ResponsiveReactGridLayout>
|
||||
</div>
|
||||
)
|
||||
|
||||
// return (
|
||||
// <Timespan />
|
||||
// <Grid columns={12} className='dashboard-grid'>
|
||||
// <Breakpoint minWidth={1600}>
|
||||
// <Span columns={5}><Timeline /></Span>
|
||||
|
||||
// <Span columns={5}><ChannelsPie /></Span>
|
||||
|
||||
// <Span columns={2} last><Errors /></Span>
|
||||
// </Breakpoint>
|
||||
|
||||
// <Breakpoint minWidth={1200} maxWidth={1600}>
|
||||
// <Span columns={6}><Timeline /></Span>
|
||||
|
||||
// <Span columns={6} last><ChannelsPie /></Span>
|
||||
|
||||
// <Span columns={3}><Errors /></Span>
|
||||
// </Breakpoint>
|
||||
|
||||
// <Breakpoint maxWidth={1200} widthMethod="componentWidth">
|
||||
// <Span columns={12}><Timeline /></Span>
|
||||
|
||||
// <Span columns={12}><ChannelsPie /></Span>
|
||||
|
||||
// <Span columns={6}><Errors /></Span>
|
||||
// </Breakpoint>
|
||||
// </Grid>
|
||||
// </div>
|
||||
// );
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
[
|
||||
{
|
||||
"x": 0,
|
||||
"y": 8,
|
||||
"w": 4,
|
||||
"h": 8,
|
||||
"i": "0"
|
||||
},
|
||||
{
|
||||
"x": 4,
|
||||
"y": 8,
|
||||
"w": 4,
|
||||
"h": 8,
|
||||
"i": "1"
|
||||
},
|
||||
{
|
||||
"x": 8,
|
||||
"y": 8,
|
||||
"w": 2,
|
||||
"h": 8,
|
||||
"i": "2"
|
||||
},
|
||||
{
|
||||
"x": 10,
|
||||
"y": 8,
|
||||
"w": 2,
|
||||
"h": 8,
|
||||
"i": "3"
|
||||
}
|
||||
]
|
|
@ -1,63 +0,0 @@
|
|||
.purpleStyle {
|
||||
border: '2px solid purple';
|
||||
color: 'purple';
|
||||
height: '10rem';
|
||||
font-size: '2rem';
|
||||
line-height: '10rem';
|
||||
margin-bottom: '1rem';
|
||||
text-align: 'center';
|
||||
}
|
||||
|
||||
.dashboard-grid > div > div > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dash-card {
|
||||
padding-bottom: 5px;
|
||||
height: 100%;
|
||||
}
|
||||
.dash-card > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dash-card > div > div > div {
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dash-card .card-header {
|
||||
height: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.dash-card .card-header div {
|
||||
padding-right: 0 !important;
|
||||
width: 100%;
|
||||
}
|
||||
.dash-card .card-header button {
|
||||
top: 0;
|
||||
right: 0;
|
||||
float: right;
|
||||
margin-top: -10px !important;
|
||||
margin-bottom: -10px !important;
|
||||
}
|
||||
|
||||
.dash-card .card-header button svg {
|
||||
color: rgb(85, 85, 85) !important;
|
||||
}
|
||||
|
||||
.dash-card .card-header button.active svg {
|
||||
color: rgb(106, 27, 154) !important;
|
||||
}
|
||||
|
||||
.dash-card .card-header button:hover svg {
|
||||
color: blueviolet !important;
|
||||
}
|
||||
|
||||
.dash-card-list {
|
||||
padding-top: 0px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.list-compact {
|
||||
padding: 7px 0 !important;
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
export default {
|
||||
cardMediaStyle: {
|
||||
height: 240
|
||||
},
|
||||
cardHeaderStyle: {
|
||||
height: 'auto'
|
||||
}
|
||||
}
|
|
@ -1,55 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const _ = require("lodash");
|
||||
const plugins_1 = require("./generic/plugins");
|
||||
class ElementConnector {
|
||||
static loadLayoutFromDashboard(elementsContainer, dashboard) {
|
||||
var layouts = {};
|
||||
_.each(dashboard.config.layout.cols, (totalColumns, key) => {
|
||||
var curCol = 0;
|
||||
var curRowOffset = 0;
|
||||
var maxRowHeight = 0;
|
||||
// Go over all elements in the dashboard and check their size
|
||||
elementsContainer.elements.forEach(element => {
|
||||
var { id, size } = element;
|
||||
if (curCol > 0 && (curCol + size.w) >= totalColumns) {
|
||||
curCol = 0;
|
||||
curRowOffset = maxRowHeight;
|
||||
}
|
||||
layouts[key] = layouts[key] || [];
|
||||
layouts[key].push({
|
||||
"i": id,
|
||||
"x": curCol,
|
||||
"y": curRowOffset + size.h,
|
||||
"w": size.w,
|
||||
"h": size.h
|
||||
});
|
||||
curCol += size.w;
|
||||
maxRowHeight = Math.max(curRowOffset + size.h, maxRowHeight);
|
||||
});
|
||||
});
|
||||
return layouts;
|
||||
}
|
||||
static loadElementsFromDashboard(dashboard, layout) {
|
||||
var elements = [];
|
||||
dashboard.elements.forEach((element, idx) => {
|
||||
var ReactElement = plugins_1.default[element.type];
|
||||
var { id, dependencies, actions, props, title, subtitle, size, theme } = element;
|
||||
var layoutProps = _.find(layout, { "i": id });
|
||||
elements.push(<div key={id}>
|
||||
<ReactElement key={idx} dependencies={dependencies} actions={actions || {}} props={props || {}} title={title} subtitle={subtitle} layout={layoutProps} theme={theme}/>
|
||||
</div>);
|
||||
});
|
||||
return elements;
|
||||
}
|
||||
static loadFiltersFromDashboard(dashboard) {
|
||||
var filters = [];
|
||||
var additionalFilters = [];
|
||||
dashboard.filters.forEach((element, idx) => {
|
||||
var ReactElement = plugins_1.default[element.type];
|
||||
(element.first ? filters : additionalFilters).push(<ReactElement key={idx} dependencies={element.dependencies} actions={element.actions}/>);
|
||||
});
|
||||
return { filters, additionalFilters };
|
||||
}
|
||||
}
|
||||
exports.default = ElementConnector;
|
|
@ -1,29 +0,0 @@
|
|||
import React, { Component } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import './style.css';
|
||||
|
||||
class Home extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedFoods: [],
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
return (
|
||||
<div className={classnames('Home', className)} >
|
||||
<div className='Home-header ui text container'>
|
||||
<h1>Bots Dashboard</h1>
|
||||
<p>Move to the <b>Dashboard</b> tab to see bot analytics</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Home;
|
|
@ -1,3 +0,0 @@
|
|||
.Home {
|
||||
margin-top: 7em;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
var __rest = (this && this.__rest) || function (s, e) {
|
||||
var t = {};
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||||
t[p] = s[p];
|
||||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
|
||||
t[p[i]] = s[p[i]];
|
||||
return t;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const react_router_1 = require("react-router");
|
||||
class NavigationLink extends React.PureComponent {
|
||||
// NOTE: Don't try using Stateless (function) component here. `ref` is
|
||||
// required by React-MD/AccessibleFakeButton, but Stateless components
|
||||
// don't have one by design:
|
||||
// https://github.com/facebook/react/issues/4936
|
||||
render() {
|
||||
const _a = this.props, { href, as, children } = _a, _props = __rest(_a, ["href", "as", "children"]);
|
||||
return (<div {..._props} style={{ padding: 0 }}>
|
||||
<react_router_1.Link href={href} as={as}>
|
||||
<a className='md-list-tile md-list-tile--mini' style={{ width: '100%', overflow: 'hidden' }}>
|
||||
{children}
|
||||
</a>
|
||||
</react_router_1.Link>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
exports.default = NavigationLink;
|
|
@ -10,7 +10,7 @@ export default class NavigationLink extends React.PureComponent<any, any> {
|
|||
const { href, as, children, ..._props } = this.props
|
||||
return (
|
||||
<div {..._props} style={{padding: 0}}>
|
||||
<Link href={href} as={as}>
|
||||
<Link href={href} to={null}>
|
||||
<a className='md-list-tile md-list-tile--mini' style={{width: '100%', overflow: 'hidden'}}>
|
||||
{children}
|
||||
</a>
|
|
@ -1,31 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const NavigationDrawers_1 = require("react-md/lib/NavigationDrawers");
|
||||
const FontIcons_1 = require("react-md/lib/FontIcons");
|
||||
const ListItem_1 = require("react-md/lib/Lists/ListItem");
|
||||
const Avatars_1 = require("react-md/lib/Avatars");
|
||||
const SelectFields_1 = require("react-md/lib/SelectFields");
|
||||
const react_router_1 = require("react-router");
|
||||
require("./style.css");
|
||||
const avatarSrc = 'https://cloud.githubusercontent.com/assets/13041/19686250/971bf7f8-9ac0-11e6-975c-188defd82df1.png';
|
||||
const drawerHeaderChildren = [
|
||||
<Avatars_1.default key={avatarSrc} src={avatarSrc} role='presentation' iconSized style={{ alignSelf: 'center', marginLeft: 16, marginRight: 16, flexShrink: 0 }}/>,
|
||||
<SelectFields_1.default id='account-switcher' defaultValue='Jonathan' menuItems={['Jonathan', 'Fred']} key='account-switcher' position={SelectFields_1.default.Positions.BELOW} className='md-select-field--toolbar'/>
|
||||
];
|
||||
exports.default = ({ children = null, title = 'Bot Framework Dashboard' }) => {
|
||||
var pathname = '/';
|
||||
try {
|
||||
pathname = window.location.pathname;
|
||||
}
|
||||
catch (e) { }
|
||||
var navigationItems = [
|
||||
<ListItem_1.default key='0' component={react_router_1.Link} href='/' active={pathname == '/'} leftIcon={<FontIcons_1.default>home</FontIcons_1.default>} tileClassName='md-list-tile--mini' primaryText={'Home'}/>,
|
||||
<ListItem_1.default key='1' component={react_router_1.Link} href='/about' active={pathname == '/about'} leftIcon={<FontIcons_1.default>info</FontIcons_1.default>} tileClassName='md-list-tile--mini' primaryText={'About'}/>,
|
||||
<ListItem_1.default key='2' component={react_router_1.Link} href='/dashboard' active={pathname == '/dashboard'} leftIcon={<FontIcons_1.default>dashboard</FontIcons_1.default>} tileClassName='md-list-tile--mini' primaryText={'Dashboard'}/>
|
||||
];
|
||||
return (<div>
|
||||
<NavigationDrawers_1.default navItems={navigationItems} contentClassName='md-grid' drawerHeaderChildren={drawerHeaderChildren} mobileDrawerType={NavigationDrawers_1.default.DrawerTypes.TEMPORARY_MINI} tabletDrawerType={NavigationDrawers_1.default.DrawerTypes.TEMPORARY_MINI} desktopDrawerType={NavigationDrawers_1.default.DrawerTypes.TEMPORARY_MINI} toolbarTitle={title} toolbarActions={null}>
|
||||
{children}
|
||||
</NavigationDrawers_1.default>
|
||||
</div>);
|
||||
};
|
|
@ -1,36 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
//import * as colors from 'material-ui/styles/colors';
|
||||
const colors = require("material-colors");
|
||||
var ThemeColors = [
|
||||
colors.pink[800],
|
||||
colors.purple[800],
|
||||
colors.cyan[800],
|
||||
colors.red[800],
|
||||
colors.blue[800],
|
||||
colors.lightBlue[800],
|
||||
colors.deepPurple[800],
|
||||
colors.lime[800],
|
||||
colors.teal[800]
|
||||
];
|
||||
var ThemeColors2 = ThemeColors.slice().reverse();
|
||||
const DangerColor = colors.red[500];
|
||||
const PersonColor = colors.teal[700];
|
||||
const IntentsColor = colors.teal['a700'];
|
||||
const GoodColor = colors.lightBlue[700];
|
||||
const BadColor = colors.red[700];
|
||||
const PositiveColor = colors.lightBlue[700];
|
||||
const NeutralColor = colors.grey[500];
|
||||
exports.default = {
|
||||
ThemeColors,
|
||||
ThemeColors2,
|
||||
DangerColor,
|
||||
PersonColor,
|
||||
IntentsColor,
|
||||
GoodColor,
|
||||
BadColor,
|
||||
PositiveColor,
|
||||
NeutralColor,
|
||||
getColor: (idx) => {
|
||||
return ThemeColors[idx];
|
||||
}
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("./GenericComponent");
|
||||
const Card_1 = require("../Card");
|
||||
const recharts_1 = require("recharts");
|
||||
const colors_1 = require("../colors");
|
||||
var { ThemeColors } = colors_1.default;
|
||||
;
|
||||
class BarData extends GenericComponent_1.GenericComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
values: [],
|
||||
bars: []
|
||||
};
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
handleClick(data, index) {
|
||||
this.trigger('onBarClick', data.payload);
|
||||
}
|
||||
render() {
|
||||
var { values, bars } = this.state;
|
||||
var { title, subtitle, props } = this.props;
|
||||
var { barProps, showLegend, nameKey } = props;
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
var barElements = [];
|
||||
if (values && values.length && bars) {
|
||||
barElements = bars.map((bar, idx) => {
|
||||
return <recharts_1.Bar key={idx} dataKey={bar} fill={ThemeColors[idx]} onClick={this.handleClick}/>;
|
||||
});
|
||||
}
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (<Card_1.default title={title} subtitle={subtitle}>
|
||||
<recharts_1.ResponsiveContainer>
|
||||
<recharts_1.BarChart data={values} margin={{ top: 5, right: 30, left: 0, bottom: 5 }} {...barProps}>
|
||||
<recharts_1.XAxis dataKey={nameKey || ''}/>
|
||||
<recharts_1.YAxis />
|
||||
<recharts_1.CartesianGrid strokeDasharray="3 3"/>
|
||||
<recharts_1.Tooltip />
|
||||
{barElements}
|
||||
{showLegend !== false && <recharts_1.Legend layout="vertical" align="right" verticalAlign="top" wrapperStyle={{ right: 5 }}/>}
|
||||
</recharts_1.BarChart>
|
||||
</recharts_1.ResponsiveContainer>
|
||||
</Card_1.default>);
|
||||
}
|
||||
}
|
||||
exports.default = BarData;
|
|
@ -1,92 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const data_sources_1 = require("../../../data-sources");
|
||||
const ElementConnector_1 = require("../../ElementConnector");
|
||||
const DialogsActions_1 = require("./DialogsActions");
|
||||
const DialogsStore_1 = require("./DialogsStore");
|
||||
const Dialogs_1 = require("react-md/lib/Dialogs");
|
||||
const ReactGridLayout = require("react-grid-layout");
|
||||
var ResponsiveReactGridLayout = ReactGridLayout.Responsive;
|
||||
var WidthProvider = ReactGridLayout.WidthProvider;
|
||||
ResponsiveReactGridLayout = WidthProvider(ResponsiveReactGridLayout);
|
||||
class Dialog extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.layouts = {};
|
||||
this.dataSources = {};
|
||||
this.onBreakpointChange = (breakpoint) => {
|
||||
var layouts = this.state.layouts;
|
||||
layouts[breakpoint] = layouts[breakpoint] || this.layouts[breakpoint];
|
||||
this.setState({
|
||||
currentBreakpoint: breakpoint,
|
||||
layouts: layouts
|
||||
});
|
||||
};
|
||||
this.closeDialog = () => {
|
||||
DialogsActions_1.default.closeDialog();
|
||||
};
|
||||
this.openDialog = () => {
|
||||
DialogsActions_1.default.openDialog('conversations', { title: this.state.dialogArgs['title'] + ':Blue', intent: 'bla', queryTimespan: 'jjj' });
|
||||
};
|
||||
this.state = DialogsStore_1.default.getState();
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this.openDialog = this.openDialog.bind(this);
|
||||
// Create dialog data source
|
||||
var dialogDS = {
|
||||
id: 'dialog_' + this.props.dialogData.id,
|
||||
type: 'Constant',
|
||||
params: {
|
||||
selectedValue: null
|
||||
}
|
||||
};
|
||||
data_sources_1.DataSourceConnector.createDataSources({ dataSources: [dialogDS] }, this.dataSources);
|
||||
// Adding other data sources
|
||||
data_sources_1.DataSourceConnector.createDataSources(this.props.dialogData, this.dataSources);
|
||||
var layouts = ElementConnector_1.default.loadLayoutFromDashboard(this.props.dialogData, this.props.dashboard);
|
||||
this.layouts = layouts;
|
||||
this.state.mounted = false;
|
||||
this.state.currentBreakpoint = 'lg';
|
||||
this.state.layouts = { lg: layouts['lg'] };
|
||||
}
|
||||
componentDidMount() {
|
||||
this.setState({ mounted: true });
|
||||
DialogsStore_1.default.listen(this.onChange);
|
||||
data_sources_1.DataSourceConnector.connectDataSources(this.dataSources);
|
||||
}
|
||||
componentDidUpdate() {
|
||||
const { dialogData } = this.props;
|
||||
var { dialogId, dialogArgs } = this.state;
|
||||
var datasourceId = 'dialog_' + dialogData.id;
|
||||
this.dataSources[datasourceId].action.updateDependencies(dialogArgs);
|
||||
}
|
||||
onChange(state) {
|
||||
var { dialogId, dialogArgs } = state;
|
||||
this.setState({ dialogId, dialogArgs });
|
||||
}
|
||||
render() {
|
||||
const { dialogData, dashboard } = this.props;
|
||||
const { id } = dialogData;
|
||||
const { dialogId, dialogArgs } = this.state;
|
||||
var { title } = dialogArgs || { title: '' };
|
||||
var visible = id === dialogId;
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
var { currentBreakpoint } = this.state;
|
||||
var layout = this.state.layouts[currentBreakpoint];
|
||||
// Creating visual elements
|
||||
var elements = ElementConnector_1.default.loadElementsFromDashboard(dialogData, layout);
|
||||
let grid = {
|
||||
className: "layout",
|
||||
rowHeight: dashboard.config.layout.rowHeight || 30,
|
||||
cols: dashboard.config.layout.cols,
|
||||
breakpoints: dashboard.config.layout.breakpoints
|
||||
};
|
||||
return (<Dialogs_1.default id={id} visible={visible} title={title} focusOnMount={false} onHide={this.closeDialog} dialogStyle={{ width: dialogData.width || '80%' }} contentStyle={{ padding: '0', maxHeight: 'calc(100vh - 148px)' }}>
|
||||
|
||||
{elements}
|
||||
|
||||
</Dialogs_1.default>);
|
||||
}
|
||||
}
|
||||
exports.default = Dialog;
|
|
@ -57,9 +57,7 @@ export default class Dialog extends React.PureComponent<IDialogProps, IDialogSta
|
|||
var layouts = ElementConnector.loadLayoutFromDashboard(this.props.dialogData, this.props.dashboard);
|
||||
|
||||
this.layouts = layouts;
|
||||
this.state.mounted = false;
|
||||
this.state.currentBreakpoint = 'lg';
|
||||
this.state.layouts = { lg: layouts['lg'] };
|
||||
(this.state as any).layouts = { lg: layouts['lg'] };
|
||||
}
|
||||
|
||||
componentDidMount() {
|
|
@ -1,15 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const alt_1 = require("../../../alt");
|
||||
class DialogsActions extends alt_1.AbstractActions {
|
||||
constructor(alt) {
|
||||
super(alt);
|
||||
}
|
||||
openDialog(dialogName, args) {
|
||||
return { dialogName, args };
|
||||
}
|
||||
closeDialog() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
const dialogsActions = alt_1.default.createActions(DialogsActions);
|
||||
exports.default = dialogsActions;
|
|
@ -1,30 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const alt_1 = require("../../../alt");
|
||||
const DialogsActions_1 = require("./DialogsActions");
|
||||
class DialogsStore extends alt_1.AbstractStoreModel {
|
||||
constructor() {
|
||||
super();
|
||||
this.dialogsStack = [];
|
||||
this.dialogId = null;
|
||||
this.dialogArgs = null;
|
||||
this.bindListeners({
|
||||
openDialog: DialogsActions_1.default.openDialog,
|
||||
closeDialog: DialogsActions_1.default.closeDialog
|
||||
});
|
||||
}
|
||||
openDialog(params) {
|
||||
this.dialogsStack.push(params);
|
||||
this.dialogId = params.dialogName;
|
||||
this.dialogArgs = params.args;
|
||||
}
|
||||
closeDialog() {
|
||||
this.dialogsStack.pop();
|
||||
var dialog = this.dialogsStack.length > 0 ?
|
||||
this.dialogsStack[this.dialogsStack.length - 1] :
|
||||
{ dialogName: null, args: null };
|
||||
this.dialogId = dialog.dialogName;
|
||||
this.dialogArgs = dialog.args;
|
||||
}
|
||||
}
|
||||
const dialogsStore = alt_1.default.createStore(DialogsStore, "DialogsStore");
|
||||
exports.default = dialogsStore;
|
|
@ -1,18 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const Dialog_1 = require("./Dialog");
|
||||
const DialogsActions_1 = require("./DialogsActions");
|
||||
const DialogsStore_1 = require("./DialogsStore");
|
||||
function loadDialogsFromDashboard(dashboard) {
|
||||
if (!dashboard.dialogs) {
|
||||
return null;
|
||||
}
|
||||
var dialogs = dashboard.dialogs.map((dialog, idx) => <Dialog_1.default key={idx} dialogData={dialog} dashboard={dashboard}/>);
|
||||
return dialogs;
|
||||
}
|
||||
exports.default = {
|
||||
loadDialogsFromDashboard,
|
||||
Dialog: Dialog_1.default,
|
||||
DialogsActions: DialogsActions_1.default,
|
||||
DialogsStore: DialogsStore_1.default
|
||||
};
|
|
@ -1,50 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const data_sources_1 = require("../../data-sources");
|
||||
class GenericComponent extends React.Component {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onStateChange = this.onStateChange.bind(this);
|
||||
this.trigger = this.trigger.bind(this);
|
||||
var result = data_sources_1.DataSourceConnector.extrapolateDependencies(this.props.dependencies);
|
||||
var initialState = {};
|
||||
Object.keys(result.dependencies).forEach(key => {
|
||||
initialState[key] = result.dependencies[key];
|
||||
});
|
||||
this.state = initialState;
|
||||
}
|
||||
componentDidMount() {
|
||||
var result = data_sources_1.DataSourceConnector.extrapolateDependencies(this.props.dependencies);
|
||||
Object.keys(result.dataSources).forEach(key => {
|
||||
result.dataSources[key].store.listen(this.onStateChange);
|
||||
});
|
||||
}
|
||||
componentWillUnmount() {
|
||||
var result = data_sources_1.DataSourceConnector.extrapolateDependencies(this.props.dependencies);
|
||||
Object.keys(result.dataSources).forEach(key => {
|
||||
result.dataSources[key].store.unlisten(this.onStateChange);
|
||||
});
|
||||
}
|
||||
onStateChange(state) {
|
||||
var result = data_sources_1.DataSourceConnector.extrapolateDependencies(this.props.dependencies);
|
||||
var updatedState = {};
|
||||
Object.keys(result.dependencies).forEach(key => {
|
||||
updatedState[key] = result.dependencies[key];
|
||||
});
|
||||
this.setState(updatedState);
|
||||
}
|
||||
trigger(actionName, args) {
|
||||
var action = this.props.actions[actionName];
|
||||
// if action was not defined, not action needed
|
||||
if (!action) {
|
||||
console.warn(`no action was found with name ${name}`);
|
||||
return;
|
||||
}
|
||||
var actionId = typeof action === 'string' ? action : action.action;
|
||||
var params = typeof action === 'string' ? {} : action.params;
|
||||
data_sources_1.DataSourceConnector.triggerAction(actionId, params, args);
|
||||
}
|
||||
}
|
||||
exports.GenericComponent = GenericComponent;
|
|
@ -1,72 +0,0 @@
|
|||
var __extends = (this && this.__extends) || (function () {
|
||||
var extendStatics = Object.setPrototypeOf ||
|
||||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
|
||||
return function (d, b) {
|
||||
extendStatics(d, b);
|
||||
function __() { this.constructor = d; }
|
||||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var React = require("react");
|
||||
var Card_1 = require("material-ui/Card");
|
||||
var recharts_1 = require("recharts");
|
||||
var moment = require("moment");
|
||||
var styles_1 = require("../styles");
|
||||
var colors = styles_1.default.colors;
|
||||
var ThemeColors = colors.ThemeColors;
|
||||
;
|
||||
;
|
||||
var Graph = (function (_super) {
|
||||
__extends(Graph, _super);
|
||||
function Graph(props) {
|
||||
var _this = _super.call(this, props) || this;
|
||||
_this.onChange = _this.onChange.bind(_this);
|
||||
_this.state = props.store.getState();
|
||||
return _this;
|
||||
}
|
||||
Graph.prototype.componentDidMount = function () {
|
||||
this.props.store.listen(this.onChange);
|
||||
};
|
||||
Graph.prototype.componentWillUnmount = function () {
|
||||
this.props.store.unlisten(this.onChange);
|
||||
};
|
||||
Graph.prototype.onChange = function (state) {
|
||||
this.setState(state);
|
||||
};
|
||||
Graph.prototype.dateFormat = function (time) {
|
||||
return moment(time).format('MMM-DD');
|
||||
};
|
||||
Graph.prototype.hourFormat = function (time) {
|
||||
return moment(time).format('HH:mm');
|
||||
};
|
||||
Graph.prototype.render = function () {
|
||||
var data = this.state[this.props.data || 'data'] || [];
|
||||
var lines = this.state[this.props.lines || 'lines'] || [];
|
||||
var format = this.state.timespan === "24 hours" ? this.hourFormat : this.dateFormat;
|
||||
var glines = [];
|
||||
if (data && data.length) {
|
||||
glines = lines.map(function (line, idx) {
|
||||
return <recharts_1.Line key={idx} type="monotone" dataKey={line} stroke={ThemeColors[idx]} dot={false} ticksCount={5}/>;
|
||||
});
|
||||
}
|
||||
return (<Card_1.Card className='dash-card'>
|
||||
<Card_1.CardHeader className='card-header' title='Users' subtitle="How many messages were send in each channel"/>
|
||||
<Card_1.CardMedia style={styles_1.default.cards.cardMediaStyle}>
|
||||
<recharts_1.ResponsiveContainer>
|
||||
<recharts_1.LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
|
||||
<recharts_1.XAxis dataKey="time" tickFormatter={format} minTickGap={20}/>
|
||||
<recharts_1.YAxis />
|
||||
<recharts_1.CartesianGrid strokeDasharray="3 3"/>
|
||||
<recharts_1.Tooltip />
|
||||
<recharts_1.Legend />
|
||||
{glines}
|
||||
</recharts_1.LineChart>
|
||||
</recharts_1.ResponsiveContainer>
|
||||
</Card_1.CardMedia>
|
||||
</Card_1.Card>);
|
||||
};
|
||||
return Graph;
|
||||
}(React.Component));
|
||||
exports.default = Graph;
|
|
@ -1,83 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("./GenericComponent");
|
||||
const Card_1 = require("../Card");
|
||||
const recharts_1 = require("recharts");
|
||||
const colors_1 = require("../colors");
|
||||
var { ThemeColors } = colors_1.default;
|
||||
;
|
||||
class PieData extends GenericComponent_1.GenericComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeIndex: 0,
|
||||
values: null
|
||||
};
|
||||
this.renderActiveShape = (props) => {
|
||||
const { mode } = this.props;
|
||||
var type = mode === 'users' ? 'Users' : 'Messages';
|
||||
const RADIAN = Math.PI / 180;
|
||||
const { name, cx, cy, midAngle, innerRadius, outerRadius, startAngle, endAngle, fill, payload, percent, value } = props;
|
||||
const sin = Math.sin(-RADIAN * midAngle);
|
||||
const cos = Math.cos(-RADIAN * midAngle);
|
||||
const sx = cx + (outerRadius + 10) * cos;
|
||||
const sy = cy + (outerRadius + 10) * sin;
|
||||
const mx = cx + (outerRadius + 30) * cos;
|
||||
const my = cy + (outerRadius + 30) * sin;
|
||||
const ex = mx + (cos >= 0 ? 1 : -1) * 22;
|
||||
const ey = my;
|
||||
const textAnchor = cos >= 0 ? 'start' : 'end';
|
||||
var c = {};
|
||||
c.midAngle = 54.11764705882353;
|
||||
c.sin = Math.sin(-RADIAN * c.midAngle);
|
||||
c.cos = Math.cos(-RADIAN * c.midAngle);
|
||||
c.cx = cx;
|
||||
c.cy = cy;
|
||||
c.sx = cx + (outerRadius + 10) * c.cos;
|
||||
c.sy = cy + (outerRadius + 10) * c.sin;
|
||||
c.mx = cx + (outerRadius + 30) * c.cos;
|
||||
c.my = cy + (outerRadius + 30) * c.sin;
|
||||
c.ex = c.mx + (c.cos >= 0 ? 1 : -1) * 22;
|
||||
c.ey = c.my;
|
||||
c.textAnchor = 'start';
|
||||
return (<g>
|
||||
<text x={cx} y={cy} dy={8} textAnchor="middle" fill={fill}>{name}</text>
|
||||
<recharts_1.Sector cx={cx} cy={cy} innerRadius={innerRadius} outerRadius={outerRadius} startAngle={startAngle} endAngle={endAngle} fill={fill}/>
|
||||
<recharts_1.Sector cx={c.cx} cy={c.cy} startAngle={300} endAngle={60} innerRadius={outerRadius + 6} outerRadius={outerRadius + 10} fill={fill}/>
|
||||
<path d={`M${c.sx},${c.sy}L${c.mx},${c.my}L${c.ex},${c.ey}`} stroke={fill} fill="none"/>
|
||||
<circle cx={c.ex} cy={c.ey} r={2} fill={fill} stroke="none"/>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} textAnchor={c.textAnchor} fill="#333">{`${value} ${type.toLowerCase()}`}</text>
|
||||
<text x={c.ex + (c.cos >= 0 ? 1 : -1) * 12} y={c.ey} dy={18} textAnchor={c.textAnchor} fill="#999">
|
||||
{`(Rate ${(percent * 100).toFixed(2)}%)`}
|
||||
</text>
|
||||
</g>);
|
||||
};
|
||||
this.onPieEnter = this.onPieEnter.bind(this);
|
||||
}
|
||||
onPieEnter(data, index) {
|
||||
this.setState({ activeIndex: index });
|
||||
}
|
||||
render() {
|
||||
var { values } = this.state;
|
||||
var { props, title, subtitle, layout, theme } = this.props;
|
||||
var { pieProps, showLegend } = props;
|
||||
if (!values) {
|
||||
return null;
|
||||
}
|
||||
var themeColors = theme || ThemeColors;
|
||||
// Todo: Receive the width of the SVG component from the container
|
||||
return (<Card_1.default title={title} subtitle={subtitle}>
|
||||
<recharts_1.ResponsiveContainer>
|
||||
<recharts_1.PieChart>
|
||||
<recharts_1.Pie data={values} cx={Math.min(layout.h / 4, layout.w) * 60} innerRadius={60} fill="#8884d8" onMouseEnter={this.onPieEnter} activeIndex={this.state.activeIndex} activeShape={this.renderActiveShape.bind(this)} paddingAngle={0} {...pieProps}>
|
||||
{values.map((entry, index) => <recharts_1.Cell key={index} fill={themeColors[index % themeColors.length]}/>)}
|
||||
<recharts_1.Cell key={0} fill={colors_1.default.GoodColor}/>
|
||||
<recharts_1.Cell key={1} fill={colors_1.default.BadColor}/>
|
||||
</recharts_1.Pie>
|
||||
{showLegend !== false && <recharts_1.Legend layout="vertical" align="right" verticalAlign="top"/>}
|
||||
</recharts_1.PieChart>
|
||||
</recharts_1.ResponsiveContainer>
|
||||
</Card_1.default>);
|
||||
}
|
||||
}
|
||||
exports.default = PieData;
|
|
@ -1,49 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("./GenericComponent");
|
||||
const Card_1 = require("../Card");
|
||||
const recharts_1 = require("recharts");
|
||||
const moment = require("moment");
|
||||
const colors_1 = require("../colors");
|
||||
var { ThemeColors } = colors_1.default;
|
||||
class Scatter extends GenericComponent_1.GenericComponent {
|
||||
dateFormat(time) {
|
||||
return moment(time).format('MMM-DD');
|
||||
}
|
||||
hourFormat(time) {
|
||||
return moment(time).format('HH:mm');
|
||||
}
|
||||
render() {
|
||||
var { title, subtitle, theme } = this.props;
|
||||
var { timeFormat, values, lines } = this.state;
|
||||
var format = timeFormat === "hour" ? this.hourFormat : this.dateFormat;
|
||||
var themeColors = theme || ThemeColors;
|
||||
const data01 = [{ x: 100, y: 200, z: 200 }, { x: 120, y: 100, z: 260 },
|
||||
{ x: 170, y: 300, z: 400 }, { x: 140, y: 250, z: 280 },
|
||||
{ x: 150, y: 400, z: 500 }, { x: 110, y: 280, z: 200 }];
|
||||
const data02 = [{ x: 200, y: 260, z: 240 }, { x: 240, y: 290, z: 220 },
|
||||
{ x: 190, y: 290, z: 250 }, { x: 198, y: 250, z: 210 },
|
||||
{ x: 180, y: 280, z: 260 }, { x: 210, y: 220, z: 230 }];
|
||||
var scatters = [];
|
||||
// if (data && data.length) {
|
||||
// scatters = lines.map((line, idx) => {
|
||||
// return <Scatter key={idx} name={line.name} dataKey={line} stroke={ThemeColors[idx]} dot={false} ticksCount={5}/>
|
||||
// })
|
||||
// }
|
||||
return (<Card_1.default title={title} subtitle={subtitle}>
|
||||
<recharts_1.ResponsiveContainer>
|
||||
<recharts_1.ScatterChart margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>
|
||||
<recharts_1.XAxis dataKey={'x'} tickFormatter={format} minTickGap={20} name='stature'/>
|
||||
<recharts_1.YAxis dataKey={'y'} name='weight' unit='kg'/>
|
||||
<recharts_1.ZAxis dataKey={'z'} range={[60, 600]} name='score' unit='km'/>
|
||||
<recharts_1.CartesianGrid strokeDasharray="3 3"/>
|
||||
<recharts_1.Tooltip cursor={{ strokeDasharray: '3 3' }}/>
|
||||
<recharts_1.Legend />
|
||||
<recharts_1.Scatter name='A school' data={data01} fill={colors_1.default.ThemeColors[0]}/>
|
||||
<recharts_1.Scatter name='B school' data={data02} fill={colors_1.default.ThemeColors[1]}/>
|
||||
</recharts_1.ScatterChart>
|
||||
</recharts_1.ResponsiveContainer>
|
||||
</Card_1.default>);
|
||||
}
|
||||
}
|
||||
exports.default = Scatter;
|
|
@ -1,25 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("./GenericComponent");
|
||||
const Media_1 = require("react-md/lib/Media");
|
||||
const Cards_1 = require("react-md/lib/Cards");
|
||||
const FontIcons_1 = require("react-md/lib/FontIcons");
|
||||
class Scorecard extends GenericComponent_1.GenericComponent {
|
||||
render() {
|
||||
var { value, icon, className } = this.state;
|
||||
var { title } = this.props;
|
||||
return (<Cards_1.Card>
|
||||
<Media_1.Media className='md-card-scorecard'>
|
||||
<div className='md-grid md-headline'>
|
||||
{icon &&
|
||||
<div className="ms-cell md-cell--middle md-cell--2 dash-icon">
|
||||
<FontIcons_1.default className={className}>{icon}</FontIcons_1.default>
|
||||
</div>}
|
||||
<div className='md-cell'>{title}</div>
|
||||
<div className='md-cell--right dash-value'>{value}</div>
|
||||
</div>
|
||||
</Media_1.Media>
|
||||
</Cards_1.Card>);
|
||||
}
|
||||
}
|
||||
exports.default = Scorecard;
|
|
@ -1,10 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const alt_1 = require("../../../alt");
|
||||
class SpinnerActions extends alt_1.AbstractActions /*implements ISpinnerActions*/ {
|
||||
constructor(alt) {
|
||||
super(alt);
|
||||
this.generateActions('startPageLoading', 'endPageLoading', 'startRequestLoading', 'endRequestLoading');
|
||||
}
|
||||
}
|
||||
const spinnerActions = alt_1.default.createActions(SpinnerActions);
|
||||
exports.default = spinnerActions;
|
|
@ -1,30 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const alt_1 = require("../../../alt");
|
||||
const SpinnerActions_1 = require("./SpinnerActions");
|
||||
class SpinnerStore extends alt_1.AbstractStoreModel {
|
||||
constructor() {
|
||||
super();
|
||||
this.pageLoading = 0;
|
||||
this.requestLoading = 0;
|
||||
this.bindListeners({
|
||||
startPageLoading: SpinnerActions_1.default.startPageLoading,
|
||||
endPageLoading: SpinnerActions_1.default.endPageLoading,
|
||||
startRequestLoading: SpinnerActions_1.default.startRequestLoading,
|
||||
endRequestLoading: SpinnerActions_1.default.endRequestLoading,
|
||||
});
|
||||
}
|
||||
startPageLoading() {
|
||||
this.pageLoading++;
|
||||
}
|
||||
endPageLoading() {
|
||||
this.pageLoading--;
|
||||
}
|
||||
startRequestLoading() {
|
||||
this.requestLoading++;
|
||||
}
|
||||
endRequestLoading() {
|
||||
this.requestLoading--;
|
||||
}
|
||||
}
|
||||
const spinnerStore = alt_1.default.createStore(SpinnerStore, "SpinnerStore");
|
||||
exports.default = spinnerStore;
|
|
@ -3,20 +3,29 @@ import alt, { AbstractStoreModel } from '../../../alt';
|
|||
import spinnerActions from './SpinnerActions';
|
||||
|
||||
export interface ISpinnerStoreState {
|
||||
pageLoading?: number,
|
||||
pageLoading?: number
|
||||
requestLoading?: number
|
||||
mounted: boolean
|
||||
currentBreakpoint: string
|
||||
layouts: object
|
||||
}
|
||||
|
||||
class SpinnerStore extends AbstractStoreModel<ISpinnerStoreState> implements ISpinnerStoreState {
|
||||
|
||||
pageLoading: number;
|
||||
requestLoading: number;
|
||||
mounted: boolean;
|
||||
currentBreakpoint: string;
|
||||
layouts: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.pageLoading = 0;
|
||||
this.requestLoading = 0;
|
||||
this.mounted = false;
|
||||
this.currentBreakpoint = 'lg';
|
||||
this.layouts = { };
|
||||
|
||||
this.bindListeners({
|
||||
startPageLoading: spinnerActions.startPageLoading,
|
|
@ -1,77 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const $ = require("jquery");
|
||||
const _ = require("lodash");
|
||||
const CircularProgress_1 = require("react-md/lib/Progress/CircularProgress");
|
||||
const Snackbars_1 = require("react-md/lib/Snackbars");
|
||||
const SpinnerStore_1 = require("./SpinnerStore");
|
||||
const SpinnerActions_1 = require("./SpinnerActions");
|
||||
class Spinner extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = SpinnerStore_1.default.getState();
|
||||
this.state.snacks = {
|
||||
toasts: [],
|
||||
autohide: true
|
||||
};
|
||||
this.onChange = this.onChange.bind(this);
|
||||
this._addToast = this._addToast.bind(this);
|
||||
this._removeToast = this._removeToast.bind(this);
|
||||
this._429ApplicationInsights = this._429ApplicationInsights.bind(this);
|
||||
var self = this;
|
||||
$.ajaxSetup({
|
||||
beforeSend: function () {
|
||||
SpinnerActions_1.default.startRequestLoading();
|
||||
},
|
||||
complete: function (response) {
|
||||
SpinnerActions_1.default.endRequestLoading();
|
||||
if (response.status === 429) {
|
||||
self._429ApplicationInsights();
|
||||
}
|
||||
}
|
||||
});
|
||||
// Todo: Add timeout to requests - if no reply received, turn spinner off
|
||||
}
|
||||
componentDidMount() {
|
||||
SpinnerStore_1.default.listen(this.onChange);
|
||||
}
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
const { snacks } = nextState;
|
||||
const [toast] = snacks.toasts;
|
||||
if (this.state.snacks.toasts === snacks.toasts || !toast) {
|
||||
return;
|
||||
}
|
||||
snacks.autohide = toast.action !== 'Retry';
|
||||
this.setState({ snacks });
|
||||
}
|
||||
_removeToast() {
|
||||
const { snacks } = this.state;
|
||||
const [, ...toasts] = snacks.toasts;
|
||||
snacks.toasts = toasts;
|
||||
this.setState({ snacks });
|
||||
}
|
||||
_429ApplicationInsights() {
|
||||
this._addToast('You have reached the maximum number of Application Insights requests.');
|
||||
}
|
||||
_addToast(text, action = null) {
|
||||
const { snacks } = this.state;
|
||||
const toasts = snacks.toasts.slice();
|
||||
if (_.find(toasts, { text })) {
|
||||
return;
|
||||
}
|
||||
toasts.push({ text, action });
|
||||
snacks.toasts = toasts;
|
||||
this.setState({ snacks });
|
||||
}
|
||||
onChange(state) {
|
||||
this.setState(state);
|
||||
}
|
||||
render() {
|
||||
let refreshing = this.state.pageLoading || this.state.requestLoading || false;
|
||||
return (<div>
|
||||
{refreshing && <CircularProgress_1.default key="progress" id="contentLoadingProgress"/>}
|
||||
<Snackbars_1.default {...this.state.snacks} onDismiss={this._removeToast}/>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
exports.default = Spinner;
|
|
@ -22,7 +22,7 @@ export default class Spinner extends React.Component<any, ISpinnerState> {
|
|||
super(props);
|
||||
|
||||
this.state = SpinnerStore.getState();
|
||||
this.state.snacks = {
|
||||
(this.state as any).snacks = {
|
||||
toasts: [],
|
||||
autohide: true
|
||||
};
|
|
@ -1,54 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("../GenericComponent");
|
||||
const moment = require("moment");
|
||||
const DataTables_1 = require("react-md/lib/DataTables");
|
||||
const Cards_1 = require("react-md/lib/Cards");
|
||||
const FontIcons_1 = require("react-md/lib/FontIcons");
|
||||
const Button_1 = require("react-md/lib/Buttons/Button");
|
||||
require("./Table.css");
|
||||
class Table extends GenericComponent_1.GenericComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
values: []
|
||||
};
|
||||
this.onButtonClick = (col, value) => {
|
||||
this.trigger(col.onClick, value);
|
||||
};
|
||||
this.onButtonClick = this.onButtonClick.bind(this);
|
||||
}
|
||||
render() {
|
||||
var { props } = this.props;
|
||||
var { checkboxes, cols } = props;
|
||||
var { values } = this.state;
|
||||
var arr = values.slice(0);
|
||||
arr = arr.concat(values);
|
||||
arr = arr.concat(values);
|
||||
arr = arr.concat(values);
|
||||
arr = arr.concat(values);
|
||||
arr = arr.concat(values);
|
||||
const rows = arr.map((value, i) => (<DataTables_1.TableRow key={i}>
|
||||
{cols.map((col, i) => <DataTables_1.TableColumn key={i}>{col.type === 'icon' ?
|
||||
<FontIcons_1.default>{col.value || value[col.field]}</FontIcons_1.default> :
|
||||
col.type === 'button' ?
|
||||
<Button_1.default icon onClick={this.onButtonClick.bind(this, col, value)}>{col.value || value[col.field]}</Button_1.default> :
|
||||
col.type === 'time' ?
|
||||
moment(value[col.field]).format('MMM-DD HH:mm:ss') :
|
||||
value[col.field]}</DataTables_1.TableColumn>)}
|
||||
</DataTables_1.TableRow>));
|
||||
return (<Cards_1.Card>
|
||||
<DataTables_1.DataTable plain={!checkboxes} data={checkboxes}>
|
||||
<DataTables_1.TableHeader>
|
||||
<DataTables_1.TableRow>
|
||||
{cols.map((col, i) => <DataTables_1.TableColumn key={i} width={col.width}>{col.header}</DataTables_1.TableColumn>)}
|
||||
</DataTables_1.TableRow>
|
||||
</DataTables_1.TableHeader>
|
||||
<DataTables_1.TableBody>
|
||||
{rows}
|
||||
</DataTables_1.TableBody>
|
||||
</DataTables_1.DataTable>
|
||||
</Cards_1.Card>);
|
||||
}
|
||||
}
|
||||
exports.default = Table;
|
|
@ -21,18 +21,18 @@ export interface ITableProps extends IGenericProps {
|
|||
type?: 'text' | 'time' | 'icon' | 'button',
|
||||
click?: string
|
||||
}[]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITableState extends IGenericState {
|
||||
values: Object[]
|
||||
values: Object[];
|
||||
}
|
||||
|
||||
export default class Table extends GenericComponent<ITableProps, ITableState> {
|
||||
|
||||
state = {
|
||||
values: []
|
||||
}
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -60,16 +60,18 @@ export default class Table extends GenericComponent<ITableProps, ITableState> {
|
|||
const rows = arr.map((value, i) => (
|
||||
<TableRow key={i}>
|
||||
{
|
||||
cols.map((col, i) =>
|
||||
<TableColumn key={i}>{
|
||||
col.type === 'icon' ?
|
||||
<FontIcon>{col.value || value[col.field]}</FontIcon> :
|
||||
col.type === 'button' ?
|
||||
<Button icon onClick={this.onButtonClick.bind(this, col, value)}>{col.value || value[col.field]}</Button> :
|
||||
col.type === 'time' ?
|
||||
moment(value[col.field]).format('MMM-DD HH:mm:ss') :
|
||||
value[col.field]
|
||||
}</TableColumn>)
|
||||
cols.map((col, i) => (
|
||||
<TableColumn key={i}>{
|
||||
col.type === 'icon' ?
|
||||
<FontIcon>{col.value || value[col.field]}</FontIcon> :
|
||||
col.type === 'button' ?
|
||||
<Button icon={true}
|
||||
onClick={this.onButtonClick.bind(this, col, value)}>{col.value || value[col.field]}</Button> :
|
||||
col.type === 'time' ?
|
||||
moment(value[col.field]).format('MMM-DD HH:mm:ss') :
|
||||
value[col.field]
|
||||
}</TableColumn>
|
||||
))
|
||||
}
|
||||
</TableRow>
|
||||
));
|
||||
|
@ -79,9 +81,7 @@ export default class Table extends GenericComponent<ITableProps, ITableState> {
|
|||
<DataTable plain={!checkboxes} data={checkboxes}>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
{
|
||||
cols.map((col, i) => <TableColumn key={i} width={col.width}>{col.header}</TableColumn>)
|
||||
}
|
||||
{cols.map((col, i) => <TableColumn key={i} width={col.width}>{col.header}</TableColumn>)}
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
|
@ -1,3 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const Table_1 = require("./Table");
|
||||
exports.default = Table_1.default;
|
|
@ -1,25 +0,0 @@
|
|||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const React = require("react");
|
||||
const GenericComponent_1 = require("./GenericComponent");
|
||||
const SelectFields_1 = require("react-md/lib/SelectFields");
|
||||
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
class TextFilter extends GenericComponent_1.GenericComponent {
|
||||
// static propTypes = {}
|
||||
// static defaultProps = {}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
onChange(newValue, index, event) {
|
||||
this.trigger('onChange', [newValue]);
|
||||
}
|
||||
render() {
|
||||
var { selectedValue, values } = this.state;
|
||||
values = values || [];
|
||||
// var buttons = values.map((value, idx) => {
|
||||
// return <Button flat key={idx} label={value} primary={value === selectedValue} onClick={this.onChange.bind(null, value)} />
|
||||
// })
|
||||
return (<SelectFields_1.default id="timespan" label="Timespan" value={selectedValue} menuItems={values} position={SelectFields_1.default.Positions.BELOW} onChange={this.onChange} toolbar={false} className='md-select-field--toolbar'/>);
|
||||
}
|
||||
}
|
||||
exports.default = TextFilter;
|
|
@ -16,7 +16,7 @@ export default class TextFilter extends GenericComponent<any, any> {
|
|||
}
|
||||
|
||||
onChange(newValue, index, event) {
|
||||
this.trigger('onChange', [newValue]);
|
||||
this.trigger('onChange', { 0: newValue });
|
||||
}
|
||||
|
||||
render() {
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче