├── .DS_Store
├── .deployment
├── .editorconfig
├── .travis.yml
├── .travis
├── ci.sh
├── coverage.sh
├── push.sh
└── setup.sh
├── .vscode
├── launch.json
└── settings.json
├── Dockerfile
├── LICENSE
├── README.md
├── build
├── asset-manifest.json
├── favicon.ico
├── images
│ ├── application-insights.png
│ ├── azure.png
│ ├── bot-ai-base.png
│ ├── bot-ai-cs.png
│ ├── bot-framework-preview.png
│ ├── bot-instrumented.png
│ ├── default.png
│ ├── human-to-bot-handoff.png
│ └── sample.png
├── index.html
├── service-worker.js
└── static
│ ├── css
│ ├── main.526645cd.css
│ └── main.526645cd.css.map
│ └── js
│ ├── main.462c17f8.js
│ └── main.462c17f8.js.map
├── client
├── @types
│ ├── ai.d.ts
│ ├── botframework.d.ts
│ ├── constant.d.ts
│ ├── cosmosdb.d.ts
│ ├── sample.d.ts
│ └── types.d.ts
├── package.json
├── public
│ ├── favicon.ico
│ ├── images
│ │ └── bot-framework-preview.png
│ └── index.html
├── src
│ ├── actions
│ │ ├── AccountActions.ts
│ │ ├── ConfigurationsActions.ts
│ │ ├── ConnectionsActions.ts
│ │ ├── SettingsActions.ts
│ │ ├── SetupActions.ts
│ │ └── VisibilityActions.ts
│ ├── alt.ts
│ ├── components
│ │ ├── App.tsx
│ │ ├── AutoRefreshSelector
│ │ │ ├── AutoRefreshSelector.tsx
│ │ │ ├── RefreshActions.ts
│ │ │ ├── RefreshStore.ts
│ │ │ └── index.tsx
│ │ ├── Card
│ │ │ ├── Card.tsx
│ │ │ ├── Settings
│ │ │ │ ├── CardSettingsActions.tsx
│ │ │ │ ├── CardSettingsStore.tsx
│ │ │ │ ├── Settings.tsx
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── Dashboard
│ │ │ ├── DownloadFile.tsx
│ │ │ ├── Editor
│ │ │ │ ├── Editor.tsx
│ │ │ │ ├── EditorActions.ts
│ │ │ │ ├── EditorStore.ts
│ │ │ │ └── index.tsx
│ │ │ └── index.tsx
│ │ ├── ElementConnector.tsx
│ │ ├── Home
│ │ │ ├── IconPicker.tsx
│ │ │ └── index.tsx
│ │ ├── Navbar
│ │ │ ├── index.tsx
│ │ │ └── style.scss
│ │ ├── ResponsiveContainer.tsx
│ │ ├── Settings
│ │ │ ├── ConnectionsSettings
│ │ │ │ └── index.tsx
│ │ │ ├── SettingsButton.tsx
│ │ │ ├── SetupDashboard.tsx
│ │ │ └── index.tsx
│ │ ├── Setup
│ │ │ └── index.tsx
│ │ ├── Spinner
│ │ │ ├── Spinner.tsx
│ │ │ ├── SpinnerActions.ts
│ │ │ ├── SpinnerStore.ts
│ │ │ └── index.tsx
│ │ ├── Toast
│ │ │ ├── Toast.tsx
│ │ │ ├── ToastActions.ts
│ │ │ ├── ToastStore.ts
│ │ │ └── index.ts
│ │ ├── Tooltip
│ │ │ ├── Tooltip.tsx
│ │ │ ├── TooltipFontIcon.tsx
│ │ │ └── index.tsx
│ │ ├── colors
│ │ │ └── index.ts
│ │ ├── common
│ │ │ ├── BaseDatasourceSettings.tsx
│ │ │ ├── InfoDrawer.tsx
│ │ │ └── TokenInput.tsx
│ │ └── generic
│ │ │ ├── Area
│ │ │ └── index.tsx
│ │ │ ├── BarData
│ │ │ └── index.tsx
│ │ │ ├── CheckboxFilter.tsx
│ │ │ ├── DatePickerFilter.tsx
│ │ │ ├── Detail
│ │ │ ├── Detail.tsx
│ │ │ └── index.ts
│ │ │ ├── Dialogs
│ │ │ ├── Dialog.tsx
│ │ │ ├── DialogsActions.ts
│ │ │ ├── DialogsStore.ts
│ │ │ └── index.tsx
│ │ │ ├── GenericComponent.tsx
│ │ │ ├── MapData.tsx
│ │ │ ├── MenuFilter.tsx
│ │ │ ├── PieData
│ │ │ └── index.tsx
│ │ │ ├── RadarChartCard.tsx
│ │ │ ├── RadialBarChartCard.tsx
│ │ │ ├── RequestButton.tsx
│ │ │ ├── Scatter
│ │ │ └── index.tsx
│ │ │ ├── Scorecard
│ │ │ └── index.tsx
│ │ │ ├── SimpleRadialBarChartCard.tsx
│ │ │ ├── SplitPanel
│ │ │ ├── SplitPanel.tsx
│ │ │ └── index.ts
│ │ │ ├── Table
│ │ │ ├── Table.tsx
│ │ │ └── index.ts
│ │ │ ├── TextFilter.tsx
│ │ │ ├── Timeline
│ │ │ └── index.tsx
│ │ │ ├── generic.scss
│ │ │ └── plugins.tsx
│ ├── constants
│ │ └── icons.tsx
│ ├── data-sources
│ │ ├── DataSourceConnector.ts
│ │ ├── connections
│ │ │ ├── Connection.tsx
│ │ │ ├── application-insights
│ │ │ │ ├── QueryTester.tsx
│ │ │ │ ├── application-insights.tsx
│ │ │ │ └── index.tsx
│ │ │ ├── azure.tsx
│ │ │ ├── bot-framework.tsx
│ │ │ ├── cosmos-db.tsx
│ │ │ ├── graphql.tsx
│ │ │ └── index.tsx
│ │ ├── index.ts
│ │ └── plugins
│ │ │ ├── ApplicationInsights
│ │ │ ├── ApplicationInsightsApi.ts
│ │ │ ├── Query.ts
│ │ │ └── common.ts
│ │ │ ├── Azure.ts
│ │ │ ├── BotFramework
│ │ │ └── DirectLine.ts
│ │ │ ├── Constant
│ │ │ ├── Settings.tsx
│ │ │ └── index.ts
│ │ │ ├── CosmosDB
│ │ │ └── Query.ts
│ │ │ ├── DataSourcePlugin.ts
│ │ │ ├── GraphQL.ts
│ │ │ ├── PluginsMapping.ts
│ │ │ ├── Sample.ts
│ │ │ └── index.ts
│ ├── index.scss
│ ├── index.tsx
│ ├── logo.svg
│ ├── pages
│ │ ├── Dashboard.tsx
│ │ ├── Home.tsx
│ │ ├── NotFound.tsx
│ │ └── Setup.tsx
│ ├── routes.tsx
│ ├── setupTests.ts
│ ├── stores
│ │ ├── AccountStore.ts
│ │ ├── ConfigurationsStore.ts
│ │ ├── ConnectionsStore.ts
│ │ ├── SettingsStore.ts
│ │ ├── SetupStore.ts
│ │ └── VisibilityStore.ts
│ ├── tests
│ │ ├── common
│ │ │ ├── TokenInput.test.tsx
│ │ │ └── infoDrawer.test.tsx
│ │ ├── data-formats
│ │ │ ├── data-formats.test.ts
│ │ │ └── formats
│ │ │ │ ├── bars.ts
│ │ │ │ ├── filter.ts
│ │ │ │ ├── filtered_samples.ts
│ │ │ │ ├── flags.ts
│ │ │ │ ├── formats.d.ts
│ │ │ │ ├── index.ts
│ │ │ │ ├── retention.ts
│ │ │ │ ├── scorecard.ts
│ │ │ │ ├── timeline.ts
│ │ │ │ └── timespan.ts
│ │ ├── data-sources
│ │ │ ├── ApplicationInsights.Fail.test.tsx
│ │ │ ├── ApplicationInsights.Query.Forked.test.ts
│ │ │ ├── ApplicationInsights.Query.test.ts
│ │ │ ├── Constants.test.ts
│ │ │ ├── CosmosDB.Query.test.ts
│ │ │ ├── DirectLine.Query.test.ts
│ │ │ ├── Samples.test.ts
│ │ │ └── __snapshots__
│ │ │ │ ├── CosmosDB.Query.test.ts.snap
│ │ │ │ └── DirectLine.Query.test.ts.snap
│ │ ├── elements
│ │ │ ├── Area.test.tsx
│ │ │ ├── AutoRefreshSelector.test.tsx
│ │ │ ├── CardSettings.test.tsx
│ │ │ ├── CardSettingsStore.test.tsx
│ │ │ ├── Dialog.test.tsx
│ │ │ ├── IconPicker.test.tsx
│ │ │ ├── Pie.test.tsx
│ │ │ ├── Scorecard.test.tsx
│ │ │ ├── Spinner.test.tsx
│ │ │ ├── SplitPanel.test.tsx
│ │ │ ├── Table.test.tsx
│ │ │ ├── Timeline.test.tsx
│ │ │ └── Toast.test.tsx
│ │ ├── mocks
│ │ │ ├── dashboards
│ │ │ │ ├── application-insights-fail.ts
│ │ │ │ ├── application-insights-forked.ts
│ │ │ │ ├── application-insights.ts
│ │ │ │ ├── area.ts
│ │ │ │ ├── bot-framework-directline.ts
│ │ │ │ ├── constants.ts
│ │ │ │ ├── cosmosdb.ts
│ │ │ │ ├── dashboard.ts
│ │ │ │ ├── dialogs.ts
│ │ │ │ ├── pie.ts
│ │ │ │ ├── rich-samples.ts
│ │ │ │ ├── samples.ts
│ │ │ │ ├── scorecard.ts
│ │ │ │ ├── splitpanel.ts
│ │ │ │ ├── table.ts
│ │ │ │ ├── timeline.ts
│ │ │ │ ├── timespan.ts
│ │ │ │ └── utils.ts
│ │ │ ├── dialog.ts
│ │ │ └── requests
│ │ │ │ ├── account.ts
│ │ │ │ ├── application-insights
│ │ │ │ ├── index.ts
│ │ │ │ ├── query.24h.mock.ts
│ │ │ │ └── query.30d.mock.ts
│ │ │ │ ├── configuration.ts
│ │ │ │ ├── cosmosdb
│ │ │ │ └── index.ts
│ │ │ │ └── directline
│ │ │ │ └── index.ts
│ │ ├── pages
│ │ │ ├── Dashboard.test.tsx
│ │ │ ├── Home.test.tsx
│ │ │ ├── HomePage.test.tsx
│ │ │ ├── NavBar.test.tsx
│ │ │ ├── NotFound.test.tsx
│ │ │ └── Setup.test.tsx
│ │ ├── stores
│ │ │ ├── Account.test.tsx
│ │ │ ├── Configuration.test.tsx
│ │ │ ├── Connection.test.ts
│ │ │ ├── Settings.test.ts
│ │ │ ├── Setup.test.ts
│ │ │ └── __snapshots__
│ │ │ │ └── Setup.test.ts.snap
│ │ └── utils
│ │ │ ├── __snapshots__
│ │ │ └── utils.test.ts.snap
│ │ │ ├── setup.ts
│ │ │ ├── utils.test.ts
│ │ │ └── utils.test.tsx
│ └── utils
│ │ ├── data-formats
│ │ ├── common.ts
│ │ ├── formats
│ │ │ ├── bars.ts
│ │ │ ├── filter.ts
│ │ │ ├── filtered-samples.ts
│ │ │ ├── flags.ts
│ │ │ ├── pie.ts
│ │ │ ├── retention.ts
│ │ │ ├── scorecard.ts
│ │ │ ├── timeline.ts
│ │ │ └── timespan.ts
│ │ └── index.ts
│ │ └── index.ts
├── tsconfig.json
├── tsconfig.test.json
├── tslint.json
└── yarn.lock
├── docs
├── DASHBOARD-SCHEMA.md
├── README.md
├── actions.md
├── add-new-data-source.md
├── add-new-element.md
├── bot-framedash-msgs.png
├── bot-framedash.png
├── components
│ ├── area.md
│ ├── bar.md
│ ├── detail.md
│ ├── pie.md
│ ├── scatter.md
│ ├── scorecard.md
│ ├── splitpanel.md
│ ├── table.md
│ └── timeline.md
├── dashboard-creation.md
├── data-formats
│ └── README.md
├── data-sources
│ ├── bot-framework.md
│ └── cosmos-db.md
├── dialog.md
├── filter.md
├── images
│ ├── bot-fmk-dashboard-intent.png
│ ├── bot-fmk-dashboard-msgs.png
│ └── bot-fmk-dashboard.png
├── requestbutton.md
├── scorecard.md
└── two-modes-element.md
├── package.json
├── scripts
└── deployment
│ ├── azure-deploy.cmd
│ ├── dashboard.template.json
│ ├── webapp
│ └── azuredeploy.json
│ └── webapponlinux
│ └── azuredeploy.json
├── server
├── app.js
├── common
│ └── paths.js
├── config
│ ├── auth.basic.json
│ └── setup.initial.json
├── dashboards
│ └── preconfigured
│ │ ├── MBF.Advanced.Analytics.ts
│ │ ├── MBF.Advanced.Health.ts
│ │ ├── app-insights-web-app.ts
│ │ ├── application-insights-sample.ts
│ │ ├── azure.ts
│ │ ├── bot-framework-goals.ts
│ │ ├── bot-framework-inst.ts
│ │ ├── bot-framework.ts
│ │ ├── human-handoff.ts
│ │ ├── qna.dashboard.ts
│ │ ├── sample-with-dialog.ts
│ │ ├── sample-with-filter.ts
│ │ └── sample.ts
├── helpers
│ ├── resourceFieldProvider.js
│ └── resourceFileProvider.js
├── index.js
├── package.json
├── routes
│ ├── api.js
│ ├── applicationInsights.js
│ ├── auth.js
│ ├── azure.js
│ ├── cosmos-db.js
│ └── graphql.js
└── yarn.lock
└── yarn.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/.DS_Store
--------------------------------------------------------------------------------
/.deployment:
--------------------------------------------------------------------------------
1 | [config]
2 | SCM_COMMAND_IDLE_TIMEOUT=7200
3 | command = ./scripts/deployment/azure-deploy.cmd
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Indentation override for all JS under src directory
7 | [src/**.{js,jsx,ts,tsx}]
8 | indent_style = space
9 | indent_size = 2
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language:
2 | node_js
3 |
4 | node_js:
5 | - "7"
6 |
7 | install:
8 | - .travis/setup.sh
9 |
10 | script:
11 | - .travis/ci.sh
12 | - .travis/coverage.sh
13 |
14 | after_success:
15 | - .travis/push.sh
--------------------------------------------------------------------------------
/.travis/ci.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | CI=true yarn lint
4 | CI=true yarn test
--------------------------------------------------------------------------------
/.travis/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | min_coverage="${MIN_COVERAGE:-42}"
4 | line_coverage="$(CI=true yarn coverage | grep '^All files *|' | cut -d'|' -f5 | tr -d ' ' | cut -d'.' -f1)"
5 |
6 | if [ ${line_coverage} -lt ${min_coverage} ]; then
7 | echo "Got test coverage of ${line_coverage} which is less than configured minimum of ${min_coverage}" >&2
8 | exit 1
9 | else
10 | echo "Got test coverage of ${line_coverage}, well done" >&2
11 | exit 0
12 | fi
--------------------------------------------------------------------------------
/.travis/push.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 |
5 | readonly GITHUB_ORG="${GITHUB_ORG:-CatalystCode}"
6 | readonly GITHUB_REPO="${GITHUB_REPO:-ibex-dashboard}"
7 | readonly TARGET_BRANCH="${TARGET_BRANCH:-master}"
8 | readonly SOURCE_BRANCH="${SOURCE_BRANCH:-ibex-version-1.0}"
9 |
10 | readonly AUTOCOMMIT_NAME="Travis CI"
11 | readonly AUTOCOMMIT_EMAIL="travis@travis-ci.org"
12 | readonly AUTOCOMMIT_BRANCH="temp"
13 |
14 | log() {
15 | echo "$@" >&2
16 | }
17 |
18 | ensure_preconditions_met() {
19 | if [ -z "${TRAVIS_PULL_REQUEST_BRANCH}" ]; then
20 | log "Job is CI for a push, skipping creation of production build"
21 | exit 0
22 | fi
23 | if [ "${TRAVIS_BRANCH}_${TRAVIS_PULL_REQUEST_BRANCH}" != "${TARGET_BRANCH}_${SOURCE_BRANCH}" ]; then
24 | log "Skipping creation of production build"
25 | log "We only create production builds for pull requests from '${SOURCE_BRANCH}' to '${TARGET_BRANCH}'"
26 | log "but this pull request is from '${TRAVIS_PULL_REQUEST BRANCH}' to '${TRAVIS_BRANCH}'"
27 | exit 0
28 | fi
29 | if [ -z "${GITHUB_TOKEN}" ]; then
30 | log "GITHUB_TOKEN not set: won't be able to push production build"
31 | log "Please configure the token in .travis.yml or the Travis UI"
32 | exit 1
33 | fi
34 | }
35 |
36 | create_production_build() {
37 | CI="" yarn build
38 | }
39 |
40 | setup_git() {
41 | git config user.name "${AUTOCOMMIT_NAME}"
42 | git config user.email "${AUTOCOMMIT_EMAIL}"
43 | git remote add origin-travis "https://${GITHUB_TOKEN}@github.com/${GITHUB_ORG}/${GITHUB_REPO}.git"
44 | }
45 |
46 | commit_build_files() {
47 | git checkout -b "${AUTOCOMMIT_BRANCH}"
48 | git add --all build
49 | echo -e "Travis build: ${TRAVIS_BUILD_NUMBER}\n\nhttps://travis-ci.org/${GITHUB_ORG}/${GITHUB_REPO}/builds/${TRAVIS_BUILD_ID}" | git commit --file -
50 | }
51 |
52 | push_to_github() {
53 | git push origin-travis "${AUTOCOMMIT_BRANCH}:${SOURCE_BRANCH}"
54 | }
55 |
56 | ensure_preconditions_met
57 | create_production_build
58 | setup_git
59 | commit_build_files
60 | push_to_github
--------------------------------------------------------------------------------
/.travis/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | yarn install
4 | yarn run css:build
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Launch Server",
11 | "program": "${workspaceRoot}/server/index.js",
12 | "outFiles": []
13 | },
14 | {
15 | "type": "node",
16 | "request": "launch",
17 | "name": "Launch Program",
18 | "program": "${workspaceRoot}/client:start",
19 | "outFiles": []
20 | },
21 | {
22 | "type": "node",
23 | "request": "attach",
24 | "name": "Attach to Process",
25 | "address": "localhost",
26 | "port": 5858,
27 | "outFiles": []
28 | }
29 | ]
30 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "**/*.css": { "when": "$(basename).scss"}
5 | },
6 | "editor.tabSize": 2,
7 | "editor.insertSpaces": true,
8 | "editor.detectIndentation": false,
9 | "editor.rulers": [
10 | 120
11 | ],
12 | "search.exclude": {
13 | "**/.git": true,
14 | "**/node_modules": true,
15 | "**/build": true,
16 | "**/coverage": true
17 | }
18 | }
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:8.2.1-alpine
2 |
3 | # Create app directory
4 | RUN mkdir -p /usr/src/app
5 | RUN mkdir -p /usr/src/app/client
6 | RUN mkdir -p /usr/src/app/server
7 | WORKDIR /usr/src/app
8 |
9 | # Install app dependencies
10 | COPY yarn.lock /usr/src/app/
11 | COPY package.json /usr/src/app/
12 |
13 | COPY server/yarn.lock /usr/src/app/server
14 | COPY server/package.json /usr/src/app/server
15 |
16 | COPY client/yarn.lock /usr/src/app/client
17 | COPY client/package.json /usr/src/app/client
18 |
19 | RUN npm install yarn -g
20 | RUN yarn
21 |
22 | # Bundle app source
23 | COPY . /usr/src/app
24 |
25 | # Build client assets
26 | WORKDIR /usr/src/app/client
27 | RUN yarn build
28 |
29 | WORKDIR /usr/src/app/
30 | CMD [ "npm", "start" ]
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Elad Iwanir
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/build/asset-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "main.css": "static/css/main.526645cd.css",
3 | "main.css.map": "static/css/main.526645cd.css.map",
4 | "main.js": "static/js/main.462c17f8.js",
5 | "main.js.map": "static/js/main.462c17f8.js.map"
6 | }
--------------------------------------------------------------------------------
/build/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/favicon.ico
--------------------------------------------------------------------------------
/build/images/application-insights.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/application-insights.png
--------------------------------------------------------------------------------
/build/images/azure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/azure.png
--------------------------------------------------------------------------------
/build/images/bot-ai-base.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/bot-ai-base.png
--------------------------------------------------------------------------------
/build/images/bot-ai-cs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/bot-ai-cs.png
--------------------------------------------------------------------------------
/build/images/bot-framework-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/bot-framework-preview.png
--------------------------------------------------------------------------------
/build/images/bot-instrumented.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/bot-instrumented.png
--------------------------------------------------------------------------------
/build/images/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/default.png
--------------------------------------------------------------------------------
/build/images/human-to-bot-handoff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/human-to-bot-handoff.png
--------------------------------------------------------------------------------
/build/images/sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/build/images/sample.png
--------------------------------------------------------------------------------
/build/index.html:
--------------------------------------------------------------------------------
1 |
React App
--------------------------------------------------------------------------------
/client/@types/ai.d.ts:
--------------------------------------------------------------------------------
1 | interface IQueryResults {
2 | Tables: IQueryResult[]
3 | }
4 |
5 | interface IQueryResult {
6 | TableName: string,
7 | Columns: {
8 | ColumnName: string,
9 | DataType: string,
10 | ColumnType: string
11 | }[],
12 | Rows: any[][]
13 | }
14 |
15 | interface IQueryStatus {
16 | Ordinal: number,
17 | Kind: string,
18 | Name: string,
19 | Id: string
20 | }
21 |
22 | /**
23 | * ====================================
24 | * Template definitions
25 | * ====================================
26 | */
27 |
28 | /**
29 | * Application Insights data source definition
30 | */
31 | interface AIDataSource extends IDataSource {
32 | type: 'ApplicationInsights/Query',
33 | dependencies: {
34 | /**
35 | * Required - to use in all queries to app insights as a basic timespan parameter
36 | */
37 | queryTimespan: string,
38 | /**
39 | * Optional - from which 'queryTimespan' is derived
40 | */
41 | timespan?: string,
42 | /**
43 | * Used for queries that require granularity (like timeline)
44 | */
45 | granularity?: string
46 | },
47 | params: AIQueryParams | AIForkedQueryParams;
48 | }
49 |
50 | /**
51 | * A simple query on application insights data source
52 | */
53 | interface AIQueryParams {
54 | query: AIQuery,
55 | mappings?: AIMapping
56 | }
57 |
58 | /**
59 | * A forked query that aggregates several queries into a single API call
60 | */
61 | interface AIForkedQueryParams {
62 | /**
63 | * The table on which to perform the forken query
64 | */
65 | table: string,
66 | queries: IDict<{
67 | query: AIQuery,
68 | mappings?: AIMapping,
69 | filters?: Array,
70 | calculated?: (state: any, dependencies?: any, prevState?: any) => any
71 | }>,
72 | }
73 |
74 | type AIQuery = string | (() => string) | ((dependencies: any) => string);
75 | type AIMapping = IDict<(value: any, row: any, idx: number) => string | number | boolean>;
76 |
--------------------------------------------------------------------------------
/client/@types/botframework.d.ts:
--------------------------------------------------------------------------------
1 | interface IQueryResult {
2 | value: string
3 | }
4 |
5 | /**
6 | * ====================================
7 | * Template definitions
8 | * ====================================
9 | */
10 |
11 | /**
12 | * Application Insights data source definition
13 | */
14 | interface BotFrameworkDataSource extends IDataSource {
15 | type: 'BotFramework/DirectLine',
16 | dependencies: {
17 | /**
18 | * Required - to use in all queries to app insights as a basic timespan parameter
19 | */
20 | queryTimespan: string,
21 | /**
22 | * Optional - from which 'queryTimespan' is derived
23 | */
24 | timespan?: string,
25 | /**
26 | * Used for queries that require granularity (like timeline)
27 | */
28 | granularity?: string
29 | },
30 | params: BotFrameworkQueryParams;
31 | }
32 |
33 | /**
34 | * A simple query on application insights data source
35 | */
36 | interface BotFrameworkQueryParams {
37 | }
38 |
39 | type BotFrameworkQuery = string | (() => string) | ((dependencies: any) => string);
40 |
--------------------------------------------------------------------------------
/client/@types/constant.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A data source to hold unchanging data
3 | */
4 | interface ConstantDataSource extends IDataSource {
5 | type: 'Constant',
6 | params: {
7 | /**
8 | * List of values to choose from
9 | */
10 | values: any[],
11 | /**
12 | * Current selected value (usually used in constant filters)
13 | */
14 | selectedValue: string
15 | }
16 | }
--------------------------------------------------------------------------------
/client/@types/cosmosdb.d.ts:
--------------------------------------------------------------------------------
1 | interface IQueryResult {
2 | value: string
3 | }
4 |
5 | /**
6 | * ====================================
7 | * Template definitions
8 | * ====================================
9 | */
10 |
11 | /**
12 | * Application Insights data source definition
13 | */
14 | interface CosmosDBDataSource extends IDataSource {
15 | type: 'CosmosDB/Query',
16 | dependencies: {
17 | /**
18 | * Required - to use in all queries to app insights as a basic timespan parameter
19 | */
20 | queryTimespan: string,
21 | /**
22 | * Optional - from which 'queryTimespan' is derived
23 | */
24 | timespan?: string,
25 | /**
26 | * Used for queries that require granularity (like timeline)
27 | */
28 | granularity?: string
29 | },
30 | params: CosmosDBQueryParams;
31 | }
32 |
33 | /**
34 | * A simple query on application insights data source
35 | */
36 | interface CosmosDBQueryParams {
37 | }
38 |
39 | type CosmosDBQuery = string | (() => string) | ((dependencies: any) => string);
40 |
--------------------------------------------------------------------------------
/client/@types/sample.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * A data source to hold unchanging data
3 | */
4 | interface SampleDataSource extends IDataSource {
5 | type: 'Sample',
6 | params?: {
7 | /**
8 | * All values in this dictionary will be available as dependencies from this data source
9 | */
10 | samples: {
11 | [key: string]: any,
12 | /**
13 | * List of sample values
14 | */
15 | values?: any[],
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibex-dashboard-client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "proxy": "http://localhost:4000/",
6 | "devDependencies": {
7 | "@types/alt": "^0.16.32",
8 | "@types/jest": "^19.2.2",
9 | "@types/lodash": "^4.14.55",
10 | "@types/nock": "^8.2.1",
11 | "@types/node": "^7.0.8",
12 | "@types/react": "^15.0.16",
13 | "@types/react-addons-test-utils": "^0.14.17",
14 | "@types/react-dom": "^0.14.23",
15 | "@types/react-router": "^3.0.8",
16 | "alt": "^0.18.6",
17 | "alt-utils": "^1.0.0",
18 | "leaflet": "^1.2.0",
19 | "leaflet-geosearch": "^2.6.0",
20 | "leaflet.markercluster": "^1.2.0",
21 | "nock": "^9.0.9",
22 | "node-sass": "^4.5.3",
23 | "npm-run-all": "^4.0.2",
24 | "react": "^15.4.2",
25 | "react-addons-css-transition-group": "^15.4.2",
26 | "react-addons-test-utils": "^15.4.2",
27 | "react-addons-transition-group": "^15.4.2",
28 | "react-dom": "^15.4.2",
29 | "react-grid-layout": "^0.14.7",
30 | "react-leaflet": "^1.7.8",
31 | "react-leaflet-div-icon": "^1.1.0",
32 | "react-leaflet-markercluster": "^1.1.8",
33 | "react-md": "^1.0.18",
34 | "react-render-html": "^0.1.6",
35 | "react-router": "3.0.0",
36 | "react-scripts-ts": "^2.6.0",
37 | "recharts": "^0.21.2",
38 | "tslint": "^5.8.0"
39 | },
40 | "dependencies": {
41 | "body-parser": "^1.17.1",
42 | "cookie-parser": "^1.4.3",
43 | "express": "^4.15.2",
44 | "express-session": "^1.15.2",
45 | "lodash": "^4.17.4",
46 | "lodash.throttle": "^4.1.1",
47 | "material-colors": "^1.2.5",
48 | "moment": "^2.18.0",
49 | "morgan": "^1.8.1",
50 | "ms-rest-azure": "^2.1.2",
51 | "passport": "^0.3.2",
52 | "passport-azure-ad": "^3.0.5",
53 | "react-ace": "^5.0.1",
54 | "react-date-range": "^0.9.4",
55 | "react-json-tree": "^0.10.9",
56 | "xhr-request": "^1.0.1"
57 | },
58 | "scripts": {
59 | "css:build": "node-sass src/ -o src/",
60 | "css:watch": "yarn run css:build && node-sass src/ -o src/ --watch --recursive",
61 | "client:start": "react-scripts-ts start",
62 | "start": "npm-run-all -p css:watch client:start",
63 | "coverage": "react-scripts-ts test --env=jsdom --coverage --collectCoverageFrom=src/**/*.{ts,tsx} --collectCoverageFrom=!src/**/*.d.ts --collectCoverageFrom=!src/tests/**",
64 | "lint": "tslint src",
65 | "build": "yarn run css:build && react-scripts-ts build && rm -rf ../build/static && cp -rf build ../",
66 | "test": "react-scripts-ts test --env=jsdom"
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/public/images/bot-framework-preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/client/public/images/bot-framework-preview.png
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
18 | React App
19 |
20 |
21 |
22 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/client/src/actions/AccountActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../alt';
2 | import * as request from 'xhr-request';
3 | import { ToastActions } from '../components/Toast';
4 | import utils from '../utils';
5 |
6 | interface IAccountActions {
7 | failure(error: any): any;
8 | updateAccount(): any;
9 | }
10 |
11 | class AccountActions extends AbstractActions implements IAccountActions {
12 |
13 | updateAccount() {
14 |
15 | return (dispatcher: (account: IDictionary) => void) => {
16 |
17 | request('/auth/account', { json: true }, (error: any, result: any) => {
18 | if (error || result && result.error) {
19 | return this.failure(error || result && result.error);
20 | }
21 | return dispatcher({ account: result.account });
22 | }
23 | );
24 |
25 | };
26 | }
27 |
28 | failure(error: any) {
29 | ToastActions.addToast({ text: utils.errorToMessage(error) });
30 | return { error };
31 | }
32 | }
33 |
34 | const accountActions = alt.createActions(AccountActions);
35 |
36 | export default accountActions;
37 |
--------------------------------------------------------------------------------
/client/src/actions/ConnectionsActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../alt';
2 |
3 | interface IConnectionsActions {
4 | updateConnection(connectionName: string, args: IDictionary): any;
5 | }
6 |
7 | class ConnectionsActions extends AbstractActions implements IConnectionsActions {
8 |
9 | updateConnection(connectionName: string, args: IDictionary) {
10 | return { connectionName, args };
11 | }
12 | }
13 |
14 | const connectionsActions = alt.createActions(ConnectionsActions);
15 |
16 | export default connectionsActions;
17 |
--------------------------------------------------------------------------------
/client/src/actions/SettingsActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../alt';
2 |
3 | interface ISettingsActions {
4 | saveSettings(): any;
5 | saveSettingsCompleted(): any;
6 | }
7 |
8 | class SettingsActions extends AbstractActions implements ISettingsActions {
9 |
10 | saveSettings() {
11 | return { };
12 | }
13 |
14 | saveSettingsCompleted() {
15 | return { };
16 | }
17 | }
18 |
19 | const settingsActions = alt.createActions(SettingsActions);
20 |
21 | export default settingsActions;
22 |
--------------------------------------------------------------------------------
/client/src/actions/SetupActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../alt';
2 | import * as request from 'xhr-request';
3 | import { IToast, ToastActions } from '../components/Toast';
4 |
5 | interface ISetupActions {
6 | load(): any;
7 | save(setupConfig: ISetupConfig, successCallback: () => void): any;
8 | failure(error: any): void;
9 | }
10 |
11 | class SetupActions extends AbstractActions implements ISetupActions {
12 |
13 | load() {
14 |
15 | return (dispatcher: (setupConfig: ISetupConfig) => void) => {
16 |
17 | request('/api/setup', { json: true }, (setupError: any, setupConfig: ISetupConfig) => {
18 | return dispatcher(setupConfig);
19 | });
20 | };
21 | }
22 |
23 | save(setupConfig: ISetupConfig, successCallback: () => void) {
24 | return (dispatcher: (setupConfig: ISetupConfig) => void) => {
25 |
26 | setupConfig.stage = 'configured';
27 | let stringConfig = JSON.stringify(setupConfig);
28 |
29 | request('/api/setup', {
30 | method: 'POST',
31 | json: true,
32 | body: { json: stringConfig }
33 | },
34 | (setupError: any, setupJson: any) => {
35 |
36 | if (setupError) {
37 | return this.failure(setupError);
38 | }
39 |
40 | return request('/auth/init',
41 | (authError: any, authJson: any) => {
42 |
43 | if (authError) {
44 | return this.failure(authError);
45 | }
46 |
47 | let toast: IToast = { text: 'Setup was saved successfully.' };
48 | ToastActions.addToast(toast);
49 |
50 | try {
51 | if (successCallback) {
52 | successCallback();
53 | }
54 | } catch (e) { }
55 |
56 | return dispatcher(authJson);
57 | }
58 | );
59 | }
60 | );
61 | };
62 | }
63 |
64 | failure(error: any) {
65 | return { error };
66 | }
67 | }
68 |
69 | const setupActions = alt.createActions(SetupActions);
70 |
71 | export default setupActions;
72 |
--------------------------------------------------------------------------------
/client/src/actions/VisibilityActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../alt';
2 |
3 | interface IVisibilityActions {
4 | setFlags(flags: IDict): any;
5 | turnFlagOn(flagName: string): any;
6 | turnFlagOff(flagName: string): any;
7 | }
8 |
9 | class VisibilityActions extends AbstractActions implements IVisibilityActions {
10 |
11 | setFlags(flags: IDict): any {
12 | return flags;
13 | }
14 |
15 | initializeFlag(flagName: string): any {
16 |
17 | }
18 |
19 | turnFlagOn(flagName: string): any {
20 | let flag = {};
21 | flag[flagName] = true;
22 | return flag;
23 | }
24 | turnFlagOff(flagName: string): any {
25 | let flag = {};
26 | flag[flagName] = false;
27 | return flag;
28 | }
29 | }
30 |
31 | const visibilityActions = alt.createActions(VisibilityActions);
32 |
33 | export default visibilityActions;
34 |
--------------------------------------------------------------------------------
/client/src/alt.ts:
--------------------------------------------------------------------------------
1 | import * as Alt from 'alt';
2 | export default new Alt();
3 |
4 | export class AbstractActions implements AltJS.ActionsClass {
5 | dispatch: (...payload: Array) => void;
6 | generateActions: (...actions: string[]) => void;
7 | constructor(alt: AltJS.Alt) {}
8 | }
9 |
10 | export class AbstractStoreModel implements AltJS.StoreModel {
11 | bindListeners: (obj: any) => void;
12 | exportPublicMethods: (config: { [key: string]: (...args: Array) => any }) => any;
13 | exportAsync: (source: any) => void;
14 | waitFor: any;
15 | exportConfig: any;
16 | getState: () => S;
17 | }
--------------------------------------------------------------------------------
/client/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Navbar from './Navbar';
3 | import { Toast } from './Toast';
4 |
5 | class App extends React.Component {
6 |
7 | render() {
8 |
9 | var { children } = this.props;
10 |
11 | return (
12 |
13 |
14 | {children}
15 |
16 |
17 |
18 | );
19 | }
20 | }
21 |
22 | export default App;
--------------------------------------------------------------------------------
/client/src/components/AutoRefreshSelector/AutoRefreshSelector.tsx:
--------------------------------------------------------------------------------
1 | import * as request from 'xhr-request';
2 | import * as React from 'react';
3 | import * as _ from 'lodash';
4 |
5 | import RefreshStore, { IRefreshStoreState } from './RefreshStore';
6 | import SelectField from 'react-md/lib/SelectFields';
7 |
8 | import RefreshActions from './RefreshActions';
9 |
10 | import { DataSourceConnector, IDataSourceDictionary, IDataSource } from '../../data-sources/DataSourceConnector';
11 |
12 | interface IRefreshState extends IRefreshStoreState {
13 | refreshMenuVisible?: boolean;
14 | }
15 |
16 | export default class AutoRefreshSelector extends React.Component {
17 |
18 | oneSecInMs = 1000;
19 | oneMinInMs = 60 * this.oneSecInMs;
20 | refreshIntervals = [
21 | {text: 'None', intervalMs: -1},
22 | {text: '30 Sec', intervalMs: 30 * this.oneSecInMs},
23 | {text: '60 Sec', intervalMs: 60 * this.oneSecInMs},
24 | {text: '90 Sec', intervalMs: 90 * this.oneSecInMs},
25 | {text: '2 Min', intervalMs: 2 * this.oneMinInMs},
26 | {text: '5 Min', intervalMs: 5 * this.oneMinInMs},
27 | {text: '15 Min', intervalMs: 15 * this.oneMinInMs},
28 | {text: '30 Min', intervalMs: 30 * this.oneMinInMs},
29 | ];
30 |
31 | constructor(props: any) {
32 | super(props);
33 |
34 | this.state = RefreshStore.getState();
35 |
36 | this.handleRefreshIntervalChange = this.handleRefreshIntervalChange.bind(this);
37 | }
38 |
39 | handleRefreshIntervalChange = (refreshInterval: string) => {
40 | var oneSec = 1000;
41 | var interval = this.refreshIntervals.find((x) => { return x.text === refreshInterval; }).intervalMs;
42 |
43 | RefreshActions.updateInterval(interval);
44 | RefreshActions.setRefreshTimer(
45 | interval,
46 | DataSourceConnector.refreshDs);
47 | }
48 |
49 | render () {
50 |
51 | let refreshDropDownTexts = this.refreshIntervals.map((x) => { return x.text; });
52 | return (
53 |
64 | );
65 | }
66 | }
--------------------------------------------------------------------------------
/client/src/components/AutoRefreshSelector/RefreshActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../alt';
2 | import * as request from 'xhr-request';
3 |
4 | interface IRefreshActions {
5 | updateInterval(newInterval: number): { refreshInterval: number };
6 | setRefreshTimer(newInterval: any, cb: any): void;
7 | }
8 |
9 | class RefreshActions extends AbstractActions implements IRefreshActions {
10 |
11 | private runningRefreshInterval: any;
12 |
13 | constructor(altobj: AltJS.Alt) {
14 | super(altobj);
15 |
16 | this.setRefreshTimer = this.setRefreshTimer.bind(this);
17 | }
18 |
19 | updateInterval(newInterval: number) {
20 |
21 | return { refreshInterval: newInterval };
22 | }
23 |
24 | setRefreshTimer(newInterval: any, cb: any) {
25 | return (dispatch) => {
26 | // clear any previously scheduled interval
27 | if (this.runningRefreshInterval) {
28 | clearInterval(this.runningRefreshInterval);
29 | this.runningRefreshInterval = null;
30 | }
31 |
32 | if (!newInterval || newInterval === -1) {
33 | // don't auto refresh
34 | return;
35 | }
36 |
37 | // setup a new interval
38 | var interval = setInterval(
39 | cb,
40 | newInterval);
41 |
42 | this.runningRefreshInterval = interval;
43 | };
44 | }
45 | }
46 | const refreshActions = alt.createActions(RefreshActions);
47 |
48 | export default refreshActions;
49 |
--------------------------------------------------------------------------------
/client/src/components/AutoRefreshSelector/RefreshStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../../alt';
2 |
3 | import refreshActions from './RefreshActions';
4 |
5 | export interface IRefreshStoreState {
6 | refreshInterval: number;
7 | }
8 |
9 | class RefreshStore extends AbstractStoreModel implements IRefreshStoreState {
10 |
11 | refreshInterval: number;
12 |
13 | constructor() {
14 | super();
15 |
16 | this.bindListeners({
17 | updateInterval: refreshActions.updateInterval
18 | });
19 | }
20 |
21 | updateInterval(state: any) {
22 | this.refreshInterval = state.refreshInterval;
23 |
24 | }
25 | }
26 |
27 | const refreshStore = alt.createStore(RefreshStore as AltJS.StoreModel, 'RefreshStore');
28 |
29 | export default refreshStore;
30 |
--------------------------------------------------------------------------------
/client/src/components/AutoRefreshSelector/index.tsx:
--------------------------------------------------------------------------------
1 | import AutoRefreshSelector from './AutoRefreshSelector';
2 | import RefreshActions from './RefreshActions';
3 | import RefreshStore from './RefreshStore';
4 |
5 | export {
6 | AutoRefreshSelector,
7 | RefreshActions,
8 | RefreshStore
9 | };
--------------------------------------------------------------------------------
/client/src/components/Card/Settings/CardSettingsActions.tsx:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../../alt';
2 |
3 | interface ICardSettingsActions {
4 | openDialog(title: string, elementId: string): IDict;
5 | closeDialog(): any;
6 | selectIndex(index: number): number;
7 | getExportData(dashboard: IDashboardConfig): IDashboardConfig;
8 | downloadData(): void;
9 | }
10 |
11 | class CardSettingsActions extends AbstractActions implements ICardSettingsActions {
12 |
13 | openDialog(title: string, elementId: string) {
14 | return {title, elementId};
15 | }
16 |
17 | closeDialog() {
18 | return {};
19 | }
20 |
21 | selectIndex(index: number) {
22 | return index;
23 | }
24 |
25 | getExportData(dashboard: IDashboardConfig) {
26 | return dashboard;
27 | }
28 |
29 | downloadData() {
30 | return {};
31 | }
32 |
33 | }
34 |
35 | const cardSettingsActions = alt.createActions(CardSettingsActions);
36 |
37 | export default cardSettingsActions;
38 |
--------------------------------------------------------------------------------
/client/src/components/Card/Settings/index.tsx:
--------------------------------------------------------------------------------
1 | import Settings from './Settings';
2 | import SettingsActions from './CardSettingsActions';
3 | import SettingsStore from './CardSettingsStore';
4 |
5 | export {
6 | Settings,
7 | SettingsActions,
8 | SettingsStore
9 | };
--------------------------------------------------------------------------------
/client/src/components/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import Card from './Card';
2 |
3 | export default Card;
--------------------------------------------------------------------------------
/client/src/components/Dashboard/DownloadFile.tsx:
--------------------------------------------------------------------------------
1 |
2 | function downloadBlob(data: string, mimeType: string, filename: string) {
3 | const blob = new Blob([data], {
4 | type: mimeType
5 | });
6 | var el = document.createElement('a');
7 | el.setAttribute('href', window.URL.createObjectURL(blob));
8 | el.setAttribute('download', filename);
9 | el.style.display = 'none';
10 | document.body.appendChild(el);
11 | el.click();
12 | document.body.removeChild(el);
13 | }
14 |
15 | export { downloadBlob };
--------------------------------------------------------------------------------
/client/src/components/Dashboard/Editor/EditorActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../../alt';
2 | import * as request from 'xhr-request';
3 |
4 | interface IEditorActions {
5 | openDialog(): any;
6 | closeDialog(): any;
7 | loadDashboard(dashboardId: string): any;
8 | selectTheme(index: number): number;
9 | updateValue(newValue: string): string;
10 | }
11 |
12 | class EditorActions extends AbstractActions implements IEditorActions {
13 |
14 | openDialog() {
15 | return {};
16 | }
17 |
18 | closeDialog() {
19 | return {};
20 | }
21 |
22 | loadDashboard(dashboardId: string) {
23 | this.openDialog();
24 | return (dispatch) => {
25 | request('/api/dashboards/' + dashboardId + '?format=raw', {}, function (err: any, data: any) {
26 | if (err) {
27 | throw err;
28 | }
29 | return dispatch( data );
30 | });
31 | };
32 | }
33 |
34 | selectTheme(index: number) {
35 | return index;
36 | }
37 |
38 | updateValue(newValue: string): string {
39 | return newValue;
40 | }
41 |
42 | }
43 |
44 | const editorActions = alt.createActions(EditorActions);
45 |
46 | export default editorActions;
47 |
--------------------------------------------------------------------------------
/client/src/components/Dashboard/Editor/EditorStore.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import alt, { AbstractStoreModel } from '../../../alt';
3 |
4 | import editorActions from './EditorActions';
5 |
6 | interface IEditorStoreState {
7 | visible: boolean;
8 | value: string;
9 | selectedTheme: number;
10 | // internal
11 | saveDisabled: boolean;
12 | }
13 |
14 | class EditorStore extends AbstractStoreModel implements IEditorStoreState {
15 |
16 | visible: boolean;
17 | value: string;
18 | selectedTheme: number;
19 | // internal
20 | saveDisabled: boolean;
21 |
22 | constructor() {
23 | super();
24 |
25 | this.visible = false;
26 | this.value = null;
27 | this.selectedTheme = 0;
28 |
29 | this.saveDisabled = false;
30 |
31 | this.bindListeners({
32 | openDialog: editorActions.openDialog,
33 | closeDialog: editorActions.closeDialog,
34 | loadDashboard: editorActions.loadDashboard,
35 | selectTheme: editorActions.selectTheme,
36 | updateValue: editorActions.updateValue,
37 | });
38 | }
39 |
40 | openDialog() {
41 | this.visible = true;
42 | }
43 |
44 | closeDialog() {
45 | this.visible = false;
46 | }
47 |
48 | loadDashboard(value: string) {
49 | this.value = value;
50 | }
51 |
52 | selectTheme(index: number) {
53 | this.selectedTheme = index;
54 | }
55 |
56 | updateValue(newValue: string) {
57 | this.value = newValue;
58 | }
59 | }
60 |
61 | const editorStore = alt.createStore(EditorStore as AltJS.StoreModel, 'EditorStore');
62 |
63 | export default editorStore;
64 |
--------------------------------------------------------------------------------
/client/src/components/Dashboard/Editor/index.tsx:
--------------------------------------------------------------------------------
1 | import Editor from './Editor';
2 | import EditorActions from './EditorActions';
3 | import EditorStore from './EditorStore';
4 |
5 | export {
6 | Editor,
7 | EditorActions,
8 | EditorStore
9 | };
--------------------------------------------------------------------------------
/client/src/components/Home/IconPicker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Autocomplete from 'react-md/lib/Autocompletes';
3 | import FontIcon from 'react-md/lib/FontIcons';
4 |
5 | import icons from '../../constants/icons';
6 |
7 | interface IIconPickerProps {
8 | defaultLabel?: string;
9 | defaultIcon?: string;
10 | listStyle?: React.CSSProperties;
11 | }
12 |
13 | interface IIconPickerState {
14 | label: string;
15 | icon: string;
16 | }
17 |
18 | export default class IconPicker extends React.Component {
19 |
20 | static defaultProps = {
21 | defaultLabel: 'Search icons',
22 | defaultIcon: 'dashboard',
23 | listStyle: {},
24 | };
25 |
26 | static listItems: any = [];
27 |
28 | constructor(props: any) {
29 | super(props);
30 |
31 | const { defaultLabel, defaultIcon } = props;
32 |
33 | this.state = {
34 | label: defaultLabel,
35 | icon: defaultIcon,
36 | };
37 |
38 | this.onChange = this.onChange.bind(this);
39 | }
40 |
41 | getIcon() {
42 | const { icon } = this.state;
43 | // check icon value is valid
44 | if (icons.findIndex(i => i === icon) > 0) {
45 | return icon;
46 | }
47 | return 'dashboard';
48 | }
49 |
50 | componentWillMount() {
51 | if (IconPicker.listItems.length === 0) {
52 | IconPicker.listItems = icons.map((icon) => ({ icon, leftIcon: {icon} }));
53 | }
54 | }
55 |
56 | render() {
57 | const { label, icon } = this.state;
58 | const { listStyle } = this.props;
59 | return (
60 |
71 | );
72 | }
73 |
74 | private onChange(icon: string) {
75 | this.setState({ icon });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/src/components/Navbar/style.scss:
--------------------------------------------------------------------------------
1 |
2 | .md-toolbar .md-select-field--toolbar.md-select-field--toolbar {
3 |
4 | margin-bottom: 6px;
5 | margin-top: 6px;
6 |
7 | .md-floating-label {
8 | padding-left: 6px;
9 | }
10 |
11 | .md-text-field {
12 | padding-bottom: 0;
13 | padding-left: 6px;
14 | padding-right: 16px;
15 | padding-top: 0;
16 | margin: 20px 0 0 0;
17 | }
18 | }
--------------------------------------------------------------------------------
/client/src/components/ResponsiveContainer.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Media } from 'react-md/lib/Media';
3 | import { ResponsiveContainer as RechartResponsiveContainer } from 'recharts';
4 |
5 | interface IeContainerProps {
6 | layout: {
7 | x: number;
8 | y: number;
9 | w: number;
10 | h: number;
11 | };
12 | }
13 |
14 | interface IContainerState {
15 |
16 | }
17 |
18 | /**
19 | * This class is used to remove warning from test cases
20 | */
21 | export default class ResponsiveContainer extends React.PureComponent {
22 |
23 | render() {
24 |
25 | const { children, layout } = this.props;
26 |
27 | let containerProps = {};
28 | if (!layout || !layout.w) {
29 | containerProps['width'] = 100;
30 | containerProps['aspect'] = 1;
31 | }
32 |
33 | return (
34 |
35 | {children}
36 |
37 | );
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/client/src/components/Settings/index.tsx:
--------------------------------------------------------------------------------
1 | import SettingsButton from './SettingsButton';
2 | import SetupDashboard from './SetupDashboard';
3 |
4 | export { SettingsButton, SetupDashboard };
--------------------------------------------------------------------------------
/client/src/components/Spinner/Spinner.tsx:
--------------------------------------------------------------------------------
1 | import * as request from 'xhr-request';
2 | import * as React from 'react';
3 | import * as _ from 'lodash';
4 |
5 | import CSSTransitionGroup from 'react-addons-css-transition-group';
6 | import CircularProgress from 'react-md/lib/Progress/CircularProgress';
7 | import Snackbar from 'react-md/lib/Snackbars';
8 |
9 | import SpinnerStore, { ISpinnerStoreState } from './SpinnerStore';
10 | import SpinnerActions from './SpinnerActions';
11 |
12 | interface ISpinnerState extends ISpinnerStoreState {
13 | }
14 |
15 | export default class Spinner extends React.Component {
16 |
17 | constructor(props: any) {
18 | super(props);
19 |
20 | this.state = SpinnerStore.getState();
21 |
22 | this.onChange = this.onChange.bind(this);
23 | }
24 |
25 | componentDidMount() {
26 | this.onChange(SpinnerStore.getState());
27 | SpinnerStore.listen(this.onChange);
28 | }
29 |
30 | componentWillUnmount() {
31 | SpinnerStore.unlisten(this.onChange);
32 | }
33 |
34 | onChange(state: ISpinnerState) {
35 | this.setState(state);
36 | }
37 |
38 | render () {
39 |
40 | let refreshing = this.state.pageLoading || this.state.requestLoading || false;
41 |
42 | return (
43 |
44 | {refreshing && }
45 |
46 | );
47 | }
48 | }
--------------------------------------------------------------------------------
/client/src/components/Spinner/SpinnerActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../alt';
2 |
3 | interface ISpinnerActions {
4 | startPageLoading: AltJS.Action;
5 | endPageLoading: AltJS.Action;
6 | startRequestLoading: AltJS.Action;
7 | endRequestLoading: AltJS.Action;
8 | }
9 |
10 | class SpinnerActions extends AbstractActions /*implements ISpinnerActions*/ {
11 | constructor(altobj: AltJS.Alt) {
12 | super(altobj);
13 |
14 | this.generateActions(
15 | 'startPageLoading',
16 | 'endPageLoading',
17 | 'startRequestLoading',
18 | 'endRequestLoading'
19 | );
20 | }
21 | }
22 |
23 | const spinnerActions = alt.createActions(SpinnerActions);
24 |
25 | export default spinnerActions;
26 |
--------------------------------------------------------------------------------
/client/src/components/Spinner/SpinnerStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../../alt';
2 |
3 | import { Toast, ToastActions, IToast } from '../Toast';
4 | import spinnerActions from './SpinnerActions';
5 |
6 | export interface ISpinnerStoreState {
7 | pageLoading?: number;
8 | requestLoading?: number;
9 | }
10 |
11 | const openOriginal = XMLHttpRequest.prototype.open;
12 | const sendOriginal = XMLHttpRequest.prototype.send;
13 |
14 | XMLHttpRequest.prototype.open = function(method: string, url: string, async?: boolean, _?: string, __?: string) {
15 | spinnerActions.startRequestLoading.defer(null);
16 | openOriginal.apply(this, arguments);
17 | };
18 |
19 | XMLHttpRequest.prototype.send = function(data: any) {
20 | let _xhr: XMLHttpRequest = this;
21 | _xhr.onreadystatechange = (response) => {
22 |
23 | // readyState === 4: means the response is complete
24 | if (_xhr.readyState === 4) {
25 | spinnerActions.endRequestLoading.defer(null);
26 |
27 | if (_xhr.status === 429) {
28 | _429ApplicationInsights();
29 | }
30 | }
31 | };
32 | sendOriginal.apply(_xhr, arguments);
33 | };
34 |
35 | function _429ApplicationInsights() {
36 | let toast: IToast = { text: 'You have reached the maximum number of Application Insights requests.' };
37 | ToastActions.addToast(toast);
38 | }
39 |
40 | class SpinnerStore extends AbstractStoreModel implements ISpinnerStoreState {
41 |
42 | pageLoading: number;
43 | requestLoading: number;
44 |
45 | constructor() {
46 | super();
47 |
48 | this.pageLoading = 0;
49 | this.requestLoading = 0;
50 |
51 | this.bindListeners({
52 | startPageLoading: spinnerActions.startPageLoading,
53 | endPageLoading: spinnerActions.endPageLoading,
54 | startRequestLoading: spinnerActions.startRequestLoading,
55 | endRequestLoading: spinnerActions.endRequestLoading,
56 | });
57 | }
58 |
59 | startPageLoading(): void {
60 | this.pageLoading++;
61 | }
62 |
63 | endPageLoading(): void {
64 | this.pageLoading--;
65 | }
66 |
67 | startRequestLoading(): void {
68 | this.requestLoading++;
69 | }
70 |
71 | endRequestLoading(): void {
72 | this.requestLoading--;
73 | }
74 | }
75 |
76 | const spinnerStore = alt.createStore(SpinnerStore as any, 'SpinnerStore');
77 |
78 | export default spinnerStore;
79 |
--------------------------------------------------------------------------------
/client/src/components/Spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import Spinner from './Spinner';
2 | import SpinnerActions from './SpinnerActions';
3 | import SpinnerStore from './SpinnerStore';
4 |
5 | export {
6 | Spinner,
7 | SpinnerActions,
8 | SpinnerStore
9 | };
--------------------------------------------------------------------------------
/client/src/components/Toast/Toast.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import ToastStore, { IToastStoreState, IToast } from './ToastStore';
4 | import ToastActions from './ToastActions';
5 |
6 | import Snackbar from 'react-md/lib/Snackbars';
7 |
8 | export default class Toast extends React.Component {
9 |
10 | constructor(props: any) {
11 | super(props);
12 |
13 | this.state = ToastStore.getState();
14 |
15 | this.onChange = this.onChange.bind(this);
16 | this.removeToast = this.removeToast.bind(this);
17 | }
18 |
19 | onChange(state: IToastStoreState) {
20 | let { toasts, autohide, autohideTimeout } = state;
21 | this.setState({
22 | toasts: toasts.map(o => ({ text: o.text })),
23 | autohide,
24 | autohideTimeout
25 | });
26 | }
27 |
28 | componentDidMount() {
29 | ToastStore.listen(this.onChange);
30 | }
31 |
32 | render() {
33 | const {toasts, autohide, autohideTimeout} = this.state;
34 | return (
35 |
42 | );
43 | }
44 |
45 | private removeToast() {
46 | ToastActions.removeToast();
47 | }
48 | }
--------------------------------------------------------------------------------
/client/src/components/Toast/ToastActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../alt';
2 | import { IToast } from './ToastStore';
3 |
4 | interface IToastActions {
5 | showText(test: string): IToast;
6 | addToast(toast: IToast): IToast;
7 | removeToast(): void;
8 | }
9 |
10 | class ToastActions extends AbstractActions {
11 | constructor(altobj: AltJS.Alt) {
12 | super(altobj);
13 |
14 | this.generateActions(
15 | 'addToast',
16 | 'removeToast'
17 | );
18 | }
19 |
20 | addToast(toast: IToast): IToast {
21 | return toast;
22 | }
23 |
24 | showText(text: string): IToast {
25 | return this.addToast({ text });
26 | }
27 | }
28 |
29 | const toastActions = alt.createActions(ToastActions);
30 |
31 | export default toastActions;
32 |
--------------------------------------------------------------------------------
/client/src/components/Toast/ToastStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../../alt';
2 |
3 | import toastActions from './ToastActions';
4 |
5 | export interface IToast {
6 | text: string;
7 | action?: any;
8 | }
9 |
10 | export interface IToastStoreState {
11 | toasts: IToast[];
12 | queued: Array;
13 | autohideTimeout: number;
14 | autohide: boolean;
15 | }
16 |
17 | const MIN_TIMEOUT_MS = 3000;
18 | const AVG_WORDS_PER_SEC: number = 2;
19 |
20 | class ToastStore extends AbstractStoreModel implements IToastStoreState {
21 | toasts: IToast[];
22 | queued: Array;
23 | autohideTimeout: number;
24 | autohide: boolean;
25 |
26 | constructor() {
27 | super();
28 |
29 | this.toasts = [];
30 | this.queued = Array();
31 | this.autohideTimeout = MIN_TIMEOUT_MS;
32 | this.autohide = true;
33 |
34 | this.bindListeners({
35 | addToast: toastActions.addToast,
36 | removeToast: toastActions.removeToast,
37 | });
38 | }
39 |
40 | addToast(toast: IToast): void {
41 | if (this.toastExists(toast)) {
42 | return; // ignore dups
43 | }
44 | if (this.toasts.length === 0) {
45 | this.toasts.push(toast);
46 | } else {
47 | this.queued.push(toast);
48 | }
49 | this.updateSnackbarAttributes(toast);
50 | }
51 |
52 | removeToast(): void {
53 | if (this.queued.length > 0) {
54 | this.toasts = this.queued.splice(0, 1);
55 | } else if (this.toasts.length > 0) {
56 | const [, ...toasts] = this.toasts;
57 | this.toasts = toasts;
58 | }
59 | }
60 |
61 | private toastExists(toast: IToast): boolean {
62 | return this.toasts.findIndex(x => x.text === toast.text) > -1
63 | || this.queued.findIndex(x => x.text === toast.text) > -1;
64 | }
65 |
66 | private updateSnackbarAttributes(toast: IToast): void {
67 | const words = toast.text.split(' ').length;
68 | this.autohideTimeout = Math.max(MIN_TIMEOUT_MS, (words / AVG_WORDS_PER_SEC) * 1000);
69 | this.autohide = !toast.action;
70 | }
71 | }
72 |
73 | const toastStore = alt.createStore(ToastStore as AltJS.StoreModel, 'ToastStore');
74 |
75 | export default toastStore;
76 |
--------------------------------------------------------------------------------
/client/src/components/Toast/index.ts:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import Toast from './Toast';
4 | import ToastActions from './ToastActions';
5 | import ToastStore, { IToast, IToastStoreState } from './ToastStore';
6 |
7 | export {
8 | Toast,
9 | ToastActions,
10 | ToastStore,
11 | IToast,
12 | IToastStoreState
13 | };
--------------------------------------------------------------------------------
/client/src/components/Tooltip/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import injectTooltip from 'react-md/lib/Tooltips';
3 |
4 | const Tooltip = injectTooltip(
5 | ({children, className, tooltip, ...props }) => (
6 |
7 | {tooltip}
8 | {children}
9 |
10 | ));
11 |
12 | Tooltip.propTypes = {
13 | children: React.PropTypes.node,
14 | className: React.PropTypes.string,
15 | tooltip: React.PropTypes.node,
16 | };
17 |
18 | export default Tooltip;
--------------------------------------------------------------------------------
/client/src/components/Tooltip/TooltipFontIcon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import FontIcon from 'react-md/lib/FontIcons';
3 | import injectTooltip from 'react-md/lib/Tooltips';
4 |
5 | // Material icons shouldn't have any other children other than the child string and
6 | // it gets converted into a span if the tooltip is added, so we add a container
7 | // around the two.
8 | const TooltipFontIcon = injectTooltip(({
9 | children, iconClassName, className, tooltip, forceIconFontSize, forceIconSize, style, iconStyle, ...props }) => (
10 |
11 |
12 | {tooltip}
13 |
19 | {children}
20 |
21 |
22 | ));
23 |
24 | TooltipFontIcon.propTypes = {
25 | children: React.PropTypes.string.isRequired,
26 | className: React.PropTypes.string,
27 | iconClassName: React.PropTypes.string,
28 | tooltip: React.PropTypes.node,
29 | forceIconFontSize: React.PropTypes.bool,
30 | forceIconSize: React.PropTypes.number,
31 | style: React.PropTypes.object,
32 | iconStyle: React.PropTypes.object
33 | };
34 |
35 | export default TooltipFontIcon;
--------------------------------------------------------------------------------
/client/src/components/Tooltip/index.tsx:
--------------------------------------------------------------------------------
1 | import Tooltip from './Tooltip';
2 |
3 | export default Tooltip;
--------------------------------------------------------------------------------
/client/src/components/colors/index.ts:
--------------------------------------------------------------------------------
1 | import * as colors from 'material-colors';
2 |
3 | var ThemeColors = [
4 | colors.pink[800],
5 | colors.purple[800],
6 | colors.cyan[800],
7 | colors.red[800],
8 | colors.blue[800],
9 | colors.lightBlue[800],
10 | colors.deepPurple[800],
11 | colors.lime[800],
12 | colors.teal[800]
13 | ];
14 |
15 | var ThemeColors2 = ThemeColors.slice().reverse();
16 |
17 | const DangerColor = colors.red[500];
18 | const PersonColor = colors.teal[700];
19 | const IntentsColor = colors.teal.a700;
20 | const GoodColor = colors.lightBlue[700];
21 | const BadColor = colors.red[700];
22 | const PositiveColor = colors.lightBlue[700];
23 | const NeutralColor = colors.grey[500];
24 |
25 | export default {
26 | ThemeColors,
27 | ThemeColors2,
28 |
29 | DangerColor,
30 | PersonColor,
31 | IntentsColor,
32 |
33 | GoodColor,
34 | BadColor,
35 |
36 | PositiveColor,
37 | NeutralColor,
38 |
39 | getColor: (idx) => {
40 | return ThemeColors[idx];
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/client/src/components/generic/CheckboxFilter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { GenericComponent } from './GenericComponent';
3 | import Checkbox from 'react-md/lib/SelectionControls/Checkbox';
4 |
5 | const style = {
6 | checkbox: {
7 | float: 'left',
8 | paddingTop: '24px'
9 | }
10 | };
11 |
12 | export default class CheckboxFilter extends GenericComponent {
13 |
14 | state = {
15 | values: [],
16 | selectedValues: []
17 | };
18 |
19 | constructor(props: any) {
20 | super(props);
21 |
22 | this.onChange = this.onChange.bind(this);
23 | }
24 |
25 | onChange(newValue: string, checked: boolean, event: React.ChangeEvent) {
26 | var { selectedValues } = this.state;
27 | let newSelectedValues = selectedValues.slice(0);
28 |
29 | const idx = selectedValues.findIndex((x) => x === newValue);
30 | if (idx === -1 && checked) {
31 | newSelectedValues.push(newValue);
32 | } else if (idx > -1 && !checked) {
33 | newSelectedValues.splice(idx, 1);
34 | } else {
35 | console.warn('Unexpected checked filter state:', newValue, checked);
36 | }
37 |
38 | this.trigger('onChange', newSelectedValues);
39 | }
40 |
41 | render() {
42 | var { title } = this.props;
43 | var { selectedValues, values } = this.state;
44 | values = values || [];
45 |
46 | let checkboxes = values.map((value, idx) => {
47 | return (
48 | x === value) !== undefined}
56 | />
57 | );
58 | });
59 |
60 | return (
61 |
62 |
63 | {checkboxes}
64 |
65 | );
66 | }
67 | }
--------------------------------------------------------------------------------
/client/src/components/generic/Detail/index.ts:
--------------------------------------------------------------------------------
1 | import Detail from './Detail';
2 |
3 | export default Detail;
--------------------------------------------------------------------------------
/client/src/components/generic/Dialogs/DialogsActions.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractActions } from '../../../alt';
2 |
3 | interface IDialogsActions {
4 | openDialog(dialogName: string, args: { [id: string]: Object }): any;
5 | closeDialog(): any;
6 | }
7 |
8 | class DialogsActions extends AbstractActions implements IDialogsActions {
9 |
10 | openDialog(dialogName: string, args: { [id: string]: Object }) {
11 | return { dialogName, args };
12 | }
13 |
14 | closeDialog() {
15 | return {};
16 | }
17 | }
18 |
19 | const dialogsActions = alt.createActions(DialogsActions);
20 |
21 | export default dialogsActions;
22 |
--------------------------------------------------------------------------------
/client/src/components/generic/Dialogs/DialogsStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../../../alt';
2 |
3 | import dialogsActions from './DialogsActions';
4 |
5 | interface IDialogsStoreState {
6 | dialogsStack: { dialogName: string, args: any }[];
7 | dialogId: string;
8 | dialogArgs: any;
9 | }
10 |
11 | class DialogsStore extends AbstractStoreModel implements IDialogsStoreState {
12 |
13 | dialogsStack: { dialogName: string, args: any }[];
14 | dialogId: string;
15 | dialogArgs: any;
16 |
17 | constructor() {
18 | super();
19 |
20 | this.dialogsStack = [];
21 | this.dialogId = null;
22 | this.dialogArgs = null;
23 |
24 | this.bindListeners({
25 | openDialog: dialogsActions.openDialog,
26 | closeDialog: dialogsActions.closeDialog
27 | });
28 | }
29 |
30 | openDialog(params: { dialogName: string, args: any }) {
31 | this.dialogsStack.push(params);
32 | this.dialogId = params.dialogName;
33 | this.dialogArgs = params.args;
34 | }
35 |
36 | closeDialog() {
37 | this.dialogsStack.pop();
38 | var dialog = this.dialogsStack.length > 0 ?
39 | this.dialogsStack[this.dialogsStack.length - 1] :
40 | { dialogName: null, args: null };
41 | this.dialogId = dialog.dialogName;
42 | this.dialogArgs = dialog.args;
43 | }
44 | }
45 |
46 | const dialogsStore = alt.createStore(DialogsStore as AltJS.StoreModel, 'DialogsStore');
47 |
48 | export default dialogsStore;
49 |
--------------------------------------------------------------------------------
/client/src/components/generic/Dialogs/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import Dialog from './Dialog';
4 | import DialogsActions from './DialogsActions';
5 | import DialogsStore from './DialogsStore';
6 |
7 | function loadDialogsFromDashboard(dashboard: IDashboardConfig): JSX.Element[] {
8 |
9 | if (!dashboard.dialogs) {
10 | return null;
11 | }
12 |
13 | var dialogs = dashboard.dialogs.map((dialog, idx) =>
14 |
15 | );
16 |
17 | return dialogs;
18 | }
19 |
20 | export {
21 | loadDialogsFromDashboard,
22 | Dialog,
23 | DialogsActions,
24 | DialogsStore
25 | };
--------------------------------------------------------------------------------
/client/src/components/generic/RadialBarChartCard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { GenericComponent, IGenericProps, IGenericState } from './GenericComponent';
3 | import Card from '../Card';
4 | import ResponsiveContainer from '../ResponsiveContainer';
5 | import { RadialBarChart, RadialBar, PolarGrid, Tooltip, Legend } from 'recharts';
6 |
7 | import * as _ from 'lodash';
8 | import * as moment from 'moment';
9 |
10 | import colors from '../colors';
11 | var { ThemeColors } = colors;
12 |
13 | interface IRadarProps extends IGenericProps {
14 | props: {
15 |
16 | nameKey: string;
17 | };
18 | }
19 |
20 | interface IRadarState extends IGenericState {
21 | values: Object[];
22 | }
23 |
24 | export default class RadialBarChartCard extends GenericComponent {
25 |
26 | state = {
27 | values: [],
28 | bars: []
29 | };
30 |
31 | constructor(props: any) {
32 | super(props);
33 |
34 | this.handleClick = this.handleClick.bind(this);
35 | }
36 |
37 | handleClick(data: any, index: number) {
38 | this.trigger('onBarClick', data.payload);
39 | }
40 |
41 | render() {
42 |
43 | var { values } = this.state;
44 | var { title, subtitle, props, layout } = this.props;
45 |
46 | if (!values) {
47 | return null;
48 | }
49 |
50 | const domain = 100;
51 |
52 | const data = [
53 | { name: 'alarm.set', uv: 31.47, pv: 2400, fill: '#8884d8' },
54 | { name: '*:/', uv: 26.69, pv: 4567, fill: '#83a6ed' },
55 | { name: 'none', uv: 15.69, pv: 1398, fill: '#8dd1e1' },
56 | { name: 'invalid property type object', uv: 8.22, pv: 9800, fill: '#82ca9d' }
57 | ];
58 |
59 | return (
60 |
61 |
62 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | );
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/client/src/components/generic/SplitPanel/index.ts:
--------------------------------------------------------------------------------
1 | import SplitPanel from './SplitPanel';
2 |
3 | export default SplitPanel;
--------------------------------------------------------------------------------
/client/src/components/generic/Table/index.ts:
--------------------------------------------------------------------------------
1 | import Table from './Table';
2 |
3 | export default Table;
--------------------------------------------------------------------------------
/client/src/components/generic/TextFilter.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { GenericComponent } from './GenericComponent';
3 | import SelectField from 'react-md/lib/SelectFields';
4 |
5 | export default class TextFilter extends GenericComponent {
6 |
7 | static defaultProps = {
8 | title: 'Select'
9 | };
10 |
11 | static fromSource(source: string) {
12 | return {
13 | selectedValue: GenericComponent.sourceFormat(source, 'values-selected'),
14 | values: GenericComponent.sourceFormat(source, 'values-all')
15 | };
16 | }
17 |
18 | constructor(props: any) {
19 | super(props);
20 |
21 | this.onChange = this.onChange.bind(this);
22 | }
23 |
24 | onChange(newValue: any) {
25 | this.trigger('onChange', newValue);
26 | }
27 |
28 | render() {
29 | var { selectedValue, values } = this.state;
30 | var { title } = this.props;
31 | values = values || [];
32 |
33 | return (
34 |
44 | );
45 | }
46 | }
--------------------------------------------------------------------------------
/client/src/components/generic/plugins.tsx:
--------------------------------------------------------------------------------
1 | import PieData from './PieData';
2 | import Timeline from './Timeline';
3 | import Scatter from './Scatter';
4 | import BarData from './BarData';
5 | import Area from './Area';
6 | import Scorecard from './Scorecard';
7 | import RadarChartCard from './RadarChartCard';
8 | import RadialBarChartCard from './RadialBarChartCard';
9 | import SimpleRadialBarChartCard from './SimpleRadialBarChartCard';
10 | import MapData from './MapData';
11 | // filters
12 | import TextFilter from './TextFilter';
13 | import CheckboxFilter from './CheckboxFilter';
14 | import MenuFilter from './MenuFilter';
15 | import DatePickerFilter from './DatePickerFilter';
16 | // dialog views
17 | import Table from './Table';
18 | import Detail from './Detail';
19 | import SplitPanel from './SplitPanel';
20 | import RequestButton from './RequestButton';
21 |
22 | export default {
23 | PieData,
24 | Timeline,
25 | Scatter,
26 | BarData,
27 | Area,
28 | Scorecard,
29 | TextFilter,
30 | CheckboxFilter,
31 | MenuFilter,
32 | DatePickerFilter,
33 | Table,
34 | Detail,
35 | SplitPanel,
36 | MapData,
37 | RadarChartCard,
38 | RadialBarChartCard,
39 | SimpleRadialBarChartCard,
40 | RequestButton,
41 | };
--------------------------------------------------------------------------------
/client/src/data-sources/connections/Connection.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface IConnection {
4 | type: string;
5 | params: string[];
6 | editor?: new (props: any) => ConnectionEditor;
7 | }
8 |
9 | interface IConnectionProps {
10 | connection: any;
11 | onParamChange: (paramId: string, paramValue: string) => void;
12 | }
13 |
14 | abstract class ConnectionEditor extends React.Component {
15 |
16 | constructor(props: T1) {
17 | super(props);
18 |
19 | this.onParamChange = this.onParamChange.bind(this);
20 | }
21 |
22 | onParamChange(value: string, event: any) {
23 | if (typeof this.props.onParamChange === 'function') {
24 | const trimmedValue = ('' + value).trim();
25 | this.props.onParamChange(event.target.id, trimmedValue);
26 | }
27 | }
28 | }
29 |
30 | export {
31 | IConnection,
32 | IConnectionProps,
33 | ConnectionEditor
34 | };
--------------------------------------------------------------------------------
/client/src/data-sources/connections/application-insights/index.tsx:
--------------------------------------------------------------------------------
1 | import ApplicationInsightsConnection from './application-insights';
2 |
3 | export default ApplicationInsightsConnection;
--------------------------------------------------------------------------------
/client/src/data-sources/connections/cosmos-db.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import TextField from 'react-md/lib/TextFields';
3 | import Checkbox from 'react-md/lib/SelectionControls/Checkbox';
4 | import Card from 'react-md/lib/Cards/Card';
5 | import CardTitle from 'react-md/lib/Cards/CardTitle';
6 | import Avatar from 'react-md/lib/Avatars';
7 | import FontIcon from 'react-md/lib/FontIcons';
8 | import InfoDrawer from '../../components/common/InfoDrawer';
9 | import { IConnection, ConnectionEditor, IConnectionProps } from './Connection';
10 |
11 | export default class CosmosDBConnection implements IConnection {
12 | type = 'cosmos-db';
13 | params = ['host', 'key'];
14 | editor = CosmosDBConnectionEditor;
15 | }
16 |
17 | class CosmosDBConnectionEditor extends ConnectionEditor {
18 |
19 | render() {
20 | let { connection } = this.props;
21 | // connection = connection || {'ssl':true };
22 | return (
23 |
24 | receipt} />}
27 | style={{ float: 'left'}}
28 | />
29 |
35 |
40 |
41 |
50 |
59 |
60 | );
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/client/src/data-sources/connections/graphql.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import Card from 'react-md/lib/Cards/Card';
3 | import CardTitle from 'react-md/lib/Cards/CardTitle';
4 | import Avatar from 'react-md/lib/Avatars';
5 | import FontIcon from 'react-md/lib/FontIcons';
6 | import TextField from 'react-md/lib/TextFields';
7 | import InfoDrawer from '../../components/common/InfoDrawer';
8 | import { IConnection, ConnectionEditor, IConnectionProps } from './Connection';
9 |
10 | export default class GraphQLConnection implements IConnection {
11 | type = 'graphql';
12 | params = [ 'serviceUrl' ];
13 | editor = GraphQLConnectionEditor;
14 | }
15 |
16 | class GraphQLConnectionEditor extends ConnectionEditor {
17 |
18 | constructor(props: IConnectionProps) {
19 | super(props);
20 |
21 | this.onParamChange = this.onParamChange.bind(this);
22 | }
23 |
24 | onParamChange(value: string, event: any) {
25 | if (typeof this.props.onParamChange === 'function') {
26 | this.props.onParamChange(event.target.id, value);
27 | }
28 | }
29 |
30 | render() {
31 | let { connection } = this.props;
32 | connection = connection || {};
33 |
34 | return (
35 |
36 | receipt} />}
39 | style={{ float: 'left'}}
40 | />
41 |
47 |
48 | Just enter the URL of the GraphQL service you wish to query below.
49 | Currently only publicly accessible GraphQL endpoints are supported.
50 |
51 |
52 |
61 |
62 | );
63 | }
64 | }
--------------------------------------------------------------------------------
/client/src/data-sources/connections/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import ApplicationInsightsConnection from './application-insights';
4 | import GraphQLConnection from './graphql';
5 | import BotFrameworkConnection from './bot-framework';
6 | import CosmosDBConnection from './cosmos-db';
7 | import AzureConnection from './azure';
8 | import { IConnection } from './Connection';
9 |
10 | var connectionTypes = [
11 | ApplicationInsightsConnection,
12 | GraphQLConnection,
13 | AzureConnection,
14 | CosmosDBConnection,
15 | BotFrameworkConnection ];
16 |
17 | var connections: IDict = {};
18 | connectionTypes.forEach(connectionType => {
19 | var newConnection: IConnection = new connectionType();
20 | connections[newConnection.type] = newConnection;
21 | });
22 |
23 | export default connections;
--------------------------------------------------------------------------------
/client/src/data-sources/index.ts:
--------------------------------------------------------------------------------
1 | import { DataSourceConnector, IDataSource, IDataSourceDictionary, IExtrapolationResult } from './DataSourceConnector';
2 |
3 | export {
4 | DataSourceConnector,
5 | IDataSource,
6 | IDataSourceDictionary,
7 | IExtrapolationResult
8 | };
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/ApplicationInsights/ApplicationInsightsApi.ts:
--------------------------------------------------------------------------------
1 | import * as request from 'xhr-request';
2 |
3 | import { appInsightsUri } from './common';
4 |
5 | export interface IApplicationInsightsApi {
6 | callQuery (query: string, callback: () => void): void;
7 | }
8 |
9 | export default class ApplicationInsightsApi implements IApplicationInsightsApi {
10 |
11 | constructor(private appId: string, private apiKey: string) { }
12 |
13 | callQuery(query: string, callback: (error?: Error, json?: any) => void) {
14 | var url = `${appInsightsUri}/${this.appId}/query`;
15 | try {
16 | request(
17 | url,
18 | {
19 | method: 'POST',
20 | json: true,
21 | headers: {
22 | 'x-api-key': this.apiKey
23 | },
24 | body: { query }
25 | },
26 | (error: Error, json: any) => {
27 | if (error) {
28 | callback(error);
29 | }
30 |
31 | callback(null, json);
32 | }
33 | );
34 | } catch (ex) {
35 | callback(ex);
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/ApplicationInsights/common.ts:
--------------------------------------------------------------------------------
1 |
2 | var appInsightsUri = 'https://api.applicationinsights.io/beta/apps';
3 | var appId = process.env.REACT_APP_APP_INSIGHTS_APPID;
4 | var apiKey = process.env.REACT_APP_APP_INSIGHTS_APIKEY;
5 |
6 | export {
7 | appInsightsUri,
8 | appId,
9 | apiKey
10 | };
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/Constant/index.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as _ from 'lodash';
3 | import { DataSourcePlugin, IOptions } from '../DataSourcePlugin';
4 | import ConstantDatasourceSettings from './Settings';
5 |
6 | interface IConstantParams {
7 | values: Array;
8 | selectedValue: string;
9 | }
10 |
11 | export default class Constant extends DataSourcePlugin {
12 |
13 | static editor = ConstantDatasourceSettings;
14 | type = 'Constant';
15 | defaultProperty = 'selectedValue';
16 |
17 | constructor(options: IOptions, connections: IDict) {
18 | super(options, connections);
19 |
20 | var props = this._props;
21 | var params = options.params;
22 |
23 | props.actions.push.apply(props.actions, [ 'initialize', 'updateSelectedValue', 'updateSelectedValues' ]);
24 | }
25 |
26 | initialize() {
27 | var { selectedValue, values } = this._props.params;
28 | return { selectedValue, values };
29 | }
30 |
31 | /**
32 | * updateDependencies - called when dependencies are created
33 | */
34 | dependenciesUpdated(dependencies: IDictionary, args: IDictionary, callback: (result: any) => void) {
35 | var result = _.extend(dependencies, args);
36 |
37 | if (typeof callback === 'function') {
38 | return callback(result);
39 | }
40 |
41 | return result;
42 | }
43 |
44 | updateSelectedValue(dependencies: IDictionary, selectedValue: any) {
45 | return { selectedValue };
46 | }
47 |
48 | updateSelectedValues(dependencies: IDictionary, selectedValues: any) {
49 | return { selectedValues };
50 | }
51 | }
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/GraphQL.ts:
--------------------------------------------------------------------------------
1 | import * as request from 'xhr-request';
2 |
3 | import { DataSourcePlugin, IOptions } from './DataSourcePlugin';
4 | import GraphQLConnection from '../connections/graphql';
5 | import { DataSourceConnector } from '../DataSourceConnector';
6 |
7 | let connectionType = new GraphQLConnection();
8 |
9 | interface IGraphQLParams {
10 | query: string;
11 | variables: Object;
12 | }
13 |
14 | export default class GraphQL extends DataSourcePlugin {
15 | type = 'GraphQL';
16 | defaultProperty = 'data';
17 | connectionType = connectionType.type;
18 |
19 | constructor(options: IOptions, connections: IDict) {
20 | super(options, connections);
21 | this.validateParams(this._props.params);
22 | }
23 |
24 | dependenciesUpdated(dependencies: any) {
25 | // Ensure dependencies exist
26 | const isAnyDependencyMissing = Object.keys(this.getDependencies()).some(key => dependencies[key] == null);
27 | if (isAnyDependencyMissing) {
28 | return dispatch => dispatch();
29 | }
30 |
31 | // Validate connection
32 | const connection = this.getConnection();
33 | const { serviceUrl } = connection;
34 | if (!connection || !serviceUrl) {
35 | return dispatch => dispatch();
36 | }
37 |
38 | const params = this.getParams() || ({} as IGraphQLParams);
39 | const query = params.query || '';
40 | const variables = dependencies['variables'] || params.variables;
41 |
42 | return dispatch => {
43 | request('/graphql/query', {
44 | method: 'POST',
45 | json: true,
46 | body: {
47 | serviceUrl: serviceUrl,
48 | query: query,
49 | variables: variables
50 | }
51 | }, (err, json) => {
52 | const error = err || (json['errors'] && json['errors'][0]);
53 | if (error || json['errors']) {
54 | return this.failure(error);
55 | }
56 |
57 | return dispatch(json);
58 | });
59 | };
60 | }
61 |
62 | updateSelectedValues(dependencies: IDictionary, selectedValues: any) {
63 | if (Array.isArray(selectedValues)) {
64 | return Object.assign(dependencies, { 'selectedValues': selectedValues });
65 | } else {
66 | return Object.assign(dependencies, { ... selectedValues });
67 | }
68 | }
69 |
70 | private validateParams(params: IGraphQLParams): void {
71 | return;
72 | }
73 | }
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/PluginsMapping.ts:
--------------------------------------------------------------------------------
1 | import Constant from './Constant/index';
2 |
3 | export default {
4 | 'ApplicationInsights/Query': 'ApplicationInsights/Query',
5 | 'CosmosDB/Query': 'CosmosDB/Query',
6 | 'Azure': 'Azure',
7 | 'Constant': 'Constant/index',
8 | 'Sample': 'Sample',
9 | 'BotFramework/DirectLine': 'BotFramework/DirectLine',
10 | 'GraphQL': 'GraphQL',
11 | };
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/Sample.ts:
--------------------------------------------------------------------------------
1 |
2 | import * as _ from 'lodash';
3 | import { DataSourcePlugin, IOptions } from './DataSourcePlugin';
4 |
5 | interface ISampleParams {
6 | samples: IDictionary;
7 | }
8 |
9 | export default class Sample extends DataSourcePlugin {
10 |
11 | type = 'Sample';
12 | defaultProperty = 'values';
13 |
14 | constructor(options: IOptions, connections: IDict) {
15 | super(options, connections);
16 |
17 | var props = this._props;
18 | var params = options.params;
19 |
20 | props.actions.push.apply(props.actions, [ 'initialize' ]);
21 | }
22 |
23 | initialize() {
24 | let { samples } = _.cloneDeep(this._props.params);
25 | return samples || {};
26 | }
27 |
28 | /**
29 | * updateDependencies - called when dependencies are created
30 | */
31 | dependenciesUpdated(dependencies: IDictionary, args: IDictionary, callback: (result: any) => void) {
32 | let result = _.extend(dependencies, args);
33 | let { samples } = this.getParams();
34 |
35 | _.extend(result, samples);
36 |
37 | if (typeof callback === 'function') {
38 | return callback(result);
39 | }
40 |
41 | return result;
42 | }
43 |
44 | updateSelectedValues(dependencies: IDictionary, selectedValues: any) {
45 | if (Array.isArray(selectedValues)) {
46 | return _.extend(dependencies, { 'selectedValues': selectedValues });
47 | } else {
48 | return _.extend(dependencies, { ... selectedValues });
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/client/src/data-sources/plugins/index.ts:
--------------------------------------------------------------------------------
1 | import ApplicationInsightsQuery from './ApplicationInsights/Query';
2 | import CosmosDBQuery from './CosmosDB/Query';
3 | import BotFrameworkDirectLine from './BotFramework/DirectLine';
4 | import Azure from './Azure';
5 | import Constant from './Constant';
6 | import Sample from './Sample';
7 | import GraphQL from './GraphQL';
8 |
9 | export default {
10 | 'ApplicationInsights/Query': ApplicationInsightsQuery,
11 | 'CosmosDB/Query': CosmosDBQuery,
12 | 'Azure': Azure,
13 | 'Constant': Constant,
14 | 'BotFramework/DirectLine': BotFrameworkDirectLine,
15 | 'Sample': Sample,
16 | 'GraphQL': GraphQL
17 | };
18 |
--------------------------------------------------------------------------------
/client/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { Router, browserHistory } from 'react-router';
4 | import routes from './routes';
5 |
6 | import './index.css';
7 | import 'react-grid-layout/css/styles.css';
8 | import 'react-resizable/css/styles.css';
9 |
10 | ReactDOM.render(
11 |
12 | {routes}
13 | ,
14 | document.getElementById('root'));
--------------------------------------------------------------------------------
/client/src/pages/Dashboard.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import DashboardComponent from '../components/Dashboard';
4 | import { SetupDashboard } from '../components/Settings';
5 |
6 | import ConfigurationsActions from '../actions/ConfigurationsActions';
7 | import ConfigurationsStore from '../stores/ConfigurationsStore';
8 |
9 | interface IDashboardState {
10 | dashboard?: IDashboardConfig;
11 | connections?: IConnections;
12 | connectionsMissing?: boolean;
13 | }
14 |
15 | export default class Dashboard extends React.Component {
16 |
17 | state: IDashboardState = {
18 | dashboard: null,
19 | connections: {},
20 | connectionsMissing: false
21 | };
22 |
23 | constructor(props: any) {
24 | super(props);
25 |
26 | this.updateConfiguration = this.updateConfiguration.bind(this);
27 | }
28 |
29 | updateConfiguration(newState: IDashboardState) {
30 | this.setState(newState);
31 | }
32 |
33 | componentDidMount() {
34 |
35 | this.setState(ConfigurationsStore.getState());
36 | ConfigurationsStore.listen(this.updateConfiguration);
37 | }
38 |
39 | componentWillUnmount() {
40 | ConfigurationsStore.unlisten(this.updateConfiguration);
41 | }
42 |
43 | render() {
44 | var { dashboard, connections, connectionsMissing } = this.state;
45 |
46 | if (!dashboard) {
47 | return null;
48 | }
49 |
50 | if (connectionsMissing) {
51 | return (
52 |
53 | );
54 | }
55 |
56 | return (
57 |
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/client/src/pages/Home.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import HomeComponent from '../components/Home';
4 |
5 | export default class Home extends React.Component {
6 |
7 | render() {
8 |
9 | return (
10 |
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/pages/NotFound.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default () => (
4 |
5 |
6 | 404 Not Found :(
7 |
8 |
9 | );
--------------------------------------------------------------------------------
/client/src/pages/Setup.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | import SetupComponent from '../components/Setup';
4 |
5 | export default class Setup extends React.Component {
6 |
7 | render() {
8 |
9 | return (
10 |
11 | );
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/routes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Route } from 'react-router';
3 |
4 | import App from './components/App';
5 | import NotFound from './pages/NotFound';
6 | import Home from './pages/Home';
7 | import Dashboard from './pages/Dashboard';
8 | import Setup from './pages/Setup';
9 |
10 | export default (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | );
--------------------------------------------------------------------------------
/client/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | process.env.REACT_APP_APP_INSIGHTS_APPID = "APPID";
2 | process.env.REACT_APP_APP_INSIGHTS_APIKEY = "APIKEY";
--------------------------------------------------------------------------------
/client/src/stores/AccountStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../alt';
2 |
3 | import accountActions from '../actions/AccountActions';
4 |
5 | interface IAccountStoreState {
6 | account: IDictionary;
7 | }
8 |
9 | class AccountStore extends AbstractStoreModel implements IAccountStoreState {
10 |
11 | account: IDictionary;
12 |
13 | constructor() {
14 | super();
15 |
16 | this.account = null;
17 |
18 | this.bindListeners({
19 | updateAccount: accountActions.updateAccount
20 | });
21 | }
22 |
23 | updateAccount(state: any) {
24 | this.account = state.account;
25 | }
26 | }
27 |
28 | const accountStore = alt.createStore((AccountStore as AltJS.StoreModel), 'AccountStore');
29 |
30 | export default accountStore;
31 |
--------------------------------------------------------------------------------
/client/src/stores/ConnectionsStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../alt';
2 |
3 | import connectionsActions from '../actions/ConnectionsActions';
4 |
5 | interface IConnectionsStoreState {
6 | connections: IDictionary;
7 | }
8 |
9 | class ConnectionsStore extends AbstractStoreModel implements IConnectionsStoreState {
10 |
11 | connections: IDictionary;
12 |
13 | constructor() {
14 | super();
15 |
16 | this.connections = {};
17 |
18 | this.bindListeners({
19 | updateConnection: connectionsActions.updateConnection
20 | });
21 | }
22 |
23 | updateConnection(connectionName: string, args: IDictionary) {
24 | this.connections[connectionName] = args;
25 | }
26 | }
27 |
28 | const connectionsStore =
29 | alt.createStore((ConnectionsStore as AltJS.StoreModel), 'ConnectionsStore');
30 |
31 | export default connectionsStore;
32 |
--------------------------------------------------------------------------------
/client/src/stores/SettingsStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../alt';
2 |
3 | import settingsActions from '../actions/SettingsActions';
4 |
5 | export interface ISettingsStoreState {
6 | isSavingSettings: boolean;
7 | }
8 |
9 | class SettingsStore extends AbstractStoreModel implements ISettingsStoreState {
10 |
11 | isSavingSettings: boolean;
12 |
13 | constructor() {
14 | super();
15 |
16 | this.bindListeners({
17 | saveSettings: settingsActions.saveSettings,
18 | saveSettingsCompleted: settingsActions.saveSettingsCompleted
19 | });
20 | }
21 |
22 | saveSettings() {
23 | this.isSavingSettings = true;
24 | }
25 | saveSettingsCompleted() {
26 | this.isSavingSettings = false;
27 | }
28 | }
29 |
30 | const settingsStore = alt.createStore((SettingsStore as AltJS.StoreModel), 'SettingsStore');
31 |
32 | export default settingsStore;
33 |
--------------------------------------------------------------------------------
/client/src/stores/SetupStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../alt';
2 | import * as _ from 'lodash';
3 |
4 | import connections from '../data-sources/connections';
5 | import { DataSourceConnector, IDataSourceDictionary } from '../data-sources';
6 | import SetupActions from '../actions/SetupActions';
7 |
8 | export interface ISetupStoreState extends ISetupConfig {
9 | loaded: boolean;
10 | saveSuccess: boolean;
11 | }
12 |
13 | class SetupStore extends AbstractStoreModel implements ISetupStoreState {
14 |
15 | admins: string[];
16 | stage: string;
17 | enableAuthentication: boolean;
18 | allowHttp: boolean;
19 | redirectUrl: string;
20 | clientID: string;
21 | clientSecret: string;
22 | issuer: string;
23 | loaded: boolean;
24 | saveSuccess: boolean;
25 |
26 | constructor() {
27 | super();
28 |
29 | this.admins = [];
30 | this.stage = '';
31 | this.enableAuthentication = false;
32 | this.allowHttp = false;
33 | this.redirectUrl = '';
34 | this.clientID = '';
35 | this.clientSecret = '';
36 | this.loaded = false;
37 | this.saveSuccess = false;
38 | this.issuer = '';
39 |
40 | this.bindListeners({
41 | load: SetupActions.load
42 | });
43 | }
44 |
45 | load(setupConfig: ISetupConfig) {
46 |
47 | if (setupConfig) {
48 | this.admins = setupConfig.admins;
49 | this.stage = setupConfig.stage;
50 | this.enableAuthentication = setupConfig.enableAuthentication;
51 | this.allowHttp = setupConfig.allowHttp;
52 | this.redirectUrl = setupConfig.redirectUrl;
53 | this.clientID = setupConfig.clientID;
54 | this.clientSecret = setupConfig.clientSecret;
55 | this.issuer = setupConfig.issuer;
56 | }
57 |
58 | this.loaded = true;
59 | this.saveSuccess = true;
60 |
61 | setTimeout(() => { this.saveSuccess = false; }, 500);
62 | }
63 | }
64 |
65 | const setupStore = alt.createStore((SetupStore as AltJS.StoreModel), 'SetupStore');
66 |
67 | export default setupStore;
68 |
--------------------------------------------------------------------------------
/client/src/stores/VisibilityStore.ts:
--------------------------------------------------------------------------------
1 | import alt, { AbstractStoreModel } from '../alt';
2 |
3 | import visibilityActions from '../actions/VisibilityActions';
4 |
5 | class VisibilityStore extends AbstractStoreModel {
6 |
7 | flags: IDict;
8 |
9 | constructor() {
10 | super();
11 |
12 | this.flags = {};
13 |
14 | this.bindListeners({
15 | updateFlags: [visibilityActions.turnFlagOn, visibilityActions.turnFlagOff, visibilityActions.setFlags]
16 | });
17 | }
18 |
19 | updateFlags(flags: any) {
20 | if (flags) {
21 | Object.keys(flags).forEach(flag => {
22 | this.flags[flag] = flags[flag];
23 | });
24 | }
25 | }
26 | }
27 |
28 | const visibilityStore = alt.createStore((VisibilityStore as AltJS.StoreModel) , 'VisibilityStore');
29 |
30 | export default visibilityStore;
31 |
--------------------------------------------------------------------------------
/client/src/tests/common/TokenInput.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import TokenInput from '../../components/common/TokenInput';
4 | import * as TestUtils from 'react-dom/test-utils';
5 |
6 | describe('Info drawer', () => {
7 | let tokenInput;
8 |
9 | it('check info drawer is loading', () => {
10 | Object.defineProperty(window, "matchMedia", {
11 | value: jest.fn(() => { return { matches: true } })
12 | });
13 |
14 | let tokensObject = [];
15 | tokenInput = TestUtils.renderIntoDocument();
17 |
18 | TestUtils.isElementOfType(tokenInput, 'div');
19 | });
20 | });
--------------------------------------------------------------------------------
/client/src/tests/common/infoDrawer.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import InfoDrawer from '../../components/common/InfoDrawer';
4 | import * as TestUtils from 'react-dom/test-utils';
5 |
6 | describe('Info drawer', () => {
7 | let infoDrawer;
8 |
9 | it('check info drawer is loading', () => {
10 | Object.defineProperty(window, "matchMedia", {
11 | value: jest.fn(() => { return { matches: true } })
12 | });
13 |
14 | TestUtils.renderIntoDocument();
20 |
21 | TestUtils.isElementOfType(infoDrawer, 'div');
22 | });
23 | });
--------------------------------------------------------------------------------
/client/src/tests/data-formats/data-formats.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import Sample from '../../data-sources/plugins/Sample';
3 |
4 | import { formatTests } from './formats';
5 | import * as formats from '../../utils/data-formats';
6 |
7 | describe('Data Formats', () => {
8 |
9 | let sampleMockPlugin = new Sample( {
10 | params: {
11 | values: [ 'value 1', 'value 2', 'value 3' ],
12 | samples: {
13 | values: [ 'value1', 'value2', 'value3' ]
14 | }
15 | }
16 | }, {});
17 |
18 | Object.keys(formatTests).forEach(testFormat => {
19 |
20 | it (testFormat, () => {
21 |
22 | let testCase = formatTests[testFormat];
23 | let tests = [];
24 | if (!Array.isArray(testCase)) {
25 | tests = [ testCase ];
26 | } else {
27 | tests = testCase;
28 | }
29 |
30 | tests.forEach(test => {
31 | let values = test.state;
32 |
33 | let mockPlugin = sampleMockPlugin;
34 | if (test.params) {
35 | mockPlugin = new Sample({
36 | params: test.params
37 | }, {});
38 | }
39 |
40 | let dependencies = test.dependencies || {};
41 |
42 | let result = formats[testFormat](test.format, test.state, dependencies, mockPlugin, {});
43 | expect(result).toMatchObject(test.expected);
44 | });
45 | });
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/bars.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default {
4 | format: {
5 | type: 'bars',
6 | args: {
7 | barsField: 'barField',
8 | seriesField: 'seriesField'
9 | }
10 | },
11 | state: {
12 | values: [
13 | { count: 10, barField: 'bar 1', seriesField: 'series1Value' },
14 | { count: 15, barField: 'bar 2', seriesField: 'series1Value' },
15 | { count: 20, barField: 'bar 1', seriesField: 'series2Value' },
16 | { count: 44, barField: 'bar 3', seriesField: 'series2Value' },
17 | ]
18 | },
19 | expected: {
20 | 'bars': ['series1Value', 'series2Value'],
21 | 'bar-values': [
22 | {'series1Value': 10, 'series2Value': 20, 'value': 'bar 1'},
23 | {'series1Value': 15, 'value': 'bar 2'},
24 | {'series2Value': 44, 'value': 'bar 3'}
25 | ]
26 | }
27 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/filter.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default {
4 | format: 'filter',
5 | state: {
6 | values: [
7 | { value: 'value 1' },
8 | { value: 'value 2' },
9 | { value: 'value 3' }
10 | ]
11 | },
12 | expected: {
13 | 'values-all': [ 'value 1', 'value 2', 'value 3' ],
14 | 'values-selected': [ ],
15 | }
16 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/filtered_samples.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import { IFormatTest } from './formats';
3 |
4 | const values = [
5 | { name: 'skype-en', value: 9, locale: 'en', channel: 'skype' },
6 | { name: 'skype-fr', value: 8, locale: 'fr', channel: 'skype' },
7 | { name: 'skype-de', value: 7, locale: 'de', channel: 'skype' },
8 | { name: 'messenger-en', value: 6, locale: 'en', channel: 'messenger' },
9 | { name: 'messenger-fr', value: 5, locale: 'fr', channel: 'messenger' },
10 | { name: 'messenger-de', value: 4, locale: 'de', channel: 'messenger' },
11 | { name: 'slack-en', value: 3, locale: 'en', channel: 'slack' },
12 | { name: 'slack-fr', value: 2, locale: 'fr', channel: 'slack' },
13 | { name: 'slack-de', value: 1, locale: 'de', channel: 'slack' }
14 | ];
15 |
16 | export default [
17 | {
18 | format: 'filtered_samples',
19 | params: {
20 | samples: { values },
21 | filters: [
22 | { dependency: 'selectedFilter1', queryProperty: 'locale' },
23 | { dependency: 'selectedFilter2', queryProperty: 'channel' }
24 | ]
25 | },
26 | dependencies: {
27 | values,
28 | selectedFilter1: [],
29 | selectedFilter2: []
30 | },
31 | state: { values: [ 'value 1', 'value 2', 'value 3' ] },
32 | expected: {
33 | 'filtered_values': values
34 | }
35 | },
36 | {
37 | format: 'filtered_samples',
38 | params: {
39 | samples: { values },
40 | filters: [
41 | { dependency: 'selectedFilter1', queryProperty: 'locale' },
42 | { dependency: 'selectedFilter2', queryProperty: 'channel' }
43 | ]
44 | },
45 | dependencies: {
46 | values,
47 | selectedFilter1: ['en'],
48 | selectedFilter2: []
49 | },
50 | state: { values: [ 'value 1', 'value 2', 'value 3' ] },
51 | expected: {
52 | 'filtered_values': _.filter(values, { locale: 'en' })
53 | }
54 | }
55 | ];
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/flags.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default {
4 | format: 'flags',
5 | state: { values: [ 'value 1', 'value 2', 'value 3' ] },
6 | expected: {
7 | 'value 1': false,
8 | 'value 2': false,
9 | 'value 3': false,
10 | 'values-all': [ 'value 1', 'value 2', 'value 3' ],
11 | 'values-selected': [],
12 | }
13 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/formats.d.ts:
--------------------------------------------------------------------------------
1 | export interface IFormatTest {
2 | format: any,
3 | params?: any,
4 | state: any,
5 | dependencies?: any,
6 | expected: any
7 | }
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/index.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 | import bars from './bars';
3 | import filter from './filter';
4 | import flags from './flags';
5 | import retention from './retention';
6 | import scorecard from './scorecard';
7 | import timeline from './timeline';
8 | import timespan from './timespan';
9 | import filtered_samples from './filtered_samples';
10 |
11 | export const formatTests = > {
12 | bars,
13 | filter,
14 | flags,
15 | retention,
16 | scorecard,
17 | timeline,
18 | timespan,
19 | filtered_samples
20 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/retention.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default [
4 | {
5 | format: 'retention',
6 | state: {
7 | values: [
8 | {
9 | totalUnique: 10,
10 | totalUniqueUsersIn24hr: 5,
11 | totalUniqueUsersIn7d: 7,
12 | totalUniqueUsersIn30d: 10,
13 | returning24hr: 3,
14 | returning7d: 3,
15 | returning30d: 6
16 | }
17 | ]
18 | },
19 | expected: {
20 | 'returning': 0,
21 | 'returning24hr': 3,
22 | 'returning30d': 6,
23 | 'returning7d': 3,
24 | 'total': 0,
25 | 'totalUnique': 10,
26 | 'totalUniqueUsersIn24hr': 5,
27 | 'totalUniqueUsersIn30d': 10,
28 | 'totalUniqueUsersIn7d': 7,
29 | 'values': [
30 | { 'retention': '60%', 'returning': 3, 'timespan': '24 hours', 'unique': 5 },
31 | { 'retention': '43%', 'returning': 3, 'timespan': '7 days', 'unique': 7 },
32 | { 'retention': '60%', 'returning': 6, 'timespan': '30 days', 'unique': 10 }
33 | ]
34 | }
35 | },
36 | {
37 | format: 'retention',
38 | dependencies: {
39 | selectedTimespan: 'PT24H'
40 | },
41 | state: {
42 | values: [
43 | {
44 | totalUnique: 10,
45 | totalUniqueUsersIn24hr: 5,
46 | totalUniqueUsersIn7d: 7,
47 | totalUniqueUsersIn30d: 10,
48 | returning24hr: 3,
49 | returning7d: 3,
50 | returning30d: 6
51 | }
52 | ]
53 | },
54 | expected: {
55 | 'returning': 3,
56 | 'returning24hr': 3,
57 | 'returning30d': 6,
58 | 'returning7d': 3,
59 | 'total': 5,
60 | 'totalUnique': 10,
61 | 'totalUniqueUsersIn24hr': 5,
62 | 'totalUniqueUsersIn30d': 10,
63 | 'totalUniqueUsersIn7d': 7,
64 | 'values': [
65 | { 'retention': '60%', 'returning': 3, 'timespan': '24 hours', 'unique': 5 },
66 | { 'retention': '43%', 'returning': 3, 'timespan': '7 days', 'unique': 7 },
67 | { 'retention': '60%', 'returning': 6, 'timespan': '30 days', 'unique': 10 }
68 | ]
69 | }
70 | }
71 | ];
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/scorecard.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default {
4 | format: {
5 | type: 'scorecard',
6 | args: {
7 | thresholds: [{ value: 0, heading: 'Heading', color: '#fff', icon: 'chat' }],
8 | subvalueField: 'other_count',
9 | subvalueThresholds: [{ subvalue: 0, subheading: 'Subheading' }]
10 | }
11 | },
12 | state: {
13 | values: [{ count: 99, other_count: 44 }]
14 | },
15 | expected: {
16 | 'value': '99',
17 | 'heading': 'Heading',
18 | 'color': '#fff',
19 | 'icon': 'chat',
20 | 'subvalue': 44,
21 | 'subheading': 'Subheading'
22 | }
23 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/timeline.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default {
4 | format: {
5 | type: 'timeline',
6 | args: {
7 | timeField: 'timestamp',
8 | lineField: 'line',
9 | valueField: 'value'
10 | }
11 | },
12 | state: {
13 | values: [
14 | { timestamp: '2015-01-01T00:00:01Z', line: 'red', value: 40 },
15 | { timestamp: '2015-01-02T00:00:01Z', line: 'red', value: 50 },
16 | { timestamp: '2015-01-03T00:00:01Z', line: 'red', value: 60 },
17 | { timestamp: '2015-01-01T00:00:01Z', line: 'blue', value: 10 },
18 | { timestamp: '2015-01-02T00:00:01Z', line: 'blue', value: 77 },
19 | { timestamp: '2015-01-03T00:00:01Z', line: 'blue', value: 30 }
20 | ]
21 | },
22 | expected: {
23 | "graphData": [
24 | {"blue": 10, "red": 40, "time": "Thu, 01 Jan 2015 00:00:01 GMT"},
25 | {"blue": 77, "red": 50, "time": "Fri, 02 Jan 2015 00:00:01 GMT"},
26 | {"blue": 30, "red": 60, "time": "Sat, 03 Jan 2015 00:00:01 GMT"}
27 | ],
28 | "lines": ["red", "blue"],
29 | "pieData": [
30 | {"name": "red", "value": 150},
31 | {"name": "blue", "value": 117}
32 | ],
33 | "timeFormat": "date"
34 | }
35 | };
--------------------------------------------------------------------------------
/client/src/tests/data-formats/formats/timespan.ts:
--------------------------------------------------------------------------------
1 | import { IFormatTest } from './formats';
2 |
3 | export default [
4 | {
5 | format: 'timespan',
6 | params: {
7 | values: ["24 hours","1 week","1 month","3 months"]
8 | },
9 | state: {
10 | selectedValue: "1 month"
11 | },
12 | expected: {
13 | "granularity": "1d",
14 | "queryTimespan": "P30D",
15 | "values-all": ["24 hours", "1 week", "1 month", "3 months"],
16 | "values-selected": "1 month"
17 | }
18 | },
19 | {
20 | format: 'timespan',
21 | params: {
22 | values: ["24 hours","1 week","1 month","3 months"]
23 | },
24 | state: {
25 | selectedValue: "24 hours"
26 | },
27 | expected: {
28 | "granularity": "5m",
29 | "queryTimespan": "PT24H",
30 | "values-all": ["24 hours", "1 week", "1 month", "3 months"],
31 | "values-selected": "24 hours"
32 | }
33 | }
34 | ];
--------------------------------------------------------------------------------
/client/src/tests/data-sources/ApplicationInsights.Fail.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { IDataSourceDictionary } from '../../data-sources';
6 | import { setupTests } from '../utils/setup';
7 | import { appInsightsUri } from '../../data-sources/plugins/ApplicationInsights/common';
8 |
9 | import { Toast } from '../../components/Toast';
10 | import { mockRequests } from '../mocks/requests/application-insights';
11 | import dashboardMock from '../mocks/dashboards/application-insights-fail';
12 |
13 | describe('Data Source: Application Insights: Query', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 | let element;
17 |
18 | beforeAll(done => {
19 |
20 | mockRequests();
21 | element = TestUtils.renderIntoDocument();
22 | TestUtils.isElementOfType(element, 'div');
23 | setupTests(dashboardMock, ds => dataSources = ds, () => setTimeout(done, 100));
24 | });
25 |
26 | it ('Query for 30 months with data rows', () => {
27 |
28 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
29 | expect(component[0].state.toasts.length).toBe(1);
30 |
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/ApplicationInsights.Query.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import { setupTests } from '../utils/setup';
3 | import { appInsightsUri } from '../../data-sources/plugins/ApplicationInsights/common';
4 |
5 | import { mockRequests } from '../mocks/requests/application-insights';
6 | import dashboardMock from '../mocks/dashboards/application-insights';
7 |
8 | describe('Data Source: Application Insights: Query', () => {
9 |
10 | let dataSources: IDataSourceDictionary = {};
11 |
12 | beforeAll((done) => {
13 |
14 | mockRequests();
15 | setupTests(dashboardMock, ds => dataSources = ds, done);
16 | });
17 |
18 | it ('Query for 30 months with data rows', () => {
19 |
20 | expect(dataSources).toHaveProperty('events');
21 | expect(dataSources.timespan).toHaveProperty('store');
22 | expect(dataSources.events).toHaveProperty('store');
23 | expect(dataSources.events).toHaveProperty('action');
24 | expect(dataSources.events.store).toHaveProperty('state');
25 |
26 | // Listening to store to catch values arrival from app insights
27 | return new Promise((resolve, reject) => {
28 | var stateUpdate = (state => {
29 | try {
30 | expect(state).toHaveProperty('values');
31 | expect(state.values).toHaveLength(15);
32 | expect(state.values[0]).toHaveProperty('name', 'name: message.received');
33 | expect(state.values[0]).toHaveProperty('rowname', 'RowName: name: message.received');
34 | expect(state.values[0]).toHaveProperty('rowindex', 'RowIndex: 0');
35 | return resolve();
36 | } catch (e) {
37 | return reject(e);
38 | } finally {
39 | dataSources.events.store.unlisten(stateUpdate);
40 | }
41 | });
42 | dataSources.events.store.listen(stateUpdate);
43 | });
44 | });
45 |
46 | it ('Query for 24 hours with 0 rows', () => {
47 | dataSources.timespan.action.updateSelectedValue.defer('24 hours');
48 |
49 | return new Promise((resolve, reject) => {
50 | var stateUpdate = (state => {
51 | try {
52 | expect(state).toHaveProperty('values');
53 | expect(state.values).toHaveLength(0);
54 | return resolve();
55 | } catch (e) {
56 | return reject(e);
57 | } finally {
58 | dataSources.events.store.unlisten(stateUpdate);
59 | }
60 | });
61 | dataSources.events.store.listen(stateUpdate);
62 | });
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/Constants.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import { setupTests } from '../utils/setup';
3 | import dashboardMock from '../mocks/dashboards/constants';
4 |
5 | describe('Data Source: Constant', () => {
6 |
7 | let dataSources: IDataSourceDictionary;
8 |
9 | beforeAll(done => {
10 | setupTests(dashboardMock, ds => dataSources = ds, () => setTimeout(done, 100));
11 | });
12 |
13 | it ('Check basic data == 3 rows', () => {
14 |
15 | expect(dataSources).toHaveProperty('data');;
16 | expect(dataSources.data).toHaveProperty('store');
17 | expect(dataSources.data).toHaveProperty('action');
18 | expect(dataSources.data.store).toHaveProperty('state');
19 | expect(dataSources.data.store.state).toMatchObject({
20 | "selectedValue": "default",
21 | "someJsonValues": [
22 | { "count": 2, "id": 1 },
23 | { "count": 0, "id": 2 },
24 | { "count": 10, "id": 3 }
25 | ],
26 | "values": [],
27 | });
28 | });
29 |
30 | it ('Check updating of store state', () => {
31 |
32 | dataSources.data.action.updateDependencies();
33 | expect(dataSources.data.store.state).toMatchObject({
34 | "selectedValue": "default",
35 | "someJsonValues": [
36 | { "count": 2, "id": 1 },
37 | { "count": 0, "id": 2 },
38 | { "count": 10, "id": 3 },
39 | { "count": 10, "id": 3 }
40 | ],
41 | "values": [],
42 | });
43 |
44 | });
45 |
46 | });
47 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/CosmosDB.Query.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import { setupTests } from '../utils/setup';
3 | import { COSMOS_DB_QUERY_URL } from '../../data-sources/plugins/CosmosDB/Query';
4 |
5 | import { mockRequests } from '../mocks/requests/cosmosdb';
6 | import dashboardMock from '../mocks/dashboards/cosmosdb';
7 |
8 | describe('Data Source: CosmosDB: Query', () => {
9 |
10 | let dataSources: IDataSourceDictionary = {};
11 |
12 | beforeAll(done => {
13 | mockRequests();
14 | setupTests(dashboardMock, ds => dataSources = ds, done);
15 | });
16 |
17 | it('Query for data', () => {
18 |
19 | expect(dataSources).toHaveProperty('events');
20 | expect(dataSources.timespan).toHaveProperty('store');
21 | expect(dataSources.events).toHaveProperty('store');
22 | expect(dataSources.events).toHaveProperty('action');
23 | expect(dataSources.events.store).toHaveProperty('state');
24 |
25 | return new Promise((resolve, reject) => {
26 | var stateUpdate = (state => {
27 | try {
28 | expect(state).toMatchSnapshot('cosmosDBStubResponse')
29 | return resolve();
30 | } catch (e) {
31 | return reject(e);
32 | } finally {
33 | dataSources.events.store.unlisten(stateUpdate);
34 | }
35 | });
36 | dataSources.events.store.listen(stateUpdate);
37 | });
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/DirectLine.Query.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import { setupTests } from '../utils/setup';
3 | import { DIRECT_LINE_URL } from '../../data-sources/plugins/BotFramework/DirectLine';
4 |
5 | import { mockRequests } from '../mocks/requests/directline';
6 | import dashboardMock from '../mocks/dashboards/bot-framework-directline';
7 |
8 | describe('Data Source: DirectLine: Query', () => {
9 |
10 | let dataSources: IDataSourceDictionary = {};
11 |
12 | beforeAll(done => {
13 | mockRequests();
14 | setupTests(dashboardMock, ds => dataSources = ds, done);
15 | });
16 |
17 | it ('Query for data', () => {
18 | expect(dataSources).toHaveProperty('events');
19 | expect(dataSources.timespan).toHaveProperty('store');
20 | expect(dataSources.events).toHaveProperty('store');
21 | expect(dataSources.events).toHaveProperty('action');
22 | expect(dataSources.events.store).toHaveProperty('state');
23 |
24 | return new Promise((resolve, reject) => {
25 | var stateUpdate = (state => {
26 | try {
27 | expect(state).toMatchSnapshot('directLineStubResponse')
28 | return resolve();
29 | } catch (e) {
30 | return reject(e);
31 | } finally {
32 | dataSources.events.store.unlisten(stateUpdate);
33 | }
34 | });
35 | dataSources.events.store.listen(stateUpdate);
36 | });
37 | });
38 | });
39 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/Samples.test.ts:
--------------------------------------------------------------------------------
1 | import { IDataSourceDictionary } from '../../data-sources';
2 | import { setupTests } from '../utils/setup';
3 | import dashboardMock from '../mocks/dashboards/samples';
4 | import { setTimeout } from 'timers';
5 |
6 | describe('Data Source: Samples', () => {
7 |
8 | let dataSources: IDataSourceDictionary;
9 |
10 | beforeAll(done => {
11 | setupTests(dashboardMock, ds => dataSources = ds, () => setTimeout(done, 100));
12 | });
13 |
14 | it ('Check basic data == 3 rows', () => {
15 |
16 | expect(dataSources).toHaveProperty('samples');
17 | expect(dataSources.samples).toHaveProperty('store');
18 | expect(dataSources.samples).toHaveProperty('action');
19 | expect(dataSources.samples.store).toHaveProperty('state', {
20 | values: [
21 | { id: "value1", count: 60 },
22 | { id: "value2", count: 10 },
23 | { id: "value3", count: 30 }
24 | ]
25 | });
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/__snapshots__/CosmosDB.Query.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`cosmosDBStubResponse 1`] = `
4 | Store {
5 | "Documents": "fakedata",
6 | "_count": 0,
7 | "_rid": undefined,
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/client/src/tests/data-sources/__snapshots__/DirectLine.Query.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`directLineStubResponse 1`] = `
4 | Store {
5 | "values": "fakedata",
6 | }
7 | `;
8 |
--------------------------------------------------------------------------------
/client/src/tests/elements/Area.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import AreaComponent from '../../components/generic/Area';
8 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
9 |
10 | import dashboardMock from '../mocks/dashboards/timeline';
11 | import ElementConnector from '../../components/ElementConnector';
12 |
13 | describe('Area', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 | let area;
17 |
18 | beforeAll((done) => {
19 |
20 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
21 | dataSources = DataSourceConnector.getDataSources();
22 |
23 | setTimeout(() => {
24 |
25 | try {
26 | let {id, dependencies, source, actions, title } = dashboardMock.elements[0];
27 | let atts = {id, dependencies, source, actions, title };
28 | let timelineElement = ElementConnector.createGenericElement(
29 | AreaComponent,
30 | 'timeline',
31 | 0,
32 | source,
33 | dependencies,
34 | actions,
35 | null,
36 | title);
37 | area = TestUtils.renderIntoDocument(timelineElement);
38 | TestUtils.isElementOfType(area, 'div');
39 |
40 | // Adding a timeout to make sure Flux cycle is complete
41 | setTimeout(done, 100);
42 |
43 | } catch (e) {
44 | return done(e);
45 | }
46 |
47 | }, 100);
48 | })
49 |
50 | it('Render inside a Card', () => {
51 | let card = TestUtils.scryRenderedComponentsWithType(area, Card);
52 | expect(card.length).toBe(1);
53 | });
54 |
55 | it('Render a timeline entity', () => {
56 | let timelineElement = TestUtils.scryRenderedDOMComponentsWithClass(area, "md-card");
57 | expect(timelineElement.length).toBe(1);
58 | });
59 |
60 | afterAll(() => {
61 | ReactDOM.unmountComponentAtNode(area);
62 | })
63 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/AutoRefreshSelector.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import { AutoRefreshSelector, RefreshActions, RefreshStore } from '../../components/AutoRefreshSelector';
8 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
9 |
10 | import dashboardMock from '../mocks/dashboards/pie';
11 | import ElementConnector from '../../components/ElementConnector';
12 |
13 | describe('AutoRefreshSelector', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 | let refresher;
17 |
18 | beforeAll((done) => {
19 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
20 | dataSources = DataSourceConnector.getDataSources();
21 |
22 | setTimeout(() => {
23 |
24 | try {
25 | let {id, dependencies, source, actions, props, title, subtitle } = dashboardMock.elements[0];
26 | let atts = {id, dependencies, source, actions, props, title, subtitle };
27 | let autoRefresherElement = ElementConnector.createGenericElement(
28 | AutoRefreshSelector,
29 | 'autorefresher',
30 | 0,
31 | source,
32 | dependencies,
33 | actions,
34 | props,
35 | title,
36 | subtitle);
37 | refresher = TestUtils.renderIntoDocument(autoRefresherElement);
38 | TestUtils.isElementOfType(refresher, 'div');
39 |
40 | // Adding a timeout to make sure Flux cycle is complete
41 | setTimeout(done, 100);
42 |
43 | } catch (e) {
44 | return done(e);
45 | }
46 |
47 | }, 100);
48 | })
49 |
50 | it('Update interval to "None"', () => {
51 | RefreshStore.listen((state) => {
52 | expect(state.refreshInterval).toBe(-1);
53 | });
54 | RefreshActions.setRefreshTimer(
55 | -1,
56 | // empty call back. we don't care which logic is scheduled...
57 | () => {
58 | });
59 | });
60 |
61 | it('Update interval to a positive value', () => {
62 | RefreshStore.listen((state) => {
63 | expect(state.refreshInterval).toBe(10000);
64 | });
65 | RefreshActions.setRefreshTimer(
66 | 10000,
67 | // empty call back. we don't care which logic is scheduled...
68 | () => {
69 | });
70 | });
71 |
72 | afterAll(() => {
73 | ReactDOM.unmountComponentAtNode(refresher);
74 | })
75 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/CardSettings.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import { Settings, SettingsActions, SettingsStore } from '../../components/Card/Settings';
8 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
9 |
10 | import dashboardMock from '../mocks/dashboards/dashboard';
11 | import ElementConnector from '../../components/ElementConnector';
12 |
13 | describe('Card settings store', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 |
17 | let settings;
18 |
19 | beforeAll((done) => {
20 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
21 | dataSources = DataSourceConnector.getDataSources();
22 |
23 | setTimeout(() => {
24 |
25 | try {
26 | let {id, dependencies, source, actions, props, title, subtitle } = dashboardMock.elements[0];
27 | let atts = {id, dependencies, source, actions, props, title, subtitle };
28 | let autoRefresherElement = ElementConnector.createGenericElement(
29 | Settings,
30 | 'settings',
31 | 0,
32 | source,
33 | dependencies,
34 | actions,
35 | props,
36 | title,
37 | subtitle);
38 | settings = TestUtils.renderIntoDocument(autoRefresherElement);
39 | TestUtils.isElementOfType(settings, 'div');
40 |
41 | // Adding a timeout to make sure Flux cycle is complete
42 | setTimeout(done, 100);
43 |
44 | } catch (e) {
45 | return done(e);
46 | }
47 |
48 | }, 100);
49 | })
50 |
51 | it('rendering the Card\'s setting component', () => {
52 | });
53 |
54 | afterAll(() => {
55 | ReactDOM.unmountComponentAtNode(settings);
56 | })
57 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/CardSettingsStore.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import { Settings, SettingsActions, SettingsStore } from '../../components/Card/Settings';
8 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
9 |
10 | import dashboardMock from '../mocks/dashboards/dashboard';
11 | import ElementConnector from '../../components/ElementConnector';
12 |
13 | describe('Card settings store', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 |
17 | it('Update interval to "None"', () => {
18 | (SettingsActions.getExportData as any).defer(dashboardMock);
19 | });
20 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/Dialog.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Dialog, DialogsActions, loadDialogsFromDashboard } from '../../components/generic/Dialogs';
6 | import MDDialog from 'react-md/lib/Dialogs';
7 |
8 | import emptyDashboard from '../mocks/dashboards/dashboard';
9 | import dashboardWithDialog from '../mocks/dashboards/dialogs';
10 | import dialogData from '../mocks/dialog';
11 |
12 | describe('Dialog', () => {
13 |
14 | let dialog;
15 |
16 | it('Load empty dialogs', () => {
17 | let dialogs = loadDialogsFromDashboard(emptyDashboard);
18 | expect(dialogs).toHaveLength(0);
19 | });
20 |
21 | it('Dynamic loading of 1 dialog', () => {
22 | let dialogs = loadDialogsFromDashboard(dashboardWithDialog);
23 | expect(dialogs).toHaveLength(1);
24 | });
25 |
26 | it('First render without content', () => {
27 | dialog = TestUtils.renderIntoDocument();
28 | let elements = TestUtils.scryRenderedComponentsWithType(dialog, MDDialog);
29 | expect(elements.length).toBe(0);
30 | });
31 |
32 | it('Opening a dialog', function () {
33 | DialogsActions.openDialog(dialogData.id, { title: 'Title', intent: 'Intent', queryspan: '30D' });
34 | let elements = TestUtils.scryRenderedComponentsWithType(dialog, MDDialog);
35 | expect(elements.length).toBe(1);
36 | });
37 |
38 | it('Closing a dialog', function () {
39 | DialogsActions.closeDialog();
40 | let elements = TestUtils.scryRenderedComponentsWithType(dialog, MDDialog);
41 | expect(elements.length).toBe(0);
42 | });
43 |
44 | afterAll(() => {
45 | ReactDOM.unmountComponentAtNode(dialog);
46 | })
47 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/IconPicker.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import Snackbar from 'react-md/lib/Snackbars';
6 | import IconPicker from '../../components/Home/IconPicker';
7 |
8 | import icons from '../../constants/icons';
9 |
10 | const DEFAULT_ICON = 'dashboard';
11 | const DEFAULT_ICON2 = 'view_quilt';
12 |
13 | describe('IconPicker', () => {
14 |
15 | let element, element2;
16 |
17 | beforeAll(() => {
18 | element = TestUtils.renderIntoDocument();
19 | TestUtils.isElementOfType(element, 'div');
20 |
21 | element2 = TestUtils.renderIntoDocument();
22 | TestUtils.isElementOfType(element2, 'div');
23 | });
24 |
25 | it('First render component', () => {
26 | let component = TestUtils.scryRenderedComponentsWithType(element, IconPicker);
27 | expect(component.length).toBe(1);
28 | });
29 |
30 | it('Should populate with icons constant', () => {
31 | let component = TestUtils.scryRenderedComponentsWithType(element, IconPicker);
32 | expect(IconPicker.listItems.length).toEqual(icons.length);
33 | });
34 |
35 | it('Default icon should be ' + DEFAULT_ICON, () => {
36 | let component = TestUtils.scryRenderedComponentsWithType(element, IconPicker);
37 | expect(component[0].getIcon()).toBe(DEFAULT_ICON);
38 | });
39 |
40 | it('Default icon should be ' + DEFAULT_ICON2, () => {
41 | let component = TestUtils.scryRenderedComponentsWithType(element2, IconPicker);
42 | expect(component[0].getIcon()).toBe(DEFAULT_ICON2);
43 | });
44 |
45 | afterAll(() => {
46 | ReactDOM.unmountComponentAtNode(element);
47 | });
48 |
49 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/Spinner.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import CircularProgress from 'react-md/lib/Progress/CircularProgress';
6 | import { Spinner, SpinnerActions } from '../../components/Spinner';
7 |
8 | describe('Spinner', () => {
9 |
10 | let spinner;
11 |
12 | beforeAll(() => {
13 | spinner = TestUtils.renderIntoDocument();
14 | TestUtils.isElementOfType(spinner, 'div');
15 | })
16 |
17 | it('First render without content', () => {
18 | let progress = TestUtils.scryRenderedComponentsWithType(spinner, CircularProgress);
19 | expect(progress.length).toBe(0);
20 | });
21 |
22 | it ('Start page loading', () => {
23 | SpinnerActions.startPageLoading(null);
24 |
25 | let progress = TestUtils.scryRenderedComponentsWithType(spinner, CircularProgress);
26 | expect(progress.length).toBe(1);
27 | });
28 |
29 | it ('Stop page loading', () => {
30 | SpinnerActions.endPageLoading(null);
31 |
32 | let progress = TestUtils.scryRenderedComponentsWithType(spinner, CircularProgress);
33 | expect(progress.length).toBe(0);
34 | });
35 |
36 | it ('Start request loading', () => {
37 | SpinnerActions.startRequestLoading(null);
38 |
39 | let progress = TestUtils.scryRenderedComponentsWithType(spinner, CircularProgress);
40 | expect(progress.length).toBe(1);
41 | });
42 |
43 | it ('Start request loading', () => {
44 | SpinnerActions.endRequestLoading(null);
45 |
46 | let progress = TestUtils.scryRenderedComponentsWithType(spinner, CircularProgress);
47 | expect(progress.length).toBe(0);
48 | });
49 |
50 | afterAll(() => {
51 | ReactDOM.unmountComponentAtNode(spinner);
52 | })
53 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/Table.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 | import { DataTable, TableRow } from 'react-md/lib/DataTables';
7 | import { Spinner, SpinnerActions } from '../../components/Spinner';
8 | import Table from '../../components/generic/Table';
9 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
10 |
11 | //import dataSourceMock from '../mocks/dataSource';
12 | import dashboardMock from '../mocks/dashboards/table';
13 |
14 | describe('Table', () => {
15 |
16 | let dataSources: IDataSourceDictionary = {};
17 | let table;
18 |
19 | beforeAll((done) => {
20 |
21 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
22 | dataSources = DataSourceConnector.getDataSources();
23 |
24 | let {id, dependencies, actions, props, title, subtitle } = dashboardMock.elements[0];
25 | let atts = {id, dependencies, actions, props, title, subtitle };
26 | table = TestUtils.renderIntoDocument();
27 | TestUtils.isElementOfType(table, 'div');
28 |
29 | setTimeout(done, 10);
30 | })
31 |
32 | it('Render inside a Card', () => {
33 | dataSources['samples'].action.updateDependencies({
34 | 'table-values': dataSources['samples'].store.state['values']
35 | });
36 | let card = TestUtils.scryRenderedComponentsWithType(table, Card);
37 | expect(card.length).toBe(1);
38 | });
39 |
40 | it('Render a Data Table entity', () => {
41 | let progress = TestUtils.scryRenderedComponentsWithType(table, DataTable);
42 | expect(progress.length).toBe(1);
43 | });
44 |
45 | it('Rows == 4', () => {
46 | let rows = TestUtils.scryRenderedComponentsWithType(table, TableRow);
47 | expect(rows.length).toBe(4);
48 | });
49 |
50 | it('Rows == 0', () => {
51 | dataSources['samples'].action.updateDependencies({
52 | 'table-values': []
53 | });
54 | let rows = TestUtils.scryRenderedComponentsWithType(table, TableRow);
55 | expect(rows.length).toBe(1);
56 | });
57 |
58 | afterAll(() => {
59 | ReactDOM.unmountComponentAtNode(table);
60 | })
61 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/Timeline.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import TimelineComponent from '../../components/generic/Timeline';
8 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
9 |
10 | import dashboardMock from '../mocks/dashboards/timeline';
11 | import ElementConnector from '../../components/ElementConnector';
12 |
13 | describe('Timeline', () => {
14 |
15 | let dataSources: IDataSourceDictionary = {};
16 | let timeline;
17 |
18 | beforeAll((done) => {
19 |
20 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
21 | dataSources = DataSourceConnector.getDataSources();
22 |
23 | setTimeout(() => {
24 |
25 | try {
26 | let {id, dependencies, source, actions, title } = dashboardMock.elements[0];
27 | let atts = {id, dependencies, source, actions, title };
28 | let timelineElement = ElementConnector.createGenericElement(
29 | TimelineComponent,
30 | 'timeline',
31 | 0,
32 | source,
33 | dependencies,
34 | actions,
35 | null,
36 | title);
37 | timeline = TestUtils.renderIntoDocument(timelineElement);
38 | TestUtils.isElementOfType(timeline, 'div');
39 |
40 | // Adding a timeout to make sure Flux cycle is complete
41 | setTimeout(done, 100);
42 |
43 | } catch (e) {
44 | return done(e);
45 | }
46 |
47 | }, 100);
48 | })
49 |
50 | it('Render inside a Card', () => {
51 | let card = TestUtils.scryRenderedComponentsWithType(timeline, Card);
52 | expect(card.length).toBe(1);
53 | });
54 |
55 | it('Render a timeline entity', () => {
56 | let timelineElement = TestUtils.scryRenderedDOMComponentsWithClass(timeline, "md-card");
57 | expect(timelineElement.length).toBe(1);
58 | });
59 |
60 | afterAll(() => {
61 | ReactDOM.unmountComponentAtNode(timeline);
62 | })
63 | });
--------------------------------------------------------------------------------
/client/src/tests/elements/Toast.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import Snackbar from 'react-md/lib/Snackbars';
6 | import { Toast, IToast, ToastActions } from '../../components/Toast';
7 |
8 | const TEST_TOAST_1: IToast = {
9 | text: "first test toast"
10 | };
11 | const TEST_TOAST_2: IToast = {
12 | text: "second test toast"
13 | };
14 |
15 | describe('Toast', () => {
16 |
17 | let element;
18 |
19 | beforeAll(() => {
20 | element = TestUtils.renderIntoDocument();
21 | TestUtils.isElementOfType(element, 'div');
22 | });
23 |
24 | it('First render component', () => {
25 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
26 | expect(component.length).toBe(1);
27 | });
28 |
29 | it('First render without toasts', () => {
30 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
31 | expect(component[0].state.toasts.length).toBe(0);
32 | });
33 |
34 | it('Render after adding toast', () => {
35 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
36 | ToastActions.addToast(TEST_TOAST_1);
37 | expect(component[0].state.toasts.length).toBe(1);
38 | });
39 |
40 | it('Render only once after adding duplicate toast', () => {
41 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
42 | ToastActions.addToast(TEST_TOAST_1);
43 | ToastActions.addToast(TEST_TOAST_1);
44 | expect(component[0].state.toasts.length).toBe(1);
45 | });
46 |
47 | it('Render add subsequent toasts to queue', () => {
48 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
49 | ToastActions.addToast(TEST_TOAST_1);
50 | ToastActions.addToast(TEST_TOAST_2);
51 | expect(component[0].state.toasts.length && component[0].state.queued.length).toBe(1);
52 | });
53 |
54 | afterAll(() => {
55 | ReactDOM.unmountComponentAtNode(element);
56 | });
57 |
58 | });
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/application-insights-fail.ts:
--------------------------------------------------------------------------------
1 | import timespan from './timespan';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(timespan);
5 | dashboard.config.connections["application-insights"] = { appId: 'id_fail', apiKey: '1' };
6 | dashboard.dataSources.push({
7 | id: 'events',
8 | type: 'ApplicationInsights/Query',
9 | dependencies: { timespan: 'timespan', queryTimespan: 'timespan:queryTimespan' },
10 | params: {
11 | query: `customEvents`,
12 | mappings: {
13 | name: (val, row, idx) => `name: ${val}`,
14 | rowname: (val, row, idx) => `RowName: ${row.name}`,
15 | rowindex: (val, row, idx) => `RowIndex: ${idx}`
16 | }
17 | }
18 | });
19 |
20 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/application-insights-forked.ts:
--------------------------------------------------------------------------------
1 | import timespan from './timespan';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(timespan);
5 | dashboard.config.connections["application-insights"] = { appId: '1', apiKey: '1' };
6 | dashboard.dataSources.push({
7 | id: "events",
8 | type: "ApplicationInsights/Query",
9 | dependencies: { timespan: "timespan",queryTimespan: "timespan:queryTimespan",granularity: "timespan:granularity" },
10 | params: {
11 | table: "customEvents",
12 | queries: {
13 | array1: {
14 | query: () => `customEvents`,
15 | calculated: (filterChannels, dependencies, prevState) => {
16 | return { "array1-calc": filterChannels };
17 | }
18 | },
19 | array2: {
20 | query: () => `customEvents2`
21 | }
22 | }
23 | }
24 | });
25 |
26 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/application-insights.ts:
--------------------------------------------------------------------------------
1 | import timespan from './timespan';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(timespan);
5 | dashboard.config.connections["application-insights"] = { appId: '1', apiKey: '1' };
6 | dashboard.dataSources.push({
7 | id: 'events',
8 | type: 'ApplicationInsights/Query',
9 | dependencies: { timespan: 'timespan', queryTimespan: 'timespan:queryTimespan' },
10 | params: {
11 | query: `customEvents`,
12 | mappings: {
13 | name: (val, row, idx) => `name: ${val}`,
14 | rowname: (val, row, idx) => `RowName: ${row.name}`,
15 | rowindex: (val, row, idx) => `RowIndex: ${idx}`
16 | }
17 | }
18 | });
19 |
20 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/area.ts:
--------------------------------------------------------------------------------
1 | import samples from './rich-samples';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(samples);
5 | dashboard.elements.push({
6 | id: "area",
7 | type: "Area",
8 | title: "Area title",
9 | size: { w: 3,h: 8 },
10 | source: "data",
11 | props: { showLegend: true,compact: true,entityType: "Messages" }
12 | });
13 |
14 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/bot-framework-directline.ts:
--------------------------------------------------------------------------------
1 | import timespan from './timespan';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(timespan);
5 | dashboard.config.connections["bot-framework"] = { directLine: 'AbCdEf123456' };
6 | dashboard.dataSources.push({
7 | id: 'events',
8 | type: 'BotFramework/DirectLine',
9 | dependencies: { timespan: 'timespan', queryTimespan: 'timespan:queryTimespan' },
10 | params: {
11 | }
12 | });
13 |
14 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/constants.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from "./utils";
2 |
3 | let someJsonValues = [
4 | { id: 1, count: 2 },
5 | { id: 2, count: 0 }
6 | ];
7 |
8 | let dashboard = createDashboard();
9 | dashboard.dataSources.push({
10 | id: 'data',
11 | type: 'Constant',
12 | params: {
13 | values: [],
14 | selectedValue: 'default'
15 | },
16 | calculated: (state, dependencies) => {
17 |
18 | someJsonValues.push({ id: 3, count: 10 });
19 | return { someJsonValues };
20 | }
21 | });
22 |
23 | export let dashboard2 = createDashboard();
24 | dashboard2.dataSources.push(
25 | {
26 | id: "timespan",
27 | type: "Constant",
28 | params: { values: ["24 hours","1 week","1 month","3 months"],selectedValue: "1 month" },
29 | calculated: (state, dependencies) => {
30 | var queryTimespan =
31 | state.selectedValue === '24 hours' ? 'PT24H' :
32 | state.selectedValue === '1 week' ? 'P7D' :
33 | state.selectedValue === '1 month' ? 'P30D' :
34 | 'P90D';
35 | var granularity =
36 | state.selectedValue === '24 hours' ? '5m' :
37 | state.selectedValue === '1 week' ? '1d' : '1d';
38 |
39 | return { queryTimespan, granularity };
40 | }
41 | }
42 | );
43 |
44 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/cosmosdb.ts:
--------------------------------------------------------------------------------
1 | import timespan from './timespan';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(timespan);
5 | dashboard.config.connections["cosmos-db"] = { host: 'http://localhost:3000', key: 'someKey' };
6 | dashboard.dataSources.push({
7 | id: "events",
8 | type: "CosmosDB/Query",
9 | dependencies: { timespan: "timespan", queryTimespan: "timespan:queryTimespan" },
10 | params: {
11 | databaseId: "admin",
12 | collectionId: "conversations",
13 | query: () => `SELECT * FROM conversations WHERE (conversations.state = 0)`,
14 | parameters: []
15 | },
16 | calculated: (result) => {
17 | return result;
18 | }
19 | });
20 |
21 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/dashboard.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | id: 'id',
3 | url: 'url',
4 | icon: 'icon',
5 | name: 'name',
6 | config: {
7 | connections: {},
8 | layout: {
9 | cols: {lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
10 | breakpoints: {lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}
11 | }
12 | },
13 | dataSources: [],
14 | filters: [],
15 | elements: [],
16 | dialogs: []
17 | };
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/dialogs.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from './utils';
2 |
3 | let dashboard = createDashboard();
4 | dashboard.dialogs.push({
5 | id: "conversations",
6 | width: '60%',
7 | params: [ 'title', 'intent', 'queryspan' ],
8 | dataSources: [
9 | {
10 | id: 'timespan',
11 | type: 'Constant',
12 | params: {
13 | values: [],
14 | selectedValue: 'default'
15 | },
16 | calculated: (state, dependencies) => {
17 |
18 | var someJsonValues = [
19 | {
20 | id: 1,
21 | count: 2,
22 | },
23 | {
24 | id: 2,
25 | count: 0,
26 | },
27 | {
28 | id: 3,
29 | count: 10,
30 | }
31 | ];
32 |
33 | return { someJsonValues };
34 | }
35 | }
36 | ],
37 | elements: [
38 | {
39 | id: 'conversations-list',
40 | type: 'Table',
41 | title: 'Conversations',
42 | size: { w: 12, h: 16},
43 | dependencies: { values: 'timespan:someJsonValues' },
44 | props: {
45 | cols: [{
46 | header: 'Conversation Id',
47 | field: 'id'
48 | }, {
49 | header: 'Count',
50 | field: 'count'
51 | }, {
52 | type: 'button',
53 | value: 'chat',
54 | onClick: 'openMessagesDialog'
55 | }]
56 | }
57 | }
58 | ]
59 | });
60 |
61 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/pie.ts:
--------------------------------------------------------------------------------
1 | import samples from './rich-samples';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(samples);
5 | dashboard.elements.push({
6 | id: "pie",
7 | type: "PieData",
8 | title: "Pie Data",
9 | subtitle: "Pie Data sub-title",
10 | size: { w: 3,h: 8 },
11 | source: "data",
12 | props: { showLegend: true,compact: true,entityType: "Messages" }
13 | });
14 |
15 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/rich-samples.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from './utils';
2 |
3 | let dashboard = createDashboard();
4 | dashboard.dataSources.push({
5 | id: 'data',
6 | type: 'Constant',
7 | params: {
8 | selectedValue: 'default',
9 | values: [
10 | { timestamp: '2015-01-01 00:00:01', line: 'red', value: 40 },
11 | { timestamp: '2015-01-02 00:00:01', line: 'red', value: 50 },
12 | { timestamp: '2015-01-03 00:00:01', line: 'red', value: 60 },
13 | { timestamp: '2015-01-01 00:00:01', line: 'blue', value: 10 },
14 | { timestamp: '2015-01-02 00:00:01', line: 'blue', value: 77 },
15 | { timestamp: '2015-01-03 00:00:01', line: 'blue', value: 30 }
16 | ]
17 | },
18 | format: {
19 | type: 'timeline',
20 | args: {
21 | timeField: 'timestamp',
22 | lineField: 'line',
23 | valueField: 'value'
24 | }
25 | }
26 | });
27 |
28 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/samples.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from "./utils";
2 |
3 | let dashboard = createDashboard();
4 | dashboard.dataSources.push({
5 | id: "samples",
6 | type: "Sample",
7 | params: {
8 | samples: {
9 | values: [
10 | { id: "value1", count: 60 },
11 | { id: "value2", count: 10 },
12 | { id: "value3", count: 30 }
13 | ]
14 | }
15 | }
16 | });
17 |
18 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/scorecard.ts:
--------------------------------------------------------------------------------
1 | import samples from './rich-samples';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard();
5 | dashboard.dataSources.push({
6 | id: "samples",
7 | type: "Sample",
8 | params: {
9 | samples: {
10 | scorecard_data_value: 3000000
11 | }
12 | }
13 | });
14 | dashboard.elements.push({
15 | id: "scorecard",
16 | type: "Scorecard",
17 | title: "Value",
18 | size: { w: 3,h: 8 },
19 | source: "samples",
20 | dependencies: {
21 | value: "samples:scorecard_data_value",
22 | color: "::#2196F3",
23 | icon: "::av_timer"
24 | }
25 | });
26 |
27 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/splitpanel.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from "./utils";
2 |
3 | let dashboard = createDashboard();
4 | dashboard.dataSources.push(
5 | {
6 | id: "samples",
7 | type: "Sample",
8 | params: {
9 | samples: { }
10 | }
11 | }
12 | );
13 | dashboard.elements.push(
14 | {
15 | id: "splitpanel",
16 | type: "SplitPanel",
17 | title: "Values",
18 | size: { w: 12,h: 16 },
19 | dependencies: { groups: "samples:groups", values: "samples:values" },
20 | props: {
21 | group: { field: "title",secondaryField: "subtitle", countField: "count" },
22 | cols: [
23 | { header: "Id",field: "id",secondaryHeader: "Repeat Id",secondaryField: "id" },
24 | { header: "Count",field: "count" },
25 | ]
26 | },
27 | actions: {
28 | select: {
29 | action: "samples:updateDependencies",
30 | params: { title: "args:title",type: "args:title" }
31 | }
32 | }
33 | }
34 | );
35 |
36 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/table.ts:
--------------------------------------------------------------------------------
1 | import samples from './samples';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(samples);
5 | dashboard.elements.push({
6 | id: 'table',
7 | type: 'Table',
8 | size: { w: 1, h: 1 },
9 | title: 'Table',
10 | subtitle: 'Table',
11 | dependencies: { values: 'samples:table-values' },
12 | props: {
13 | cols: [
14 | {
15 | header: 'Conversation Id',
16 | field: 'id'
17 | },
18 | {
19 | header: 'Count',
20 | field: 'count'
21 | }
22 | ]
23 | }
24 | });
25 |
26 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/timeline.ts:
--------------------------------------------------------------------------------
1 | import samples from './rich-samples';
2 | import { createDashboard } from "./utils";
3 |
4 | let dashboard = createDashboard(samples);
5 | dashboard.elements.push({
6 | id: "timeline",
7 | type: "Timeline",
8 | title: "Timeline title",
9 | size: { w: 3,h: 8 },
10 | source: "data",
11 | props: { showLegend: true,compact: true,entityType: "Messages" }
12 | });
13 |
14 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/timespan.ts:
--------------------------------------------------------------------------------
1 | import { createDashboard } from './utils';
2 |
3 | let dashboard = createDashboard();
4 | dashboard.dataSources.push({
5 | id: 'timespan',
6 | type: 'Constant',
7 | params: {
8 | values: ['24 hours', '1 week', '1 month'],
9 | selectedValue: '1 month'
10 | },
11 | calculated: (state, dependencies) => {
12 | var queryTimespan = state.selectedValue === '24 hours' ? 'PT24H' : state.selectedValue === '1 week' ? 'P7D' : 'P30D';
13 | var granularity = state.selectedValue === '24 hours' ? '5m' : state.selectedValue === '1 week' ? '1d' : '1d';
14 |
15 | return { queryTimespan, granularity };
16 | }
17 | });
18 |
19 | export default dashboard;
--------------------------------------------------------------------------------
/client/src/tests/mocks/dashboards/utils.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import dashboard from './dashboard';
3 |
4 | let createDashboard = (fromDashboard?: IDashboardConfig) : IDashboardConfig => _.cloneDeep(fromDashboard || dashboard);
5 |
6 | export {
7 | createDashboard
8 | };
--------------------------------------------------------------------------------
/client/src/tests/mocks/dialog.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | id: "conversations",
3 | width: '60%',
4 | params: [ 'title', 'intent', 'queryspan' ],
5 | dataSources: [
6 | {
7 | id: 'timespan',
8 | type: 'Constant',
9 | params: {
10 | values: [],
11 | selectedValue: 'default'
12 | },
13 | calculated: (state, dependencies) => {
14 | return {
15 | someJsonValues: [
16 | { id: 1, count: 2 },
17 | { id: 2, count: 0 },
18 | { id: 3, count: 10 }
19 | ]
20 | };
21 | }
22 | }
23 | ],
24 | elements: [
25 | {
26 | id: 'conversations-list',
27 | type: 'Table',
28 | title: 'Conversations',
29 | size: { w: 12, h: 16},
30 | dependencies: { values: 'timespan:someJsonValues' },
31 | props: {
32 | cols: [{
33 | header: 'Conversation Id',
34 | field: 'id'
35 | }, {
36 | header: 'Count',
37 | field: 'count'
38 | }, {
39 | type: 'button',
40 | value: 'chat',
41 | onClick: 'openMessagesDialog'
42 | }]
43 | }
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------
/client/src/tests/mocks/requests/account.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 |
3 | function mockRequests() {
4 |
5 | nock('http://localhost')
6 | .get('/auth/account')
7 | .once()
8 | .reply(404, {
9 | error: 'Some exception'
10 | });
11 |
12 | nock('http://localhost')
13 | .get('/auth/account')
14 | .twice()
15 | .reply(200, {
16 | account: 'account'
17 | });
18 | }
19 | export {
20 | mockRequests
21 | };
--------------------------------------------------------------------------------
/client/src/tests/mocks/requests/application-insights/index.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 | import dashboardMock from '../../dashboards/application-insights';
3 | import query24HResponseMock from './query.24h.mock';
4 | import query30DResponseMock from './query.30d.mock';
5 | import { appInsightsUri } from '../../../../data-sources/plugins/ApplicationInsights/common';
6 | import { REPLACE } from 'history/lib/actions';
7 |
8 | const { appId, apiKey } = dashboardMock.config.connections['application-insights'];
9 |
10 | /**
11 | * Mocking application insights requets
12 | */
13 | function mockRequests() {
14 |
15 | nock('http://localhost')
16 | .post('/applicationInsights/query', {
17 | query: /(.*?)/g,
18 | appId: appId,
19 | dashboardId: dashboardMock.id,
20 | queryTimespan: 'PT24H'
21 | })
22 | .delay(10)
23 | .reply(200, query24HResponseMock)
24 | .post('/applicationInsights/query', {
25 | query: /(.*?)/g,
26 | appId: appId,
27 | dashboardId: dashboardMock.id,
28 | queryTimespan: 'P30D'
29 | })
30 | .delay(10)
31 | .reply(200, query30DResponseMock)
32 | .post('/applicationInsights/query', {
33 | query: /(.*?)/g,
34 | appId: 'id_fail',
35 | dashboardId: dashboardMock.id,
36 | queryTimespan: 'P30D'
37 | })
38 | .delay(10)
39 | .reply(404, 'Some error');
40 | }
41 |
42 | export {
43 | mockRequests
44 | };
--------------------------------------------------------------------------------
/client/src/tests/mocks/requests/configuration.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 | import dashboard from '../dashboards/dashboard';
3 |
4 | function mockRequests() {
5 |
6 | nock('http://localhost')
7 | .get('/api/dashboards')
8 | .reply(200, `
9 | (function (window) {
10 | var dashboardTemplate = (function () {
11 | return ${JSON.stringify(dashboard)};
12 | })();
13 | window.dashboardTemplates = window.dashboardTemplates || [];
14 | window.dashboardTemplates.push(dashboardTemplate);
15 | })(window);
16 | (function (window) {
17 | var dashboard = (function () {
18 | return ${JSON.stringify(dashboard)};
19 | })();
20 | window.dashboardDefinitions = window.dashboardDefinitions || [];
21 | window.dashboardDefinitions.push(dashboard);
22 | })(window);
23 | `);
24 |
25 | nock('http://localhost')
26 | .get('/api/dashboards/id_fail')
27 | .reply(404, `Some error`);
28 |
29 | nock('http://localhost')
30 | .get('/api/dashboards/id_success')
31 | .reply(200, `
32 | (function (window) {
33 | var dashboard = (function () {
34 | return ${JSON.stringify(dashboard)};
35 | })();
36 | window.dashboard = dashboard || null;
37 | })(window);
38 | `);
39 |
40 | nock('http://localhost')
41 | .get('/api/templates/id_fail')
42 | .reply(404, `Some error`);
43 |
44 | nock('http://localhost')
45 | .get('/api/templates/id_success')
46 | .reply(200, `
47 | (function (window) {
48 | var template = (function () {
49 | return ${JSON.stringify(dashboard)};
50 | })();
51 | window.template = template || null;
52 | })(window);
53 | `);
54 |
55 | nock('http://localhost')
56 | .put('/api/dashboards/id')
57 | .reply(200, { success: true });
58 |
59 | nock('http://localhost')
60 | .put('/api/templates/id')
61 | .reply(200, { script: true });
62 | }
63 | export {
64 | mockRequests
65 | };
--------------------------------------------------------------------------------
/client/src/tests/mocks/requests/cosmosdb/index.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 | import dashboardMock from '../../dashboards/cosmosdb';
3 | import { COSMOS_DB_QUERY_URL } from '../../../../data-sources/plugins/CosmosDB/Query';
4 |
5 | const { host, key } = dashboardMock.config.connections['cosmos-db'];
6 |
7 | /**
8 | * Mocking application insights requets
9 | */
10 | function mockRequests() {
11 | nock('http://localhost', {
12 | })
13 | .post('/cosmosdb/query')
14 | .delay(100)
15 | .reply(200, { Documents: 'fakedata' })
16 | }
17 | export {
18 | mockRequests
19 | };
20 |
--------------------------------------------------------------------------------
/client/src/tests/mocks/requests/directline/index.ts:
--------------------------------------------------------------------------------
1 | import * as nock from 'nock';
2 | import dashboardMock from '../../dashboards/bot-framework-directline';
3 | import { DIRECT_LINE_URL } from '../../../../data-sources/plugins/BotFramework/DirectLine';
4 |
5 | const { directLine } = dashboardMock.config.connections['bot-framework'];
6 |
7 | /**
8 | * Mocking application insights requets
9 | */
10 | function mockRequests() {
11 | let bearer = 'Bearer ' + directLine;
12 | nock('https://directline.botframework.com/', {
13 | reqheaders: {
14 | "Authorization": bearer
15 | }
16 | })
17 | .post('/v3/directline/conversations')
18 | .delay(100)
19 | // DirectLine doesn't support timespan based query, so the result is a stub JSON
20 | .reply(200, { values: 'fakedata' })
21 | }
22 | export {
23 | mockRequests
24 | };
25 |
--------------------------------------------------------------------------------
/client/src/tests/pages/Dashboard.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import Dashboard from '../../components/Dashboard';
8 |
9 | import DashboardPage from '../../pages/Dashboard';
10 |
11 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
12 |
13 | import dashboardMock from '../mocks/dashboards/dashboard';
14 | import ElementConnector from '../../components/ElementConnector';
15 |
16 | describe('HomePage', () => {
17 |
18 | let dataSources: IDataSourceDictionary = {};
19 | let dashboard;
20 |
21 | it('Check Dashboard Page is loading', () => {
22 | var a = TestUtils.renderIntoDocument();
23 |
24 | // This test is just to make sure the component is able to render
25 | });
26 |
27 | it('Check Dashboard is loading', () => {
28 | var a = TestUtils.renderIntoDocument();
29 |
30 | // This test is just to make sure the component is able to render
31 | });
32 |
33 | afterAll(() => {
34 | ReactDOM.unmountComponentAtNode(dashboard);
35 | })
36 | });
--------------------------------------------------------------------------------
/client/src/tests/pages/Home.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import HomeComponent from '../../components/Home';
6 | import dashboardMock from '../mocks/dashboards/pie';
7 | import ElementConnector from '../../components/ElementConnector';
8 |
9 | describe('HomePage', () => {
10 |
11 | let homepage;
12 |
13 | beforeAll((done) => {
14 |
15 | setTimeout(() => {
16 |
17 | try {
18 | let {id, dependencies, source, actions, props, title, subtitle } = dashboardMock.elements[0];
19 | let atts = {id, dependencies, source, actions, props, title, subtitle };
20 | let homeElement = ElementConnector.createGenericElement(
21 | HomeComponent,
22 | 'home',
23 | 0,
24 | source,
25 | dependencies,
26 | actions,
27 | props,
28 | title,
29 | subtitle);
30 | homepage = TestUtils.renderIntoDocument(homeElement);
31 | TestUtils.isElementOfType(homepage, 'div');
32 |
33 | // Adding a timeout to make sure Flux cycle is complete
34 | setTimeout(done, 100);
35 |
36 | } catch (e) {
37 | return done(e);
38 | }
39 |
40 | }, 100);
41 | })
42 |
43 | it('Check Home page is loading', (done) => {
44 | // This test is just to make sure the component is able to render
45 | setTimeout(() => done(), 500);
46 | });
47 |
48 | afterAll(() => {
49 | ReactDOM.unmountComponentAtNode(homepage);
50 | })
51 | });
--------------------------------------------------------------------------------
/client/src/tests/pages/HomePage.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import Home from '../../pages/Home'
6 |
7 | describe('Home', () => {
8 | let setup;
9 | let setupComponent;
10 |
11 | it('Home is loading', (done) => {
12 | setup = TestUtils.renderIntoDocument();
13 | TestUtils.isElementOfType(setup, 'div');
14 |
15 | setTimeout(() => done(), 500);
16 | });
17 | });
--------------------------------------------------------------------------------
/client/src/tests/pages/NavBar.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Card } from 'react-md/lib/Cards';
6 |
7 | import Navbar from '../../components/Navbar';
8 |
9 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
10 |
11 | import dashboardMock from '../mocks/dashboards/pie';
12 | import ElementConnector from '../../components/ElementConnector';
13 |
14 | describe('HomePage', () => {
15 |
16 | let dataSources: IDataSourceDictionary = {};
17 | let navbar;
18 |
19 | beforeAll((done) => {
20 | DataSourceConnector.createDataSources(dashboardMock, dashboardMock.config.connections);
21 | dataSources = DataSourceConnector.getDataSources();
22 |
23 | setTimeout(() => {
24 |
25 | try {
26 | let {id, dependencies, source, actions, props, title, subtitle } = dashboardMock.elements[0];
27 | let atts = {id, dependencies, source, actions, props, title, subtitle };
28 | let navbarElement = ElementConnector.createGenericElement(
29 | Navbar,
30 | 'autorefresher',
31 | 0,
32 | source,
33 | dependencies,
34 | actions,
35 | props,
36 | title,
37 | subtitle);
38 | navbar = TestUtils.renderIntoDocument(navbarElement);
39 | TestUtils.isElementOfType(navbar, 'div');
40 |
41 | // Adding a timeout to make sure Flux cycle is complete
42 | setTimeout(done, 100);
43 |
44 | } catch (e) {
45 | return done(e);
46 | }
47 |
48 | }, 100);
49 | })
50 |
51 | it('Check NavBar is loading', () => {
52 | // This test is just to make sure the component is able to render
53 | });
54 |
55 | afterAll(() => {
56 | ReactDOM.unmountComponentAtNode(navbar);
57 | })
58 | });
--------------------------------------------------------------------------------
/client/src/tests/pages/NotFound.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import NotFound from '../../pages/NotFound';
6 |
7 | describe('NotFound', () => {
8 | let setup;
9 | let setupComponent;
10 |
11 | it('not found is loading', () => {
12 | setup = TestUtils.renderIntoDocument();
13 | TestUtils.isElementOfType(setup, 'div');
14 | });
15 | });
--------------------------------------------------------------------------------
/client/src/tests/pages/Setup.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import Setup from '../../components/Setup';
6 | import SetupComponent from '../../pages/Setup'
7 |
8 | describe('Setup', () => {
9 | let setup;
10 | let setupComponent;
11 |
12 | beforeEach(() => {
13 | Object.defineProperty(window, "matchMedia", {
14 | value: jest.fn(() => { return { matches: true } })
15 | });
16 | });
17 |
18 | it('check setup is loading', (done) => {
19 | setup = TestUtils.renderIntoDocument();
20 | TestUtils.isElementOfType(setup, 'div');
21 |
22 | setTimeout(() => done(), 500);
23 | });
24 |
25 | it('check setup component is loading', (done) => {
26 | setupComponent = TestUtils.renderIntoDocument();
27 | TestUtils.isElementOfType(setupComponent, 'div');
28 |
29 | let switchContainer = TestUtils.scryRenderedDOMComponentsWithClass(setupComponent, "md-switch-track");
30 | expect(switchContainer.length).toBe(1);
31 |
32 | setTimeout(() => done(), 500);
33 | });
34 | });
--------------------------------------------------------------------------------
/client/src/tests/stores/Account.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import * as TestUtils from 'react-dom/test-utils';
4 |
5 | import { Toast } from '../../components/Toast';
6 | import AccountStore from '../../stores/AccountStore';
7 | import AccountActions from '../../actions/AccountActions';
8 | import { mockRequests } from '../mocks/requests/account';
9 |
10 | describe('Data Source: Samples', () => {
11 |
12 | let element;
13 |
14 | beforeAll(() => {
15 | element = TestUtils.renderIntoDocument();
16 | mockRequests();
17 | });
18 |
19 | it ('AccountActions failure', done => {
20 |
21 | let component = TestUtils.scryRenderedComponentsWithType(element, Toast);
22 | expect(component[0].state.toasts.length).toBe(0);
23 | AccountActions.updateAccount();
24 |
25 | setTimeout(
26 | () => {
27 | try {
28 | component = TestUtils.scryRenderedComponentsWithType(element, Toast);
29 | expect(component[0].state.toasts.length).toBe(1);
30 | done();
31 | } catch (e) {
32 | done.fail(e);
33 | }
34 | },
35 | 10);
36 | });
37 |
38 | it ('Testing AccountActions', done => {
39 |
40 | const stateUpdate = (state) => {
41 | expect(state).toHaveProperty('account');
42 | AccountStore.unlisten(stateUpdate);
43 | done();
44 | };
45 | AccountStore.listen(stateUpdate);
46 | AccountActions.updateAccount();
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/client/src/tests/stores/Connection.test.ts:
--------------------------------------------------------------------------------
1 | import ConnectionsStore from "../../stores/ConnectionsStore";
2 | import ConnectionsActions from "../../actions/ConnectionsActions";
3 | //import { mockRequests } from '../mocks/requests/account';
4 |
5 | describe('Data Source: Samples', () => {
6 |
7 | beforeAll(() => {
8 | // mockRequests();
9 | })
10 |
11 | it ('Testing SetupActions', (done) => {
12 |
13 | ConnectionsStore.listen((state) => {
14 | expect(state).toHaveProperty('connections');
15 | done();
16 | });
17 | ConnectionsActions.updateConnection('Conn name', {});
18 | });
19 | })
20 |
--------------------------------------------------------------------------------
/client/src/tests/stores/Settings.test.ts:
--------------------------------------------------------------------------------
1 | import SettingsStore from "../../stores/SettingsStore";
2 | import SettingsActions from "../../actions/SettingsActions";
3 |
4 | describe('Data Source: Samples', () => {
5 |
6 | this.timeout = 10000;
7 |
8 | beforeAll(() => {
9 | });
10 |
11 |
12 | it ('Testing SetupActions - failure message', done => {
13 |
14 | //fff(Store, test, callback)
15 |
16 | let checkState = state => {
17 | expect(state).toHaveProperty('isSavingSettings');
18 | expect(state.isSavingSettings).toEqual(false);
19 |
20 | SettingsStore.unlisten(checkState);
21 | done();
22 | };
23 | SettingsStore.listen(checkState);
24 |
25 | (SettingsActions.saveSettingsCompleted as any).defer();
26 | });
27 |
28 |
29 | it ('Testing SetupActions', done => {
30 |
31 | let checkState = state => {
32 | expect(state).toHaveProperty('isSavingSettings');
33 | expect(state.isSavingSettings).toEqual(true);
34 |
35 | SettingsStore.unlisten(checkState);
36 | done();
37 | };
38 | SettingsStore.listen(checkState);
39 |
40 | (SettingsActions.saveSettings as any).defer();
41 | });
42 | })
43 |
--------------------------------------------------------------------------------
/client/src/tests/stores/Setup.test.ts:
--------------------------------------------------------------------------------
1 | import SetupStore from "../../stores/SetupStore";
2 | import SetupActions from "../../actions/SetupActions";
3 |
4 | describe('Data Source: Samples', () => {
5 |
6 | this.timeout = 10000;
7 | beforeAll(() => {
8 | })
9 |
10 | it('Testing SetupActions', (done) => {
11 |
12 | let checkState = (state) => {
13 | expect(state).toMatchSnapshot('SetupActionsLoad')
14 |
15 | SetupStore.unlisten(checkState);
16 | done();
17 | };
18 |
19 | SetupStore.listen(checkState);
20 | (SetupActions.load as any).defer();
21 | });
22 | })
23 |
--------------------------------------------------------------------------------
/client/src/tests/stores/__snapshots__/Setup.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`SetupActionsLoad 1`] = `
4 | Store {
5 | "admins": Array [],
6 | "allowHttp": false,
7 | "clientID": "",
8 | "clientSecret": "",
9 | "enableAuthentication": false,
10 | "issuer": "",
11 | "loaded": true,
12 | "redirectUrl": "",
13 | "saveSuccess": true,
14 | "stage": "",
15 | }
16 | `;
17 |
--------------------------------------------------------------------------------
/client/src/tests/utils/__snapshots__/utils.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`sample dashboard 1`] = `
4 | "{
5 | id: \\"id\\",
6 | url: \\"url\\",
7 | icon: \\"icon\\",
8 | name: \\"name\\",
9 | config: {
10 | connections: { },
11 | layout: { cols: { lg: 12,md: 10,sm: 6,xs: 4,xxs: 2 },breakpoints: { lg: 1200,md: 996,sm: 768,xs: 480,xxs: 0 } }
12 | },
13 | dataSources: [
14 | {
15 | id: \\"samples\\",
16 | type: \\"Sample\\",
17 | params: {
18 | samples: { values: [{ id: \\"value1\\",count: 60 },{ id: \\"value2\\",count: 10 },{ id: \\"value3\\",count: 30 }] }
19 | }
20 | }
21 | ],
22 | filters: [],
23 | elements: [],
24 | dialogs: []
25 | }"
26 | `;
27 |
--------------------------------------------------------------------------------
/client/src/tests/utils/setup.ts:
--------------------------------------------------------------------------------
1 | import ConfigurationsActions from '../../actions/ConfigurationsActions';
2 | import ConfigurationsStore from '../../stores/ConfigurationsStore';
3 | import { DataSourceConnector, IDataSourceDictionary } from '../../data-sources';
4 |
5 | function setupTests(
6 | dashboardMock: IDashboardConfig,
7 | setDataSources: (ds: IDataSourceDictionary) => void,
8 | done: () => void): void {
9 |
10 | // Waiting for all defered functions to complete their execution
11 | ConfigurationsStore.listen(() => {
12 | let dataSources = DataSourceConnector.getDataSources();
13 | setDataSources(dataSources);
14 | return done();
15 | });
16 |
17 | ConfigurationsActions.loadDashboardComplete(dashboardMock);
18 | }
19 |
20 | export {
21 | setupTests
22 | };
--------------------------------------------------------------------------------
/client/src/tests/utils/utils.test.ts:
--------------------------------------------------------------------------------
1 | import utils from '../../utils';
2 | import dashboardSample from '../mocks/dashboards/samples';
3 | import dashboardAI from '../mocks/dashboards/application-insights';
4 | import dashboardAIForked from '../mocks/dashboards/application-insights-forked';
5 |
6 | describe('Utils', () => {
7 |
8 | it ('KM Number', () => {
9 |
10 | expect(utils.kmNumber(0)).toBe("0");
11 | expect(utils.kmNumber(10)).toBe("10");
12 | expect(utils.kmNumber(0.1)).toBe("0.1");
13 | expect(utils.kmNumber(0.999)).toBe("1.0");
14 | expect(utils.kmNumber(10, '%')).toBe("10%");
15 | expect(utils.kmNumber(100)).toBe("100");
16 | expect(utils.kmNumber(1000)).toBe("1.0K");
17 | expect(utils.kmNumber(10000)).toBe("10.0K");
18 | expect(utils.kmNumber(100000)).toBe("100.0K");
19 | expect(utils.kmNumber(1000000)).toBe("1.0M");
20 |
21 | });
22 |
23 | it ('Ago', () => {
24 | let time = new Date();
25 | time.setSeconds(time.getSeconds() - 10);
26 | expect(utils.ago(time)).toBe('a few seconds ago');
27 |
28 | time = new Date();
29 | time.setMinutes(time.getMinutes() - 10);
30 | expect(utils.ago(time)).toBe('10 minutes ago');
31 |
32 | time = new Date();
33 | time.setDate(time.getDate() - 1);
34 | expect(utils.ago(time)).toBe('a day ago');
35 |
36 | time = new Date();
37 | time.setDate(time.getDate() - 10);
38 | expect(utils.ago(time)).toBe('10 days ago');
39 |
40 | time = new Date();
41 | time.setMonth(time.getMonth() - 10);
42 | expect(utils.ago(time)).toBe('10 months ago');
43 | });
44 |
45 | it ('string <==> object', () => {
46 |
47 | let s_dashboardSample = utils.convertDashboardToString(dashboardSample);
48 | let s_dashboardAI = utils.convertDashboardToString(dashboardAI);
49 | let s_dashboardAIForked = utils.convertDashboardToString(dashboardAIForked);
50 |
51 | expect(s_dashboardSample).toMatchSnapshot('sample dashboard');
52 | //expect(s_dashboardAI).toMatchSnapshot('ai dashboard');
53 | //expect(s_dashboardAIForked).toMatchSnapshot('ai forked dashboard');
54 |
55 | });
56 | });
57 |
--------------------------------------------------------------------------------
/client/src/tests/utils/utils.test.tsx:
--------------------------------------------------------------------------------
1 | import utils from '../../utils';
2 | import dashboardWithDialog from '../mocks/dashboards/dialogs';
3 |
4 | describe('Toast', () => {
5 |
6 | let element;
7 |
8 | it('test utility methods', () => {
9 | var dashboardStr = utils.objectToString(dashboardWithDialog);
10 | expect(dashboardStr.length).toBeGreaterThanOrEqual(1);
11 | });
12 |
13 | });
--------------------------------------------------------------------------------
/client/src/utils/data-formats/common.ts:
--------------------------------------------------------------------------------
1 | import { ToastActions } from '../../components/Toast';
2 | import { IDataSourcePlugin } from '../../data-sources/plugins/DataSourcePlugin';
3 |
4 | export enum DataFormatTypes {
5 | none,
6 | timespan,
7 | flags,
8 | retention,
9 | timeline
10 | }
11 |
12 | export interface IDataFormat {
13 | type: string;
14 | args: any;
15 | }
16 |
17 | export function formatWarn(text: string, format: string, plugin: IDataSourcePlugin) {
18 | ToastActions.addToast({ text: `[format:${format}] text [data source:${plugin._props.id}]` });
19 | }
20 |
21 | export function getPrefix(format: string | IDataFormat) {
22 | return (format && typeof format !== 'string' && format.args && format.args.prefix) || '';
23 | }
--------------------------------------------------------------------------------
/client/src/utils/data-formats/formats/filter.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import utils from '../../index';
3 | import { DataFormatTypes, IDataFormat, formatWarn, getPrefix } from '../common';
4 | import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugin';
5 |
6 | /**
7 | * Formats a result to fit a filter.
8 | *
9 | * Receives a list of filtering values:
10 | * values: [
11 | * { field: 'value 1' },
12 | * { field: 'value 2' },
13 | * { field: 'value 3' },
14 | * ]
15 | *
16 | * And outputs the result in a consumable filter way:
17 | * result: {
18 | * "prefix-filters": [ 'value 1', 'value 2', 'value 3' ],
19 | * "prefix-selected": [ ],
20 | * }
21 | *
22 | * "prefix-selected" will be able to hold the selected values from the filter component
23 | *
24 | * @param format {
25 | * type: 'filter',
26 | * args: {
27 | * prefix: string - a prefix string for the exported variables (default to id).
28 | * data: string - the state property holding the data (default is 'values').
29 | * field: string - the field holding the filter values in the results (default = "value")
30 | * }
31 | * }
32 | * @param state Current received state from data source
33 | * @param dependencies Dependencies for the plugin
34 | * @param plugin The entire plugin (for id generation, params etc...)
35 | * @param prevState The previous state to compare for changing filters
36 | */
37 | export function filter (
38 | format: string | IDataFormat,
39 | state: any,
40 | dependencies: IDictionary,
41 | plugin: IDataSourcePlugin,
42 | prevState: any) {
43 |
44 | const args = typeof format !== 'string' ? format.args : {};
45 | const prefix = getPrefix(format);
46 | const field = args.field || 'value';
47 | const unknown = args.unknown || 'unknown';
48 |
49 | const values = state[args.data || 'values'];
50 | if (!values) { return null; }
51 |
52 | // This code is meant to fix the following scenario:
53 | // When "Timespan" filter changes, to "channels-selected" variable
54 | // is going to be reset into an empty set.
55 | // For this reason, using previous state to copy filter
56 | const filters = values.map(x => x[field] || unknown);
57 | let result = {};
58 | result[prefix + 'values-all'] = filters;
59 |
60 | if (prevState[prefix + 'values-selected'] === undefined) {
61 | result[prefix + 'values-selected'] = [];
62 | }
63 |
64 | return result;
65 | }
--------------------------------------------------------------------------------
/client/src/utils/data-formats/formats/flags.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import utils from '../../index';
3 | import { DataFormatTypes, IDataFormat, formatWarn, getPrefix } from '../common';
4 | import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugin';
5 |
6 | /**
7 | * Turns a list of values into a list of flags
8 | *
9 | * Receives a list of filtering values (on the data source params variable):
10 | * params: {
11 | * values: [ 'value1', 'value2', 'value3' ]
12 | * }
13 | *
14 | * And outputs the result in a consumable filter way:
15 | * result: {
16 | * "value1": false, (will be true if prefix-selected contains "value1")
17 | * "value2": false,
18 | * "value3": false,
19 | * "prefix-filters": [ 'value 1', 'value 2', 'value 3' ],
20 | * "prefix-selected": [ ],
21 | * }
22 | *
23 | * "prefix-selected" will be able to hold the selected values from the filter component
24 | *
25 | * @param format 'filter' | {
26 | * type: 'filter',
27 | * args: {
28 | * prefix: string - a prefix string for the exported variables (default to id).
29 | * data: string - the state property holding the data (default is 'values').
30 | * }
31 | * }
32 | * @param state Current received state from data source
33 | * @param dependencies Dependencies for the plugin
34 | * @param plugin The entire plugin (for id generation, params etc...)
35 | * @param prevState The previous state to compare for changing filters
36 | */
37 | export function flags(
38 | format: string | IDataFormat,
39 | state: any,
40 | dependencies: IDictionary,
41 | plugin: IDataSourcePlugin,
42 | prevState: any) {
43 |
44 | const prefix = getPrefix(format);
45 | const args = typeof format !== 'string' && format.args || {};
46 | const params = plugin.getParams();
47 |
48 | if (!params || !Array.isArray(params.values)) {
49 | return formatWarn('A paramerter "values" is expected as an array on "params" in the data source', 'filter', plugin);
50 | }
51 |
52 | if (!state) { return null; }
53 |
54 | let values = params[args.data || 'values'];
55 | let flagsobj = {};
56 | values.forEach(key => { flagsobj[key] = state.selectedValue === key; });
57 |
58 | flagsobj[prefix + 'values-all'] = values;
59 | flagsobj[prefix + 'values-selected'] = state.selectedValue || [];
60 |
61 | return flagsobj;
62 | }
--------------------------------------------------------------------------------
/client/src/utils/data-formats/formats/pie.ts:
--------------------------------------------------------------------------------
1 | import * as _ from 'lodash';
2 | import utils from '../../index';
3 | import { DataFormatTypes, IDataFormat, formatWarn, getPrefix } from '../common';
4 | import { IDataSourcePlugin } from '../../../data-sources/plugins/DataSourcePlugin';
5 |
6 | /**
7 | * Formats a result to suite a pie chart
8 | *
9 | * Receives a list of filtering values:
10 | * values: [
11 | * { count: 10, field: 'piece 1' },
12 | * { count: 15, field: 'piece 2' },
13 | * { count: 44, field: 'piece 3' },
14 | * ]
15 | *
16 | * And outputs the result in a consumable filter way:
17 | * result: {
18 | * "prefix-pieData": [
19 | * { name: 'bar 1', value: 10},
20 | * { name: 'bar 2', value: 15},
21 | * { name: 'bar 3', value: 20},
22 | * ],
23 | * }
24 | *
25 | * "prefix-selected" will be able to hold the selected values from the filter component
26 | *
27 | * @param format {
28 | * type: 'pie',
29 | * args: {
30 | * value: string - The field name holding the value the pie piece
31 | * data: string - the state property holding the data (default is 'values').
32 | * label: string - The field name holding the series name (aggregation in a specific field)
33 | * maxLength: number - At what length to cut string values (default: 13),
34 | * }
35 | * }
36 | * @param state Current received state from data source
37 | * @param plugin The entire plugin (for id generation, params etc...)
38 | * @param prevState The previous state to compare for changing filters
39 | */
40 | export function pie(
41 | format: string | IDataFormat,
42 | state: any,
43 | plugin: IDataSourcePlugin,
44 | prevState: any) {
45 |
46 | if (typeof format === 'string') {
47 | return formatWarn('format should be an object with args', 'timeline', plugin);
48 | }
49 |
50 | const args = format.args || {};
51 | let values: any[] = state[args.data || 'values'] || [];
52 | const prefix = getPrefix(format);
53 |
54 | const labelField = args.label || 'name';
55 | const valueField = args.value || 'value';
56 | let maxLength = args.maxLength && parseInt(args.maxLength, 10) || 13;
57 | let maxLengthCut = Math.max(0, maxLength - 3);
58 |
59 | let result = {};
60 | result[prefix + 'pieData'] = values.map(value => ({
61 | name: maxLength && value[labelField].length > maxLength
62 | ? value[labelField].substr(0, maxLengthCut) + '...'
63 | : value[labelField],
64 | value: value[valueField]
65 | }));
66 |
67 | return result;
68 | }
--------------------------------------------------------------------------------
/client/src/utils/data-formats/index.ts:
--------------------------------------------------------------------------------
1 | export * from './common';
2 | export * from './formats/bars';
3 | export * from './formats/filter';
4 | export * from './formats/flags';
5 | export * from './formats/pie';
6 | export * from './formats/retention';
7 | export * from './formats/scorecard';
8 | export * from './formats/timeline';
9 | export * from './formats/timespan';
10 | export * from './formats/filtered-samples';
--------------------------------------------------------------------------------
/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/dist",
4 | "module": "commonjs",
5 | "target": "es5",
6 | "lib": ["es6", "dom"],
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "jsx": "react",
10 | "moduleResolution": "node",
11 | "rootDir": "src",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": false,
15 | "noImplicitAny": false,
16 | "strictNullChecks": false,
17 | "suppressImplicitAnyIndexErrors": true,
18 | "noUnusedLocals": false
19 | },
20 | "exclude": [
21 | "node_modules",
22 | "src/setupTests.ts"
23 | ],
24 | "types": [
25 | "typePatches"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/client/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "build/dist",
4 | "module": "commonjs",
5 | "target": "es5",
6 | "lib": ["es6", "dom"],
7 | "sourceMap": true,
8 | "allowJs": true,
9 | "jsx": "react",
10 | "moduleResolution": "node",
11 | "rootDir": "src",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": false,
15 | "noImplicitAny": false,
16 | "strictNullChecks": false,
17 | "suppressImplicitAnyIndexErrors": true,
18 | "noUnusedLocals": false
19 | },
20 | "exclude": [
21 | "node_modules",
22 | "src/setupTests.ts"
23 | ],
24 | "types": [
25 | "typePatches"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # Ibex Dashboard Development Guide
2 |
3 | ## Framework
4 | This project is built using [create-react-app](https://github.com/facebookincubator/create-react-app).
5 | The server side appraoch was addopted [through this link](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/).
6 |
7 | * [Creating a new Dashboard Template](dashboard-creation.md)
8 |
9 | # Orchestrators
10 |
11 | ## DataSourceConnector
12 | [DataSourceConnector](../client/src/data-sources/DataSourceConnector.ts) is a class that created and initializes the various data sources.
13 |
14 | [ElementConnector](../client/src/components/ElementConnector.tsx) is a class the creates and initializes Visual component for the dashboard and consecutive dialogs.
15 |
16 | ## Plugins
17 | Many of the aspects in this project are extendible. The following are possibilities to donate your own plugins.
18 |
19 | ## Connection Plugins
20 |
21 | Connection plugins are connected to Data Source plugins. A Data Source can have a connection plugin which will provide all the instances of the Data Source with a single connection to receive credentials information from.
22 |
23 | ## Data Source Plugins
24 |
25 | [How to create a Data Source Plugin](add-new-data-source.md)
26 |
27 | * Constant
28 | * Sample
29 | * Application Insights
30 | * [CosmosDB](data-sources/cosmos-db.md)
31 | * [Bot Framework](data-sources/bot-framework.md)
32 | * GraphQL
33 | * Azure
34 |
35 | ## Elements Plugins
36 |
37 | [How to create a Visual Plugin](add-new-element.md)
38 |
39 | * [Area Chart](components/area.md)
40 | * [Bar Chart](components/bar.md)
41 | * [Detail View](components/detail.md)
42 | * [Pie Chart](components/pie.md)
43 | * [Request Button](components/requestbutton.md)
44 | * [Scatter Chart](components/scatter.md)
45 | * [Scorecard](components/scorecard.md)
46 | * [Split View Panel](components/splitpanel.md)
47 | * [Table](components/table.md)
48 | * [Timeline Chart](components/timeline.md)
49 |
50 | ## data-formats plugins
51 |
52 | [A short excerpt on data-formats](data-formats)
53 |
54 | # Additional Features
55 |
56 | * [Two Modes Elements](two-modes-element.md)
57 | * [Filter Plugins](filter.md)
58 | * [Dialogs](dialog.md)
59 | * [Actions](actions.md)
--------------------------------------------------------------------------------
/docs/bot-framedash-msgs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/docs/bot-framedash-msgs.png
--------------------------------------------------------------------------------
/docs/bot-framedash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/docs/bot-framedash.png
--------------------------------------------------------------------------------
/docs/components/area.md:
--------------------------------------------------------------------------------
1 | # Area
2 |
3 | This article explains how define an Area element. This element is composed of an [AreaChart](http://recharts.org/#/en-US/api/AreaChart) component.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "Area" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `subtitle`| `string` || Description of the chart (displayed as tooltip)
13 | | `size`| `{ w: number, h: number}` || Width/Height of the view
14 | | `dependencies`| `object` || Dependencies that are required for this element
15 | | `props`| `object` || Additional properties to define for this element
16 |
17 | ## Dependencies
18 |
19 | Define `dependencies` as follows:
20 |
21 | | Property | Type | Description
22 | | :--------|:-----|:-----------
23 | | `values`| `string` | Reference to data source values
24 | | `lines`| `string` | Reference to data source lines
25 | | `timeFormat`| `string` | Reference to data source timeline
26 |
27 | #### Dependencies sample
28 |
29 | ```js
30 | dependencies: {
31 | values: "ai:timeline-graphData",
32 | lines: "ai:timeline-channels",
33 | timeFormat: "ai:timeline-timeFormat"
34 | }
35 | ```
36 |
37 | ## Props
38 |
39 | Define `props` as follows:
40 |
41 | | Property | Type | Description
42 | | :--------|:-----|:-----------
43 | | `isStacked`| `boolean` | Display as stacked area
44 | | `showLegend`| `boolean` | Display legend
45 | | `areaProps`| `object` | [AreaChart](http://recharts.org/#/en-US/api/AreaChart) properties
46 |
47 | #### Props sample
48 |
49 | ```js
50 | props: {
51 | isStacked: true,
52 | showLegend: false
53 | }
54 | ```
55 |
56 | #### AreaChart properties
57 | - Tip: `areaProps` can be used to specify additional properties of the [AreaChart](http://recharts.org/#/en-US/api/AreaChart) chart component such as `syncId` to link related charts. Refer to the [AreaChart API](http://recharts.org/#/en-US/api/AreaChart) for more info.
58 |
59 | ```js
60 | props: {
61 | isStacked: true,
62 | showLegend: false
63 | areaProps: {
64 | syncId: "sharedId"
65 | }
66 | }
67 | ```
--------------------------------------------------------------------------------
/docs/components/bar.md:
--------------------------------------------------------------------------------
1 | # BarData
2 |
3 | This article explains how define a BarData element. This element is composed of a [BarChart](http://recharts.org/#/en-US/api/BarChart) component.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "BarData" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `subtitle`| `string` || Description of the chart (displayed as tooltip)
13 | | `size`| `{ w: number, h: number}` || Width/Height of the view
14 | | `dependencies`| `object` || Dependencies that are required for this element
15 | | `props`| `object` || Additional properties to define for this element
16 | | `actions`| `object` || Actions to trigger when bar is clicked
17 |
18 | ## Dependencies
19 |
20 | Define `dependencies` as follows:
21 |
22 | | Property | Type | Description
23 | | :--------|:-----|:-----------
24 | | `values`| `string` | Reference to data source values
25 | | `bars`| `string` | Reference to data source bars
26 |
27 | #### Dependencies sample
28 |
29 | ```js
30 | dependencies: {
31 | values: "ai:intents",
32 | bars: "ai:intents-bars",
33 | }
34 | ```
35 |
36 | ## Props
37 |
38 | Define `props` as follows:
39 |
40 | | Property | Type | Description
41 | | :--------|:-----|:-----------
42 | | `nameKey`| `string` | Data key to use for x-axis
43 | | `barProps`| `object` | [BarChart](http://recharts.org/#/en-US/api/BarChart) properties
44 |
45 | #### Props sample
46 |
47 | ```js
48 | props: {
49 | nameKey: "intent"
50 | }
51 | ```
52 |
53 | #### BarChart properties
54 | - Tip: `barProps` can be used to specify additional properties of the [BarChart](http://recharts.org/#/en-US/api/BarChart). Refer to the [BarChart API](http://recharts.org/#/en-US/api/BarChart) for more info.
55 |
56 | ## Actions
57 |
58 | Define an `onBarClick` action as follows:
59 |
60 | | Property | Type | Description
61 | | :--------|:-----|:-----------
62 | | `action`| `string` | Reference to dialog id
63 | | `params`| `object` | Arguments or properties that need to be passed to run the dialog's query
64 |
65 | #### Actions sample
66 |
67 | ```js
68 | actions: {
69 | onBarClick: {
70 | action: "dialog:conversations",
71 | params: {
72 | title: "args:intent",
73 | intent: "args:intent",
74 | queryspan: "timespan:queryTimespan"
75 | }
76 | }
77 | }
78 | ```
79 |
80 |
--------------------------------------------------------------------------------
/docs/components/detail.md:
--------------------------------------------------------------------------------
1 | # Detail
2 |
3 | The detail view can be used to display a JSON result with HTML formatting to make it easier to inspect nested information.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "Detail" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `size`| `{ w: number, h: number}` || Width/Height of the view
13 | | `dependencies`| `object` || Dependencies that will be requested for this element
14 | | `props`| `object` || Additional properties to define for this element
15 |
16 | ## Dependencies
17 |
18 | Define `values` as follows:
19 |
20 | | Property | Type | Description
21 | | :--------|:-----|:-----------
22 | | `values`| `string` | Reference to data source's id
23 |
24 | #### Dependencies sample:
25 |
26 | ```js
27 | dependencies: {
28 | values: "errordetail-data"
29 | },
30 | ```
31 |
32 | ## Props
33 |
34 | Define `props` as follows:
35 |
36 | | Property | Type | Description
37 | | :--------|:-----|:-----------
38 | | `cols`| `object[]` | Collection of table column properties
39 |
40 | Define `props.cols` as follows:
41 |
42 | | Property | Type | Description
43 | | :--------|:-----|:-----------
44 | | `header`| `string` | Column header
45 | | `field`| `string` | Defines the query field
46 |
47 | #### Props sample:
48 |
49 | ```js
50 | props: {
51 | cols: [{
52 | header: "Handle",
53 | field: "handledAt"
54 | },{
55 | header: "Type",
56 | field: "type"
57 | },{
58 | header: "Message",
59 | field: "innermostMessage"
60 | },{
61 | header: "Conversation ID",
62 | field: "conversationId"
63 | },{
64 | header: "Operation ID",
65 | field: "operation_Id"
66 | },{
67 | header: "Timestamp",
68 | field: "timestamp"
69 | },{
70 | header: "Details",
71 | field: "details"
72 | }]
73 | }
74 | ```
--------------------------------------------------------------------------------
/docs/components/pie.md:
--------------------------------------------------------------------------------
1 | # PieData
2 |
3 | This article explains how define an PieData element. This element is composed of a [PieChart](http://recharts.org/#/en-US/api/PieChart) component.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "PieData" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `subtitle`| `string` || Description of the chart (displayed as tooltip)
13 | | `size`| `{ w: number, h: number}` || Width/Height of the view
14 | | `dependencies`| `object` || Dependencies that are required for this element
15 | | `props`| `object` || Additional properties to define for this element
16 |
17 | ## Dependencies
18 |
19 | Define `dependencies` as follows:
20 |
21 | | Property | Type | Description
22 | | :--------|:-----|:-----------
23 | | `values`| `string` | Reference to data source values
24 |
25 | #### Dependencies sample
26 |
27 | ```js
28 | dependencies: {
29 | values: "ai:timeline-channelUsage",
30 | }
31 | ```
32 |
33 | ## Props
34 |
35 | Define `props` as follows:
36 |
37 | | Property | Type | Description
38 | | :--------|:-----|:-----------
39 | | `showLegend`| `boolean` | Display legend
40 | | `compact`| `boolean` | Display as compact chart
41 | | `pieProps`| `object` | [PieChart](http://recharts.org/#/en-US/api/PieChart) properties
42 |
43 | #### Props sample
44 |
45 | ```js
46 | props: {
47 | showLegend: false,
48 | compact: true
49 | }
50 | ```
51 |
52 | #### PieChart properties
53 | - Tip: `pieProps` can be used to specify additional properties of the [PieChart](http://recharts.org/#/en-US/api/PieChart). Refer to the [PieChart API](http://recharts.org/#/en-US/api/PieChart) for more info.
--------------------------------------------------------------------------------
/docs/components/scatter.md:
--------------------------------------------------------------------------------
1 | # Scatter
2 |
3 | This article explains how define a Scatter element. This element is composed of an [ScatterChart](http://recharts.org/#/en-US/api/ScatterChart) component.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "Scatter" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `subtitle`| `string` || Description of the chart (displayed as tooltip)
13 | | `size`| `{ w: number, h: number}` || Width/Height of the view
14 | | `dependencies`| `object` || Dependencies that are required for this element
15 | | `props`| `object` || Additional properties to define for this element
16 |
17 | ## Dependencies
18 |
19 | Define `dependencies` as follows:
20 |
21 | | Property | Type | Description
22 | | :--------|:-----|:-----------
23 | | `groupedValues`| `string` | Reference to data source grouped values. Each group is a Scatter line.
24 |
25 | #### Dependencies sample
26 |
27 | ```js
28 | dependencies: {
29 | groupedValues: "ai:channelActivity-groupedValues"
30 | }
31 | ```
32 |
33 | ## Props
34 |
35 | Define `props` as follows:
36 |
37 | | Property | Type | Description
38 | | :--------|:-----|:-----------
39 | | `xDataKey`| `string` | x-axis data key
40 | | `yDataKey`| `string` | y-axis data key
41 | | `zDataKey`| `string` | z-axis data key
42 | | `zRange`| `number[]` | Range of the z-axis used for scale of scatter points.
43 | | `scatterProps`| `object` | [ScatterChart](http://recharts.org/#/en-US/api/ScatterChart) properties
44 |
45 | ```js
46 | props: {
47 | xDataKey: "hourOfDay",
48 | yDataKey: "duration",
49 | zDataKey: "count",
50 | zRange: [10,500]
51 | }
52 | ```
53 |
54 | #### ScatterChart properties
55 | - Tip: `scatterProps` can be used to specify additional properties of the [ScatterChart](http://recharts.org/#/en-US/api/ScatterChart). Refer to the [ScatterChart API](http://recharts.org/#/en-US/api/ScatterChart) for more info.
--------------------------------------------------------------------------------
/docs/components/timeline.md:
--------------------------------------------------------------------------------
1 | # Timeline
2 |
3 | This article explains how define a Timeline element. This element is composed of an [LineChart](http://recharts.org/#/en-US/api/LineChart) component.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "Timeline" |
11 | | `title`| `string` || Title that will appear at the top of the view
12 | | `subtitle`| `string` || Description of the chart (displayed as tooltip)
13 | | `size`| `{ w: number, h: number}` || Width/Height of the view
14 | | `dependencies`| `object` || Dependencies that are required for this element
15 | | `props`| `object` || Additional properties to define for this element
16 |
17 | ## Dependencies
18 |
19 | Define `dependencies` as follows:
20 |
21 | | Property | Type | Description
22 | | :--------|:-----|:-----------
23 | | `values`| `string` | Reference to data source values
24 | | `lines`| `string` | Reference to data source lines
25 | | `timeFormat`| `string` | Reference to data source timeline
26 |
27 | #### Dependencies sample
28 |
29 | ```js
30 | dependencies: {
31 | values: "ai:timeline-graphData",
32 | lines: "ai:timeline-channels",
33 | timeFormat: "ai:timeline-timeFormat"
34 | }
35 | ```
36 |
37 | ## Props
38 |
39 | Define `props` as follows:
40 |
41 | | Property | Type | Description
42 | | :--------|:-----|:-----------
43 | | `lineProps`| `object` | [LineChart](http://recharts.org/#/en-US/api/LineChart) properties
44 |
45 | #### LineChart properties
46 | - Tip: `lineProps` can be used to specify additional properties of the [LineChart](http://recharts.org/#/en-US/api/LineChart) chart component such as `syncId` to link related charts. Refer to the [LineChart API](http://recharts.org/#/en-US/api/LineChart) for more info.
47 |
48 | ```js
49 | props: {
50 | lineProps: {
51 | syncId: "sharedId"
52 | }
53 | }
54 | ```
--------------------------------------------------------------------------------
/docs/dashboard-creation.md:
--------------------------------------------------------------------------------
1 | # Dashboard Creation
2 | Creating a dashboard relies upon the following assumptions:
3 |
4 | ## Listing Available Templates
5 | The list of available template will appear in the home page and will enable the user to choose a template to create from.
6 | The following fields will appear in the list:
7 |
8 | - `preview` (as an image)
9 | - `name`
10 | - `description`
11 |
12 | # Creation Screen
13 | When clicking on one of the template a dialog will be open with the following fields:
14 |
15 | - `id`: Will be used as an id for the dashboard and in the url
16 | - `name`: Will be used in the title of the dashboard
17 | - `icon`: Will be used in the navigation bar
--------------------------------------------------------------------------------
/docs/data-formats/README.md:
--------------------------------------------------------------------------------
1 | # Data Formats
2 | Data formats are data transformers that align the data received from a data source to the format required by a visual component.
3 |
4 | The available data formats are:
5 |
6 | * [bars](../../client/src/utils/data-formats/formats/bars.ts)
7 | * [filter](../../client/src/utils/data-formats/formats/filter.ts)
8 | * [filtered-samples](../../client/src/utils/data-formats/formats/filtered-samples.ts)
9 | * [flags](../../client/src/utils/data-formats/formats/flags.ts)
10 | * [retention](../../client/src/utils/data-formats/formats/retention.ts)
11 | * [scorecard](../../client/src/utils/data-formats/formats/scorecard.ts)
12 | * [timeline](../../client/src/utils/data-formats/formats/timeline.ts)
13 | * [timespan](../../client/src/utils/data-formats/formats/timespan.ts)
14 |
15 | To create a new Visual Element with a correlating data-format [click here](../add-new-element.md).
--------------------------------------------------------------------------------
/docs/images/bot-fmk-dashboard-intent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/docs/images/bot-fmk-dashboard-intent.png
--------------------------------------------------------------------------------
/docs/images/bot-fmk-dashboard-msgs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/docs/images/bot-fmk-dashboard-msgs.png
--------------------------------------------------------------------------------
/docs/images/bot-fmk-dashboard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/webdevstar/Dashborad/84d34bee2227de8742a0990deaf0e8a1941ce619/docs/images/bot-fmk-dashboard.png
--------------------------------------------------------------------------------
/docs/requestbutton.md:
--------------------------------------------------------------------------------
1 | # Request Button
2 |
3 | A button that opens a url in new window, or sends a request.
4 |
5 | ## Basic properties
6 |
7 | | Property | Type | Value | Description
8 | | :--------|:-----|:------|:------------
9 | | `id`| `string` || ID of the element on the page
10 | | `type`| `string` | "RequestButton" |
11 | | `title`| `string` || Button title
12 | | `size`| `{ w: number, h: number}` || Width/Height of the view
13 | | `location`| `{ x: number, y: number}` || Grid position of the view
14 | | `dependencies`| `object` || Dependencies that will be requested for this element
15 | | `props`| `object` || Additional properties to define for this element
16 |
17 | ## Dependencies
18 |
19 | Define `dependencies` as follows:
20 |
21 | | Property | Type | Value | Description
22 | | :--------|:-----|:------|:------------
23 | | `body`| `object` | `''` | Request body as JSON object
24 | | `headers`| `object` | `{}` | Request headers in `{ key : value }` dictionary format
25 | | `disabled`| `boolean` | `false` | Button state
26 |
27 | ## Props
28 |
29 | Define `props` as follows:
30 |
31 | | Property | Type | Description
32 | | :--------|:-----|:-----------
33 | | `url`| `string | function` | Static url string, or a function with string injection
34 | | `link`| `boolean` | Opens url in new window if set to `true`
35 | | `method`| `string` | Send request using 'GET', 'POST', 'PUT', or 'DELETE'. Default is 'GET'
36 | | `disableAfterFirstClick`| `boolean` | Only allows button to be clicked once if set to `true`
37 | | `icon`| `string` | [Material design icon name](https://material.io/icons/) (use underscores instead of spaces)
38 | | `buttonProps`| `string` | For additional [react-md Button props](https://react-md.mlaursen.com/components/buttons?tab=1)
39 |
40 | NB. A Request Button can act as a link or as an xhr request, but not both.
41 |
42 | #### Request Button sample
43 |
44 | ```js
45 | {
46 | id: "agent-button",
47 | type: "RequestButton",
48 | title: "Open Webchat",
49 | size: { w: 2, h: 1 },
50 | location: { x: 2, y: 0 },
51 | dependencies: { token: "directLine:token", host: "::localhost:3978/webchat" },
52 | props: {
53 | url: ({token, host}) => `http://${host}/?s=${token}`,
54 | link: true,
55 | icon: "open_in_new",
56 | buttonProps: {
57 | iconBefore: false,
58 | secondary: true
59 | }
60 | }
61 | }
62 | ```
--------------------------------------------------------------------------------
/docs/two-modes-element.md:
--------------------------------------------------------------------------------
1 | # Two Modes Element
2 | This article describes how to create two modes for an element in the dashboard
3 |
4 | ## Data Source
5 | First you'll need to create a data source that exposes a set boolean variable:
6 |
7 | ```js
8 | {
9 | id: "modes",
10 | type: "Constant",
11 | params: {
12 | values: ["messages","users"],
13 | selectedValue: "messages"
14 | },
15 | calculated: (state, dependencies) => {
16 | let flags = {};
17 | flags['messages'] = (state.selectedValue === 'messages');
18 | flags['users'] = (state.selectedValue !== 'messages');
19 | return flags;
20 | }
21 | }
22 | ```
23 |
24 | Second, you'll need to create a filter to control that constant:
25 |
26 | ```js
27 | {
28 | type: "TextFilter",
29 | dependencies: {
30 | selectedValue: "modes",
31 | values: "modes:values"
32 | },
33 | actions: {
34 | onChange: "modes:updateSelectedValue"
35 | }
36 | }
37 | ```
38 |
39 | And last, you need to create two instances of the same element (with the same id), where each appears only when their flag is on:
40 |
41 | ```js
42 | elements: [
43 | {
44 | id: "timeline",
45 | type: "Timeline",
46 | title: "Message Rate",
47 | subtitle: "How many messages were sent per timeframe",
48 | size: { w: 5, h: 8 },
49 | dependencies: {
50 | visible: "modes:messages",
51 | values: "ai:timeline-graphData",
52 | lines: "ai:timeline-channels",
53 | timeFormat: "ai:timeline-timeFormat"
54 | }
55 | },
56 | {
57 | id: "timeline",
58 | type: "Timeline",
59 | title: "Users Rate",
60 | subtitle: "How many users were sent per timeframe",
61 | size: { w: 5, h: 8 },
62 | dependencies: {
63 | visible: "modes:users",
64 | values: "ai:timeline-users-graphData",
65 | lines: "ai:timeline-users-channels",
66 | timeFormat: "ai:timeline-users-timeFormat"
67 | }
68 | }
69 | }]
70 | ```
71 |
72 | Notice that each instance has a `visible` property defined under dependencies and a different set of properties and `dependencies`.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibex-dashboard",
3 | "version": "1.3.0",
4 | "private": true,
5 | "dependencies": {
6 | "npm-run-all": "^4.0.2"
7 | },
8 | "scripts": {
9 | "build": "cd client && yarn build",
10 | "start": "node server",
11 | "start:client": "cd client && yarn start",
12 | "start:server": "cd server && yarn start",
13 | "start:dev": "npm-run-all -p start:client start:server",
14 | "postinstall": "cd client && yarn && cd ../server && yarn"
15 | },
16 | "license": "MIT"
17 | }
18 |
--------------------------------------------------------------------------------
/scripts/deployment/webapponlinux/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "appName": {
6 | "defaultValue": "ibex-dashboard-linux",
7 | "type": "string"
8 | }
9 | },
10 | "variables": {
11 | "servicePlanName": "[concat('serviceplan',uniqueString(resourceGroup().id))]"
12 | },
13 | "resources": [
14 | {
15 | "type": "Microsoft.Web/serverfarms",
16 | "name": "[variables('servicePlanName')]",
17 | "apiVersion": "2016-03-01",
18 | "location": "[resourceGroup().location]",
19 | "sku": {
20 | "name": "S1",
21 | "capacity": 1
22 | },
23 | "kind": "linux",
24 | "properties": {
25 | "workerSizeId": 0,
26 | "reserved": true,
27 | "hostingEnvironment": ""
28 | }
29 | },
30 | {
31 | "type": "Microsoft.Web/sites",
32 | "name": "[parameters('appName')]",
33 | "apiVersion": "2016-03-01",
34 | "location": "[resourceGroup().location]",
35 | "properties": {
36 | "name": "[parameters('appName')]",
37 | "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]"
38 | },
39 | "dependsOn": [
40 | "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]"
41 | ],
42 | "resources": [
43 | {
44 | "name": "appsettings",
45 | "type": "config",
46 | "apiVersion": "2016-03-01",
47 | "dependsOn": [
48 | "[resourceId('Microsoft.Web/sites', parameters('appName'))]"
49 | ],
50 | "tags": {
51 | "displayName": "appSettings"
52 | },
53 | "properties": {
54 | "DOCKER_CUSTOM_IMAGE_NAME": "azure/ibex-dashboard"
55 | }
56 | }
57 | ]
58 | }
59 | ]
60 | }
61 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | // server/app.js
2 | const express = require('express');
3 | const expressSession = require('express-session');
4 | const cookieParser = require('cookie-parser');
5 | const morgan = require('morgan');
6 | const path = require('path');
7 | const fs = require('fs');
8 | const bodyParser = require('body-parser');
9 | const authRouter = require('./routes/auth');
10 | const apiRouter = require('./routes/api');
11 | const graphQLRouter = require('./routes/graphql');
12 | const cosmosDBRouter = require('./routes/cosmos-db');
13 | const azureRouter = require('./routes/azure');
14 | const applicationInsightsRouter = require('./routes/applicationInsights');
15 |
16 | const app = express();
17 |
18 |
19 | app.use((req, res, next) => {
20 | console.log(`Request URL: ${req.url}`);
21 | return next();
22 | });
23 |
24 | app.use(cookieParser());
25 | app.use(expressSession({ secret: 'keyboard cat', resave: true, saveUninitialized: false }));
26 | app.use(bodyParser.json());
27 | app.use(bodyParser.urlencoded({ extended: true }));
28 |
29 | // Setup logger
30 | app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] :response-time ms'));
31 |
32 | app.use(authRouter.authenticationMiddleware('/auth', '/api/setup'));
33 | app.use('/auth', authRouter.router);
34 | app.use('/api', apiRouter.router);
35 | app.use('/cosmosdb', cosmosDBRouter.router);
36 | app.use('/azure', azureRouter.router);
37 | app.use('/graphql', graphQLRouter.router);
38 | app.use('/applicationinsights', applicationInsightsRouter.router);
39 |
40 | app.use(express.static(path.resolve(__dirname, '..', 'build')));
41 |
42 | // Always return the main index.html, so react-router render the route in the client
43 | app.get('*', (req, res) => {
44 | res.sendFile(path.resolve(__dirname, '..', 'build', 'index.html'));
45 | });
46 |
47 | module.exports = app;
--------------------------------------------------------------------------------
/server/common/paths.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const resourcesPaths = () => ({
4 | persistentFolder: path.join(__dirname, '..', 'dashboards', 'persistent'),
5 | privateDashboard: path.join(__dirname, '..', 'dashboards','persistent','private'),
6 | preconfDashboard: path.join(__dirname, '..', 'dashboards', 'preconfigured'),
7 | privateTemplate: path.join(__dirname, '..', 'dashboards','persistent', 'customTemplates')
8 | });
9 |
10 | module.exports = {
11 | resourcesPaths
12 | }
--------------------------------------------------------------------------------
/server/config/auth.basic.json:
--------------------------------------------------------------------------------
1 | {
2 | "identityMetadata": "https://login.microsoftonline.com/common/.well-known/openid-configuration",
3 | "validateIssuer": true,
4 | "skipUserProfile": false,
5 | "responseType": "id_token code",
6 | "responseMode": "form_post"
7 | }
8 |
--------------------------------------------------------------------------------
/server/config/setup.initial.json:
--------------------------------------------------------------------------------
1 | {
2 | "admins": [],
3 | "stage": "none",
4 | "allowHttp": false,
5 | "enableAuthentication": false,
6 | "redirectUrl": "",
7 | "clientID": "",
8 | "clientSecret": ""
9 | }
--------------------------------------------------------------------------------
/server/dashboards/preconfigured/application-insights-sample.ts:
--------------------------------------------------------------------------------
1 | ///
2 | import * as _ from 'lodash';
3 |
4 | // The following line is important to keep in that format so it can be rendered into the page
5 | export const config: IDashboardConfig = /*return*/ {
6 | id: "app_insights_sample",
7 | name: "Application Insights Sample",
8 | icon: "dashboard",
9 | url: "app_insights_sample",
10 | description: "A basic Application Insights dashboard",
11 | preview: "/images/sample.png",
12 | category: 'Samples',
13 | html: `
14 |
15 | This is a basic Application Insights dashboard.
16 | This dashboard is built to view Requests that are sent to Application Insights.
17 |
18 | `,
19 | config: {
20 | connections: {},
21 | layout: {
22 | isDraggable: true,
23 | isResizable: true,
24 | rowHeight: 30,
25 | verticalCompact: false,
26 | cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 },
27 | breakpoints: { lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }
28 | }
29 | },
30 | dataSources: [
31 | {
32 | id: "timespan",
33 | type: "Constant",
34 | params: { values: ["24 hours", "1 week", "1 month"], selectedValue: "24 hours" },
35 | format: "timespan"
36 | },
37 | {
38 | id: "requests",
39 | type: "ApplicationInsights/Query",
40 | dependencies: { timespan: "timespan", queryTimespan: "timespan:queryTimespan", granularity: "timespan:granularity" },
41 | params: {
42 | table: "requests",
43 | queries: {
44 | count: {
45 | query: ({ granularity }) => {
46 | return `
47 | summarize count= count() by bin(timestamp, ${granularity}) |
48 | order by timestamp asc
49 | `
50 | },
51 | format: { type: "timeline", args: { timeField: "timestamp", valueField: "count" } }
52 | },
53 | }
54 | }
55 | }
56 | ],
57 | filters: [
58 | {
59 | type: "TextFilter",
60 | title: "Timespan",
61 | source: "timespan",
62 | actions: { onChange: "timespan:updateSelectedValue" },
63 | first: true
64 | },
65 | ],
66 | elements: [
67 | {
68 | id: "serverResponseTime",
69 | type: "Timeline",
70 | title: "Server requests count",
71 | subtitle: "The server requests count in the selected time range",
72 | size: { w: 5, h: 8 },
73 | source: "requests:count",
74 | },
75 | ],
76 | dialogs: [
77 | ]
78 | }
--------------------------------------------------------------------------------
/server/helpers/resourceFieldProvider.js:
--------------------------------------------------------------------------------
1 | // Template/Dashboard metadata is stored inside the files
2 | // To read the metadata we could eval() the files, but that's dangerous.
3 | // Instead, we use regular expressions to pull them out
4 | // Assumes that:
5 | // * the metadata comes before config information
6 | // * fields in the file look like fieldname: "string" or fieldname: 'string'
7 | // * or, special case, html: `string`
8 |
9 | const fields = {
10 | id: /\s*id:\s*("|')(.*)("|')/,
11 | name: /\s*name:\s*("|')(.*)("|')/,
12 | description: /\s*description:\s*("|')(.*)("|')/,
13 | icon: /\s*icon:\s*("|')(.*)("|')/,
14 | logo: /\s*logo:\s*("|')(.*)("|')/,
15 | url: /\s*url:\s*("|')(.*)("|')/,
16 | preview: /\s*preview:\s*("|')(.*)("|')/,
17 | category: /\s*category:\s*("|')(.*)("|')/,
18 | html: /\s*html:\s*(`)([\s\S]*?)(`)/gm,
19 | featured: /\s*featured:\s*(true|false)/,
20 | connections: /\s*connections:( )([\S\s]*)(,)\s*layout:/
21 | }
22 |
23 | const MASK_STRING = '***********';
24 | const MASKING_REQUIREMENTS = [
25 | 'connections|application-insights|apiKey'
26 | ];
27 |
28 | const getField = (fieldName, text) => {
29 | regExp = fields[fieldName];
30 | regExp.lastIndex = 0;
31 |
32 | const matches = regExp.exec(text);
33 | let match = matches && matches.length >= 3 && matches[2];
34 | if (!match) {
35 | match = matches && matches.length > 0 && matches[0];
36 | }
37 | return match;
38 | }
39 |
40 | const getMetadata = (text) => {
41 | const metadata = {}
42 | for (key in fields) {
43 | metadata[key] = getField(key, text);
44 | }
45 | return metadata;
46 | }
47 |
48 | module.exports = {
49 | getField,
50 | getMetadata,
51 | MASK_STRING,
52 | MASKING_REQUIREMENTS
53 | }
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | // server/index.js
2 | 'use strict';
3 |
4 | const app = require('./app');
5 |
6 | const PORT = process.env.PORT || 4000;
7 |
8 | app.listen(PORT, () => {
9 | console.log(`App listening on port ${PORT}!`);
10 | });
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibex-dashboard-server",
3 | "version": "0.1.0",
4 | "private": true,
5 | "proxy": "http://localhost:4000/",
6 | "dependencies": {
7 | "@types/lodash": "^4.14.67",
8 | "body-parser": "^1.17.1",
9 | "cookie-parser": "^1.4.3",
10 | "express": "^4.15.2",
11 | "express-session": "^1.15.2",
12 | "lodash": "^4.17.4",
13 | "morgan": "^1.8.1",
14 | "ms-rest-azure": "^2.1.2",
15 | "passport": "^0.3.2",
16 | "passport-azure-ad": "^3.0.5",
17 | "xhr-request": "^1.0.1"
18 | },
19 | "scripts": {
20 | "start": "node index.js"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/server/routes/azure.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 | const express = require('express');
4 |
5 | const msRestAzure = require('ms-rest-azure');
6 | const AzureServiceClient = msRestAzure.AzureServiceClient;
7 |
8 | const router = new express.Router();
9 |
10 | router.post('/query', (req, res) => {
11 |
12 | let { servicePrincipalId, servicePrincipalKey, servicePrincipalDomain, subscriptionId, options } = req.body || { };
13 |
14 | // Interactive Login
15 | msRestAzure.loginWithServicePrincipalSecret(servicePrincipalId, servicePrincipalKey, servicePrincipalDomain, function(err, credentials) {
16 |
17 | if (err) { return this.failure(err); }
18 |
19 | let client = new AzureServiceClient(credentials, null);
20 |
21 | options.method = options.method || 'GET';
22 | options.url = `https://management.azure.com` + options.url;
23 |
24 | return client.sendRequest(options, (err, result) => {
25 | if (err) { throw err; }
26 |
27 | let values = result.value || [];
28 | return res.json(values);
29 | });
30 | });
31 |
32 | });
33 |
34 | module.exports = {
35 | router
36 | }
--------------------------------------------------------------------------------
/server/routes/cosmos-db.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const request = require('xhr-request');
3 | const router = new express.Router();
4 | const crypto = require('crypto');
5 |
6 | router.post('/query', (req, res) => {
7 | const { host, key, verb, resourceType, databaseId, collectionId, query, parameters } = req.body;
8 |
9 | if ( !host || !key || !verb || !resourceType || !databaseId || !collectionId || !query ) {
10 | console.log('Invalid request parameters');
11 | return res.send({ error: 'Invalid request parameters' });
12 | }
13 |
14 | const date = new Date().toUTCString();
15 | const resourceLink = `dbs/${databaseId}/colls/${collectionId}`;
16 | const auth = getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, key);
17 |
18 | let cosmosQuery = {
19 | query: query,
20 | parameters: parameters || [],
21 | };
22 |
23 | const url = `https://${host}.documents.azure.com/${resourceLink}/docs`;
24 |
25 | request(url, {
26 | method: 'POST',
27 | headers: {
28 | 'Content-Type': 'application/query+json',
29 | 'Accept': 'application/json',
30 | 'Authorization': auth,
31 | 'x-ms-date': date,
32 | 'x-ms-version': '2015-04-08',
33 | 'x-ms-documentdb-isquery': true,
34 | },
35 | body: cosmosQuery,
36 | responseType: 'json',
37 | json: true,
38 | }, (err, doc) => {
39 | if (err) {
40 | console.log(err);
41 | return res.send({ error: err });
42 | }
43 | res.send(doc);
44 | });
45 |
46 | });
47 |
48 | function getAuthorizationTokenUsingMasterKey(verb, resourceType, resourceLink, date, masterKey) {
49 | var key = new Buffer(masterKey, "base64");
50 | var text = (verb || "").toLowerCase() + "\n" +
51 | (resourceType || "").toLowerCase() + "\n" +
52 | (resourceLink || "") + "\n" +
53 | date.toLowerCase() + "\n" +
54 | "" + "\n";
55 | var body = new Buffer(text, "utf8");
56 | var signature = crypto.createHmac("sha256", key).update(body).digest("base64");
57 | var MasterToken = "master";
58 | var TokenVersion = "1.0";
59 | return encodeURIComponent("type=" + MasterToken + "&ver=" + TokenVersion + "&sig=" + signature);
60 | }
61 |
62 | module.exports = {
63 | router
64 | }
--------------------------------------------------------------------------------
/server/routes/graphql.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const request = require('xhr-request');
3 |
4 | const router = new express.Router();
5 |
6 | router.post('/query', (req, res) => {
7 | const { serviceUrl, query, variables } = req.body || { };
8 |
9 | const requestOptions = {
10 | method: 'POST',
11 | json: true,
12 | body: {
13 | query: query,
14 | variables: variables
15 | },
16 | };
17 |
18 | request(serviceUrl, requestOptions, function(err, data) {
19 | if (err) { throw err; }
20 | return res.json(data);
21 | });
22 | });
23 |
24 | module.exports = {
25 | router
26 | }
--------------------------------------------------------------------------------