├── .editorconfig
├── .gitignore
├── .jshintrc
├── Dockerfile
├── LICENSE
├── README.md
├── app
├── assets
│ ├── css
│ │ └── styles.css
│ ├── fonts
│ │ ├── FontAwesome.otf
│ │ ├── fontawesome-webfont.eot
│ │ ├── fontawesome-webfont.ttf
│ │ ├── fontawesome-webfont.woff
│ │ ├── fontawesome-webfont.woff2
│ │ ├── noto-sans-700.ttf
│ │ ├── noto-sans-700italic.ttf
│ │ ├── noto-sans-italic.ttf
│ │ ├── noto-sans-regular.ttf
│ │ ├── source-code-pro-300.ttf
│ │ └── source-code-pro-500.ttf
│ ├── img
│ │ └── pev-logo.png
│ ├── sass
│ │ ├── _buttons.scss
│ │ ├── _common.scss
│ │ ├── _fonts.scss
│ │ ├── _footer.scss
│ │ ├── _forms.scss
│ │ ├── _highlight.scss
│ │ ├── _menu.scss
│ │ ├── _modal.scss
│ │ ├── _nav.scss
│ │ ├── _page.scss
│ │ ├── _plan-node.scss
│ │ ├── _plan.scss
│ │ ├── _table.scss
│ │ ├── _variables.scss
│ │ ├── font-awesome
│ │ │ ├── _animated.scss
│ │ │ ├── _bordered-pulled.scss
│ │ │ ├── _core.scss
│ │ │ ├── _fixed-width.scss
│ │ │ ├── _icons.scss
│ │ │ ├── _larger.scss
│ │ │ ├── _list.scss
│ │ │ ├── _mixins.scss
│ │ │ ├── _path.scss
│ │ │ ├── _rotated-flipped.scss
│ │ │ ├── _stacked.scss
│ │ │ ├── _styles.scss
│ │ │ └── _variables.scss
│ │ └── styles.scss
│ └── styles.css
├── bootstrap.ts
├── components
│ ├── about
│ │ ├── about.html
│ │ └── about.ts
│ ├── app
│ │ ├── app.html
│ │ └── app.ts
│ ├── plan-list
│ │ ├── plan-list.html
│ │ └── plan-list.ts
│ ├── plan-new
│ │ ├── plan-new.html
│ │ └── plan-new.ts
│ ├── plan-node
│ │ ├── plan-node.html
│ │ └── plan-node.ts
│ └── plan-view
│ │ ├── plan-view.html
│ │ └── plan-view.ts
├── enums.ts
├── index.html
├── interfaces
│ └── iplan.ts
├── pipes.ts
├── sample-plans
│ └── plan1.json
└── services
│ ├── color-service.ts
│ ├── help-service.ts
│ ├── plan-service.ts
│ └── syntax-highlight-service.ts
├── docker-compose.yml
├── gulpfile.ts
├── karma.conf.js
├── logo.ai
├── package.json
├── test-main.js
├── tools
├── config.ts
├── tasks
│ ├── build.bundles.ts
│ ├── build.deps.ts
│ ├── build.docs.ts
│ ├── build.fonts.dev.ts
│ ├── build.html_css.prod.ts
│ ├── build.img.dev.ts
│ ├── build.index.ts
│ ├── build.js.dev.ts
│ ├── build.js.prod.ts
│ ├── build.sass.dev.ts
│ ├── build.test.ts
│ ├── check.versions.ts
│ ├── clean.ts
│ ├── karma.start.ts
│ ├── npm.ts
│ ├── serve.docs.ts
│ ├── server.start.ts
│ ├── tsd.ts
│ ├── tslint.ts
│ ├── watch.dev.ts
│ ├── watch.serve.ts
│ └── watch.test.ts
├── typings
│ ├── connect-livereload.d.ts
│ ├── gulp-load-plugins.d.ts
│ ├── karma.d.ts
│ ├── merge-stream.d.ts
│ ├── open.d.ts
│ ├── run-sequence.d.ts
│ ├── slash.d.ts
│ ├── systemjs-builder.d.ts
│ ├── tiny-lr.d.ts
│ └── yargs.d.ts
├── utils.ts
└── utils
│ ├── server.ts
│ ├── tasks_tools.ts
│ ├── template-injectables.ts
│ └── template-locals.ts
├── tsconfig.json
├── tsd.json
├── tsdrc
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # Compiled binary addons (http://nodejs.org/api/addons.html)
20 | build/Release
21 |
22 | node_modules
23 | .sass-cache
24 |
25 | # Users Environment Variables
26 | .lock-wscript
27 | .tsdrc
28 |
29 | #IDE configuration files
30 | .idea
31 | .vscode
32 |
33 | dist
34 | dev
35 | docs
36 | lib
37 | test
38 | tools/typings/tsd
39 | tmp
40 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "immed": true,
4 | "newcap": true,
5 | "noarg": true,
6 | "noempty": true,
7 | "nonew": true,
8 | "trailing": true,
9 | "maxlen": 200,
10 | "boss": true,
11 | "eqnull": true,
12 | "expr": true,
13 | "globalstrict": true,
14 | "laxbreak": true,
15 | "loopfunc": true,
16 | "sub": true,
17 | "undef": true,
18 | "indent": 2,
19 | "unused": true,
20 |
21 | "node": true,
22 | "globals": {
23 | "System": true
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM debian:jessie
2 |
3 | RUN apt-get update && apt-get -y install apt-transport-https curl g++ make python ruby
4 |
5 | RUN curl -sL https://deb.nodesource.com/setup_4.x | bash -
6 |
7 | RUN apt-get -y install nodejs
8 |
9 | COPY tsdrc ~/.tsdrc
10 |
11 | COPY . /var/www/pev
12 |
13 | WORKDIR /var/www/pev
14 |
15 | RUN npm install -g gulp
16 |
17 | RUN npm install
18 |
19 | CMD cd /var/www/pev && npm start
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Alex Tatiyants
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 NON-INFRINGEMENT. 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Postgres Explain Visualizer (pev)
2 |
3 | Postgres Explain Visualizer (dev) is designed to make [EXPLAIN](http://www.postgresql.org/docs/current/static/sql-explain.html) output easier to grok. It creates a graphical representation of the plan. You can see it in action at [tatiyants.com/pev](http://tatiyants.com/pev/), or read about it [on my blog](http://tatiyants.com/postgres-query-plan-visualization/).
4 |
5 | Pev is heavily influenced by the excellent [explain.depesz.com](http://explain.depesz.com/).
6 |
7 | Pev is written in [angular 2](https://angular.io/) with [TypeScript](http://www.typescriptlang.org/). The project is based on [angular2 seed](https://github.com/mgechev/angular2-seed). It requires [npm](https://www.npmjs.com/), [gulp](http://gulpjs.com/), [tsd](http://definitelytyped.org/tsd/), and [compass](http://compass-style.org/).
8 |
9 |
10 | ## Installation
11 |
12 | ```
13 | npm install
14 | npm start
15 | ```
16 |
17 | You may also need to install tsd and compass:
18 |
19 | ```
20 | npm install tsd -g
21 | gem install compass
22 | ```
23 |
24 | ## Build
25 | To build, run the build command for a specific environment. For example, the following will create a production distribution:
26 |
27 | ```
28 | npm start build.prod
29 | ```
30 |
--------------------------------------------------------------------------------
/app/assets/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/app/assets/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/app/assets/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/app/assets/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/app/assets/fonts/noto-sans-700.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/noto-sans-700.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/noto-sans-700italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/noto-sans-700italic.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/noto-sans-italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/noto-sans-italic.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/noto-sans-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/noto-sans-regular.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/source-code-pro-300.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/source-code-pro-300.ttf
--------------------------------------------------------------------------------
/app/assets/fonts/source-code-pro-500.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/fonts/source-code-pro-500.ttf
--------------------------------------------------------------------------------
/app/assets/img/pev-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/app/assets/img/pev-logo.png
--------------------------------------------------------------------------------
/app/assets/sass/_buttons.scss:
--------------------------------------------------------------------------------
1 | .btn {
2 | border-radius: $border-radius-base;
3 | padding: $padding-base $padding-lg;
4 | font-size: $font-size-base;
5 | line-height: 1.2;
6 | text-decoration: none;
7 | text-transform: uppercase;
8 | cursor: pointer;
9 | margin-left: $padding-base;
10 |
11 | &-default {
12 | border: 1px solid $blue;
13 | color: $blue;
14 | background-color: #fff;
15 |
16 | &:hover {
17 | background-color: $light-blue;
18 | color: #fff;
19 | }
20 | }
21 |
22 | &-lg {
23 | padding: $padding-base $padding-lg*2;
24 | font-size: round($font-size-base * 1.2);
25 | }
26 |
27 | &-sm {
28 | padding: $padding-base $padding-base;
29 | font-size: round($font-size-base * 0.9);
30 | }
31 |
32 | &-slim {
33 | .fa { margin: 0};
34 | }
35 |
36 | &-link {
37 | border: 0;
38 | background-color: transparent;
39 | color: $link-color;
40 | padding: 0;
41 | }
42 |
43 | &-danger {
44 | border: 1px solid $red;
45 | color: $red;
46 | text-transform: uppercase;
47 | background-color: #fff;
48 |
49 | &:hover {
50 | background-color: $light-red;
51 | }
52 | }
53 |
54 | &-primary {
55 | border: 0;
56 | background: $blue;
57 | box-shadow: 1px 1px 1px $gray;
58 | color: #ffffff;
59 |
60 | &:hover {
61 | background: $dark-blue;
62 | }
63 | }
64 |
65 | &-close {
66 | @extend .btn-slim;
67 | border-radius: 50%;
68 | box-shadow: 0;
69 | line-height: 1.5;
70 | border: 1px solid $line-color;
71 | background-color: #fff;
72 | }
73 | }
74 |
75 | .button-group {
76 | button {
77 | margin: 0;
78 | background-color: $gray-lightest;
79 | border-radius: 0;
80 | float: left;
81 | border: 1px solid $line-color;
82 | cursor: pointer;
83 |
84 | &:first-of-type {
85 | border-top-left-radius: $border-radius-lg;
86 | border-bottom-left-radius: $border-radius-lg;
87 | }
88 | &:last-of-type {
89 | border-top-right-radius: $border-radius-lg;
90 | border-bottom-right-radius: $border-radius-lg;
91 | }
92 | }
93 |
94 | .selected {
95 | background-color: $gray-light;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/assets/sass/_common.scss:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | }
4 |
5 | body {
6 | font-size: $font-size-base;
7 | font-weight: 300;
8 | color: $text-color;
9 | height: 100%;
10 | width: 100%;
11 | background-color: $bg-color;
12 | line-height: $line-height-base;
13 | }
14 |
15 | strong {
16 | font-weight: 600;
17 | }
18 |
19 | body, input, a, button, textarea {
20 | font-family: $font-family-sans-serif;
21 | font-weight: 300;
22 | }
23 |
24 | .text-muted {
25 | color: $text-color-light;
26 | }
27 |
28 | .text-warning {
29 | color: $alert-color;
30 | }
31 |
32 | .hero-container {
33 | margin: $padding-lg * 3;
34 | font-size: $font-size-xl;
35 | text-align: center;
36 | }
37 |
38 | .pull-right {
39 | float: right;
40 | }
41 |
42 | .align-right {
43 | text-align: right;
44 | }
45 |
46 | a {
47 | color: $link-color;
48 | text-decoration: none;
49 | }
50 |
51 | .fa {
52 | margin-right: $padding-sm;
53 | }
54 |
55 | .clickable {
56 | cursor: pointer;
57 | }
58 |
59 | code, .code {
60 | font-family: $font-family-mono;
61 | font-weight: 600;
62 | }
63 |
64 | .pad-left {
65 | margin-left: $padding-lg;
66 | }
67 |
68 | .pad-top {
69 | margin-top: $padding-lg;
70 | }
71 |
72 | .pad-bottom {
73 | margin-bottom: $padding-lg;
74 | }
75 |
76 | [tooltip]:before {
77 | width: 150px;
78 | text-transform: none;
79 | text-align: left;
80 | content: attr(tooltip);
81 | position: absolute;
82 | opacity: 0;
83 | transition: all 0.15s ease;
84 | padding: $padding-lg;
85 | color: #fff;
86 | border-radius: $border-radius-lg;
87 | margin-top: -10px;
88 | margin-left: 20px;
89 | z-index: 5;
90 | pointer-events: none;
91 | }
92 |
93 | [tooltip]:hover:before {
94 | opacity: 1;
95 | background: $dark-blue;
96 | }
97 |
--------------------------------------------------------------------------------
/app/assets/sass/_fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'noto';
3 | src: url('../fonts/noto-sans-regular.ttf') format('truetype');
4 | font-weight: 300;
5 | font-style: normal;
6 | }
7 |
8 | @font-face {
9 | font-family: 'noto';
10 | src: url('../fonts/noto-sans-italic.ttf') format('truetype');
11 | font-weight: 300;
12 | font-style: italic;
13 | }
14 |
15 | @font-face {
16 | font-family: 'noto';
17 | src: url('../fonts/noto-sans-700.ttf') format('truetype');
18 | font-weight: 600;
19 | font-style: normal;
20 | }
21 |
22 | @font-face {
23 | font-family: 'noto';
24 | src: url('../fonts/noto-sans-700italic.ttf') format('truetype');
25 | font-weight: 600;
26 | font-style: italic;
27 | }
28 |
29 | @font-face {
30 | font-family: 'source code';
31 | src: url('../fonts/source-code-pro-300.ttf') format('truetype');
32 | font-weight: 300;
33 | font-style: normal;
34 | }
35 |
36 | @font-face {
37 | font-family: 'source code';
38 | src: url('../fonts/source-code-pro-500.ttf') format('truetype');
39 | font-weight: 600;
40 | font-style: normal;
41 | }
42 |
--------------------------------------------------------------------------------
/app/assets/sass/_footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | border-top: 2px solid $gray-light;
3 | margin: round($padding-lg * 2);
4 | padding: round($padding-lg * 2);
5 | color: $gray;
6 | text-align: center;
7 | width: 600px;
8 | margin: auto;
9 |
10 | .fa {
11 | font-size: $font-size-lg;
12 | margin-left: $padding-base;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/app/assets/sass/_forms.scss:
--------------------------------------------------------------------------------
1 | @import "compass/css3/user-interface";
2 |
3 | .input-box {
4 | @include input-placeholder {
5 | color: $gray;
6 | font-size: round($font-size-base * 1.4);
7 | }
8 |
9 | &:focus {
10 | box-shadow: 0 0 5px rgba(81, 203, 238, 1);
11 | }
12 |
13 | &-main {
14 | font-size: round($font-size-base * 1.4);
15 | width: 700px;
16 | border: 0;
17 | border-bottom: 2px solid $blue;
18 | padding: $padding-lg;
19 | margin-top: $padding-lg;
20 | margin-bottom: $padding-lg;
21 | }
22 |
23 | &-lg {
24 | width: 98%;
25 | height: 210px;
26 | margin-bottom: $padding-base;
27 | margin-bottom: $padding-lg;
28 | border-radius: $border-radius-base;
29 | border: 1px solid $line-color;
30 | padding: $padding-lg;
31 | }
32 | }
33 |
34 | .error-message {
35 | background-color: $alert-color;
36 | padding: $padding-base;
37 | color: #fff;
38 | }
39 |
--------------------------------------------------------------------------------
/app/assets/sass/_highlight.scss:
--------------------------------------------------------------------------------
1 | .plan-query-container {
2 | border: 1px solid $line-color;
3 | padding: $padding-xl;
4 | background-color: #fff;
5 | position: absolute;
6 | box-shadow: 0px 0px 10px 2px rgba(0,0,0,0.3);
7 | border-radius: $border-radius-base;
8 | margin-bottom: $padding-xl;
9 | z-index: 6;
10 | left: 0;
11 |
12 | code {
13 | font-weight: 300;
14 | }
15 |
16 | .plan-query-text {
17 | background-color: #fff;
18 | font-family: $font-family-mono;
19 | text-align: left;
20 |
21 | .code-key-item {
22 | background-color: $yellow;
23 | font-weight: 600;
24 | padding: 1px;
25 | }
26 | }
27 |
28 | h3 {
29 | font-size: $font-size-lg;
30 | width: 93%;
31 | text-align: left;
32 | border-bottom: 1px solid $line-color;
33 | padding-bottom: $padding-base;
34 | margin-bottom: $padding-lg;
35 | }
36 | }
37 |
38 | .hljs, .hljs-subst {
39 | color: $text-color;
40 | }
41 |
42 | .hljs-keyword,
43 | .hljs-attribute,
44 | .hljs-selector-tag,
45 | .hljs-meta-keyword,
46 | .hljs-doctag,
47 | .hljs-name {
48 | color: $dark-blue;
49 | font-weight: bold;
50 | }
51 |
52 | .hljs-built_in,
53 | .hljs-literal,
54 | .hljs-bullet,
55 | .hljs-code,
56 | .hljs-addition {
57 | color: $dark-blue;
58 | font-weight: bold;
59 | }
60 |
61 | .hljs-regexp,
62 | .hljs-symbol,
63 | .hljs-variable,
64 | .hljs-template-variable,
65 | .hljs-link,
66 | .hljs-selector-attr,
67 | .hljs-selector-pseudo {
68 | color: $dark-blue;
69 | font-weight: bold;
70 | }
71 |
72 | .hljs-type,
73 | .hljs-string,
74 | .hljs-number,
75 | .hljs-selector-id,
76 | .hljs-selector-class,
77 | .hljs-quote,
78 | .hljs-template-tag,
79 | .hljs-deletion {
80 | color: $green;
81 | font-weight: 600;
82 | }
83 |
84 | .hljs-title,
85 | .hljs-section {
86 | color: $dark-blue;
87 | font-weight: bold;
88 | }
89 |
90 | .hljs-comment {
91 | color: $text-color-light;
92 | font-style: italic;
93 | }
94 |
95 | .hljs-meta {
96 | color: $text-color-light;
97 | }
98 |
99 | .hljs-emphasis {
100 | font-style: italic;
101 | }
102 |
103 | .hljs-strong {
104 | font-weight: 600;
105 | }
106 |
--------------------------------------------------------------------------------
/app/assets/sass/_menu.scss:
--------------------------------------------------------------------------------
1 | $menu-offset: 120px;
2 | $menu-toggle-height: 45px;
3 |
4 | .menu {
5 | width: 190px;
6 | height: 260px;
7 | position: absolute;
8 | font-size: $font-size-sm;
9 | top: $menu-offset - 5;
10 | left: 0;
11 | background-color: $gray-dark;
12 | box-shadow: 1px 1px 2px 1px rgba(0,0,0,0.2);
13 | color: #fff;
14 | border-top-right-radius: $border-radius-base;
15 | border-bottom-right-radius: $border-radius-base;
16 | z-index: 1;
17 |
18 | header {
19 | h3 {
20 | padding-top: $padding-lg;
21 | margin-bottom: $padding-base;
22 | font-size: round($font-size-base * 1.2);
23 | font-weight: 600;
24 | line-height: 2;
25 | text-align: right;
26 | padding-right: $padding-lg * 2;
27 | }
28 | }
29 |
30 | ul {
31 | margin-left: $padding-lg;
32 |
33 | li {
34 | line-height: 2.5;
35 | }
36 | }
37 |
38 | &-toggle {
39 | font-size: round($font-size-lg * 1.3);
40 | float: left;
41 | padding-left: $padding-base;
42 | line-height: 2;
43 | cursor: pointer;
44 | }
45 |
46 | &-hidden {
47 | width: $menu-toggle-height;
48 | height: $menu-toggle-height;
49 | border-top-right-radius: 50%;
50 | border-bottom-right-radius: 50%;
51 |
52 | ul, h3 {
53 | visibility: hidden;
54 | }
55 | }
56 |
57 | .button-group {
58 | display: flex;
59 | margin-bottom: $padding-base;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/assets/sass/_modal.scss:
--------------------------------------------------------------------------------
1 | .modal-backdrop {
2 | position: fixed;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | left: 0;
7 | z-index: 1040;
8 | background-color: #000;
9 | opacity: 0.1;
10 | }
11 |
12 | .modal {
13 | position: fixed;
14 | top: 0;
15 | right: 0;
16 | bottom: 0;
17 | left: 0;
18 | z-index: 1050;
19 | transition: all 1s;
20 |
21 | .modal-dialog {
22 | position: relative;
23 | transform: translate(0);
24 | margin: 100px auto;
25 | width: 500px;
26 | opacity: 1;
27 |
28 | .modal-content {
29 | padding: 30px;
30 | position: relative;
31 | background-color: #fff;
32 | background-clip: padding-box;
33 | border: 1px solid #999;
34 | border: 1px solid rgba(0,0,0,.2);
35 | border-radius: 6px;
36 | outline: 0;
37 | box-shadow: 0 2px 2px rgba(0,0,0,.5);
38 | display: block;
39 |
40 | .modal-body {
41 | padding: $padding-sm;
42 | margin-bottom: $padding-xl;
43 | text-align: left;
44 | line-height: 1.5;
45 | }
46 |
47 | .modal-footer {
48 | text-align: right;
49 |
50 | button {
51 | margin-left: $padding-sm;
52 | }
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/assets/sass/_nav.scss:
--------------------------------------------------------------------------------
1 | nav {
2 | font-size: round($font-size-base * 1.3);
3 | background-color: #fff;
4 | padding: round($padding-lg * 1.5);
5 |
6 | .nav-container {
7 | width: $page-width;
8 | margin: auto;
9 | }
10 |
11 | .about {
12 | float: right;
13 | line-height: 2;
14 | margin-left: $padding-xl;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/assets/sass/_page.scss:
--------------------------------------------------------------------------------
1 | .page {
2 | padding-top: $padding-lg;
3 | margin: auto;
4 | width: $page-width;
5 | min-height: 600px;
6 |
7 | em {
8 | font-style: italic;
9 | }
10 | }
11 |
12 | .page-content {
13 | h2 {
14 | font-size: round($font-size-base * 2);
15 | line-height: 2;
16 | }
17 | h3 {
18 | font-size: round($font-size-base * 1.7);
19 | margin-top: $padding-xl;
20 | line-height: 2;
21 | }
22 |
23 | p, ul {
24 | font-size: round($font-size-base * 1.2);
25 | line-height: 1.5;
26 | margin-bottom: $padding-xl;
27 | }
28 |
29 | ul {
30 | list-style-type: disc;
31 | position: relative;
32 | left: $padding-xl;
33 | }
34 | }
35 |
36 | .page-stretch {
37 | @extend .page;
38 | margin: auto;
39 | width: 95%;
40 |
41 | h2 {
42 | text-align: left;
43 | font-size: $font-size-lg;
44 | max-width: $page-width;
45 | margin: auto;
46 | margin-bottom: $padding-lg*2;
47 |
48 | .sub-title {
49 | font-size: $font-size-base;
50 | font-style: italic;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/assets/sass/_plan-node.scss:
--------------------------------------------------------------------------------
1 | .plan-node {
2 | text-decoration: none;
3 | color: $text-color;
4 | display: inline-block;
5 | transition: all 0.1s;
6 | position: relative;
7 | padding: $padding-base $padding-lg;
8 | background-color: #fff;
9 | font-size: $font-size-sm;
10 | border: 1px solid $line-color;
11 | margin-bottom: 4px;
12 | border-radius: $border-radius-base;
13 | overflow-wrap: break-word;
14 | word-wrap: break-word;
15 | word-break: break-all;
16 | width: 240px;
17 | box-shadow: 1px 1px 3px 0px rgba(0,0,0,0.1);
18 |
19 | header {
20 | margin-bottom: $padding-base;
21 | overflow: hidden;
22 | cursor: pointer;
23 |
24 | &:hover {
25 | background-color: $gray-lightest;
26 | }
27 |
28 | h4 {
29 | font-size: $font-size-base;
30 | float: left;
31 | font-weight: 600;
32 | }
33 |
34 | .node-duration {
35 | float: right;
36 | margin-left: $padding-lg;
37 | font-size: $font-size-base;
38 | }
39 | }
40 |
41 | .prop-list {
42 | float: left;
43 | text-align: left;
44 | overflow-wrap: break-word;
45 | word-wrap: break-word;
46 | word-break: break-all;
47 | margin-top: $padding-lg;
48 | margin-bottom: $padding-base;
49 | }
50 |
51 | .relation-name {
52 | text-align: left;
53 | }
54 |
55 | .planner-estimate {
56 | border-top: 1px solid $line-color;
57 | text-align: left;
58 | padding-top: $padding-sm;
59 | margin-top: $padding-base;
60 | width: 100%;
61 | }
62 |
63 | .tags {
64 | margin-top: $padding-base;
65 | text-align: left;
66 |
67 | span {
68 | display: inline-block;
69 | background-color: $alert-color;
70 | color: #fff;
71 | font-size: round($font-size-sm * 0.8);
72 | font-weight: 600;
73 | margin-right: $padding-sm;
74 | margin-bottom: $padding-sm;
75 | padding: $padding-sm;
76 | border-radius: $border-radius-base;
77 | line-height: 1.1;
78 | }
79 | }
80 |
81 | //hovers
82 | &:hover {
83 | border-color: $highlight-color;
84 | }
85 |
86 | .node-description {
87 | text-align: left;
88 | font-style: italic;
89 | padding-top: $padding-lg;
90 | word-break: normal;
91 |
92 | .node-type {
93 | font-weight: 600;
94 | background-color: $blue;
95 | color: #fff;
96 | padding: 0 $padding-base;
97 | }
98 | }
99 |
100 | .btn-default {
101 | border: 0;
102 | }
103 | }
104 |
105 | .node-bar-container {
106 | height: 5px;
107 | margin-top: $padding-lg;
108 | margin-bottom: $padding-sm;
109 | border-radius: $border-radius-lg;
110 | background-color: $gray-light;
111 | position: relative;
112 |
113 | .node-bar {
114 | border-radius: $border-radius-lg;
115 | height: 100%;
116 | text-align: left;
117 | position: absolute;
118 | left: 0;
119 | top: 0;
120 | }
121 | }
122 |
123 | .node-bar-label {
124 | text-align: left;
125 | display: block;
126 | }
127 |
128 | .expanded {
129 | width: 400px !important;
130 | overflow: visible !important;
131 | padding: $padding-base $padding-lg !important;
132 | .tags span {
133 | margin-right: $padding-sm !important;
134 | }
135 | }
136 |
137 | .compact {
138 | width: 140px;
139 | }
140 |
141 | .dot {
142 | width: 30px;
143 | overflow: hidden;
144 | padding: $padding-sm;
145 |
146 | .tags span {
147 | margin-right: 1px;
148 | }
149 |
150 | .node-bar-container {
151 | margin-top: $padding-sm;
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/app/assets/sass/_plan.scss:
--------------------------------------------------------------------------------
1 | $connector-height: 12px;
2 | $connector-line: 2px solid darken($line-color, 10%);
3 |
4 | .plan {
5 | padding-bottom: $padding-lg * 3;
6 | margin-left: 100px;
7 |
8 | ul {
9 | display: flex;
10 | padding-top: $connector-height;
11 | position: relative;
12 | margin: auto;
13 | transition: all 0.5s;
14 | margin-top: -5px;
15 |
16 | // vertical connector
17 | ul::before {
18 | content: '';
19 | position: absolute; top: 0; left: 50%;
20 | border-left: $connector-line;
21 | height: $connector-height;
22 | width: 0;
23 | }
24 |
25 | li {
26 | float: left; text-align: center;
27 | list-style-type: none;
28 | position: relative;
29 | padding: $connector-height $padding-sm 0 $padding-sm;
30 | transition: all 0.5s;
31 |
32 | // connectors
33 | &:before, &:after {
34 | content: '';
35 | position: absolute; top: 0; right: 50%;
36 | border-top: $connector-line;
37 | width: 50%; height: $connector-height;
38 | }
39 |
40 | &:after {
41 | right: auto; left: 50%;
42 | border-left: $connector-line;
43 | }
44 |
45 | &:only-child {
46 | padding-top: 0;
47 | &:after, &:before {
48 | display: none;
49 | }
50 | }
51 |
52 | &:first-child::before, &:last-child::after {
53 | border: 0 none;
54 | }
55 |
56 | &:last-child::before {
57 | border-right: $connector-line;
58 | border-radius: 0 $border-radius-lg 0 0;
59 | }
60 |
61 | &:first-child::after {
62 | border-radius: $border-radius-lg 0 0 0;
63 | }
64 |
65 | //hovers
66 | .plan-node:hover+ul::before {
67 | border-color: $highlight-color;
68 | }
69 |
70 | .plan-node:hover+ul li::after,
71 | .plan-node:hover+ul li::before,
72 | .plan-node:hover+ul ul::before{
73 | border-color: $highlight-color-dark;
74 | }
75 | }
76 | }
77 | }
78 |
79 | .plan-stats {
80 | display: flex;
81 | font-size: $font-size-base;
82 | margin: $padding-lg auto $padding-lg auto;
83 | padding-bottom: $padding-lg;
84 | border-bottom: 1px solid $line-color;
85 | border-radius: $border-radius-lg*2;
86 | width: 650px;
87 | position: relative;
88 |
89 | div {
90 | padding-right: $padding-lg;
91 | flex-grow: 1;
92 | }
93 |
94 | .stat-value {
95 | display: block;
96 | text-align: center;
97 | font-size: $font-size-lg;
98 | }
99 |
100 | .stat-label {
101 | display: block;
102 | text-align: center;
103 | font-size: $font-size-sm;
104 | }
105 |
106 | $triangle-size: 9px;
107 | &:after {
108 | content:'';
109 | position: absolute;
110 | top: 100%;
111 | left: 50%;
112 | margin-left: $triangle-size*-1;
113 | width: 0;
114 | height: 0;
115 | border-top: solid $triangle-size $line-color;
116 | border-left: solid $triangle-size transparent;
117 | border-right: solid $triangle-size transparent;
118 | }
119 |
120 | .btn-close {
121 | padding: $padding-base;
122 | background-color: transparent;
123 | font-size: $font-size-lg;
124 | text-align: center;
125 | @extend .text-muted;
126 | margin-left: $padding-base;
127 | cursor: pointer;
128 | border: 0;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/app/assets/sass/_table.scss:
--------------------------------------------------------------------------------
1 | .table {
2 | width: 100%;
3 |
4 | td {
5 | border-bottom: 1px solid $line-color;
6 | padding: $padding-base;
7 | }
8 |
9 | tr:hover {
10 | background-color: $gray-lightest;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/app/assets/sass/_variables.scss:
--------------------------------------------------------------------------------
1 | //vars
2 | $page-width: 1000px;
3 |
4 | $padding-base: 6px;
5 | $padding-sm: 3px;
6 | $padding-lg: 10px;
7 | $padding-xl: 18px;
8 |
9 | $font-size-base: 13px;
10 | $font-size-xs: round($font-size-base * 0.7);
11 | $font-size-sm: round($font-size-base * 0.9);
12 | $font-size-lg: round($font-size-base * 1.3);
13 | $font-size-xl: round($font-size-base * 1.7);
14 |
15 | $font-family-sans-serif: 'noto';
16 | $font-family-mono: 'source code';
17 |
18 | $line-height-base: 1.3;
19 |
20 | $gray-lightest: #f7f7f7;
21 | $gray-light: darken($gray-lightest, 10%);
22 | $gray: darken(#f7f7f7, 30%);
23 | $gray-dark: darken(#f7f7f7, 50%);
24 | $gray-darkest: darken($gray-lightest, 70%);
25 |
26 | $blue: #00B5E2;
27 | $dark-blue: #008CAF;
28 | $light-blue: #65DDFB;
29 |
30 | $red: #AF2F11;
31 | $dark-red: #7C210C;
32 | $light-red: #FB8165;
33 |
34 | $green: #279404;
35 | $yellow: #F8E400;
36 |
37 | $bg-color: $gray-lightest;
38 |
39 | $text-color: #4d525a;
40 | $text-color-light: lighten($text-color, 30%);
41 |
42 | $line-color: $gray-light;
43 | $line-color-light: lighten($gray-light, 10%);
44 |
45 | $link-color: $blue;
46 |
47 | $border-radius-base: 3px;
48 | $border-radius-lg: 6px;
49 |
50 | $main-color: $blue;
51 | $main-color-dark: $blue;
52 |
53 | $highlight-color: $blue;
54 | $highlight-color-dark: $dark-blue;
55 |
56 | $alert-color: #FB4418;
57 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_animated.scss:
--------------------------------------------------------------------------------
1 | // Spinning Icons
2 | // --------------------------
3 |
4 | .#{$fa-css-prefix}-spin {
5 | -webkit-animation: fa-spin 2s infinite linear;
6 | animation: fa-spin 2s infinite linear;
7 | }
8 |
9 | .#{$fa-css-prefix}-pulse {
10 | -webkit-animation: fa-spin 1s infinite steps(8);
11 | animation: fa-spin 1s infinite steps(8);
12 | }
13 |
14 | @-webkit-keyframes fa-spin {
15 | 0% {
16 | -webkit-transform: rotate(0deg);
17 | transform: rotate(0deg);
18 | }
19 | 100% {
20 | -webkit-transform: rotate(359deg);
21 | transform: rotate(359deg);
22 | }
23 | }
24 |
25 | @keyframes fa-spin {
26 | 0% {
27 | -webkit-transform: rotate(0deg);
28 | transform: rotate(0deg);
29 | }
30 | 100% {
31 | -webkit-transform: rotate(359deg);
32 | transform: rotate(359deg);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_bordered-pulled.scss:
--------------------------------------------------------------------------------
1 | // Bordered & Pulled
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-border {
5 | padding: .2em .25em .15em;
6 | border: solid .08em $fa-border-color;
7 | border-radius: .1em;
8 | }
9 |
10 | .#{$fa-css-prefix}-pull-left { float: left; }
11 | .#{$fa-css-prefix}-pull-right { float: right; }
12 |
13 | .#{$fa-css-prefix} {
14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; }
15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; }
16 | }
17 |
18 | /* Deprecated as of 4.4.0 */
19 | .pull-right { float: right; }
20 | .pull-left { float: left; }
21 |
22 | .#{$fa-css-prefix} {
23 | &.pull-left { margin-right: .3em; }
24 | &.pull-right { margin-left: .3em; }
25 | }
26 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_core.scss:
--------------------------------------------------------------------------------
1 | // Base Class Definition
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix} {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_fixed-width.scss:
--------------------------------------------------------------------------------
1 | // Fixed Width Icons
2 | // -------------------------
3 | .#{$fa-css-prefix}-fw {
4 | width: (18em / 14);
5 | text-align: center;
6 | }
7 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_larger.scss:
--------------------------------------------------------------------------------
1 | // Icon Sizes
2 | // -------------------------
3 |
4 | /* makes the font 33% larger relative to the icon container */
5 | .#{$fa-css-prefix}-lg {
6 | font-size: (4em / 3);
7 | line-height: (3em / 4);
8 | vertical-align: -15%;
9 | }
10 | .#{$fa-css-prefix}-2x { font-size: 2em; }
11 | .#{$fa-css-prefix}-3x { font-size: 3em; }
12 | .#{$fa-css-prefix}-4x { font-size: 4em; }
13 | .#{$fa-css-prefix}-5x { font-size: 5em; }
14 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_list.scss:
--------------------------------------------------------------------------------
1 | // List Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-ul {
5 | padding-left: 0;
6 | margin-left: $fa-li-width;
7 | list-style-type: none;
8 | > li { position: relative; }
9 | }
10 | .#{$fa-css-prefix}-li {
11 | position: absolute;
12 | left: -$fa-li-width;
13 | width: $fa-li-width;
14 | top: (2em / 14);
15 | text-align: center;
16 | &.#{$fa-css-prefix}-lg {
17 | left: -$fa-li-width + (4em / 14);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // --------------------------
3 |
4 | @mixin fa-icon() {
5 | display: inline-block;
6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration
7 | font-size: inherit; // can't have font-size inherit on line above, so need to override
8 | text-rendering: auto; // optimizelegibility throws things off #1094
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | @mixin fa-icon-rotate($degrees, $rotation) {
15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
16 | -webkit-transform: rotate($degrees);
17 | -ms-transform: rotate($degrees);
18 | transform: rotate($degrees);
19 | }
20 |
21 | @mixin fa-icon-flip($horiz, $vert, $rotation) {
22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation});
23 | -webkit-transform: scale($horiz, $vert);
24 | -ms-transform: scale($horiz, $vert);
25 | transform: scale($horiz, $vert);
26 | }
27 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_path.scss:
--------------------------------------------------------------------------------
1 | /* FONT PATH
2 | * -------------------------- */
3 |
4 | @font-face {
5 | font-family: 'FontAwesome';
6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}');
7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'),
8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'),
9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'),
10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'),
11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg');
12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_rotated-flipped.scss:
--------------------------------------------------------------------------------
1 | // Rotated & Flipped Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); }
5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); }
6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); }
7 |
8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); }
9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); }
10 |
11 | // Hook for IE8-9
12 | // -------------------------
13 |
14 | :root .#{$fa-css-prefix}-rotate-90,
15 | :root .#{$fa-css-prefix}-rotate-180,
16 | :root .#{$fa-css-prefix}-rotate-270,
17 | :root .#{$fa-css-prefix}-flip-horizontal,
18 | :root .#{$fa-css-prefix}-flip-vertical {
19 | filter: none;
20 | }
21 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_stacked.scss:
--------------------------------------------------------------------------------
1 | // Stacked Icons
2 | // -------------------------
3 |
4 | .#{$fa-css-prefix}-stack {
5 | position: relative;
6 | display: inline-block;
7 | width: 2em;
8 | height: 2em;
9 | line-height: 2em;
10 | vertical-align: middle;
11 | }
12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x {
13 | position: absolute;
14 | left: 0;
15 | width: 100%;
16 | text-align: center;
17 | }
18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; }
19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; }
20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; }
21 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_styles.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome
3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
4 | */
5 |
6 | @import "variables";
7 | @import "mixins";
8 | @import "path";
9 | @import "core";
10 | @import "larger";
11 | @import "fixed-width";
12 | @import "list";
13 | @import "bordered-pulled";
14 | @import "animated";
15 | @import "rotated-flipped";
16 | @import "stacked";
17 | @import "icons";
18 |
--------------------------------------------------------------------------------
/app/assets/sass/font-awesome/_variables.scss:
--------------------------------------------------------------------------------
1 | // Variables
2 | // --------------------------
3 |
4 | $fa-font-path: "../fonts" !default;
5 | $fa-font-size-base: 14px !default;
6 | $fa-line-height-base: 1 !default;
7 | //$fa-font-path: "//netdna.bootstrapcdn.com/font-awesome/4.5.0/fonts" !default; // for referencing Bootstrap CDN font files directly
8 | $fa-css-prefix: fa !default;
9 | $fa-version: "4.5.0" !default;
10 | $fa-border-color: #eee !default;
11 | $fa-inverse: #fff !default;
12 | $fa-li-width: (30em / 14) !default;
13 |
14 | $fa-var-500px: "\f26e";
15 | $fa-var-adjust: "\f042";
16 | $fa-var-adn: "\f170";
17 | $fa-var-align-center: "\f037";
18 | $fa-var-align-justify: "\f039";
19 | $fa-var-align-left: "\f036";
20 | $fa-var-align-right: "\f038";
21 | $fa-var-amazon: "\f270";
22 | $fa-var-ambulance: "\f0f9";
23 | $fa-var-anchor: "\f13d";
24 | $fa-var-android: "\f17b";
25 | $fa-var-angellist: "\f209";
26 | $fa-var-angle-double-down: "\f103";
27 | $fa-var-angle-double-left: "\f100";
28 | $fa-var-angle-double-right: "\f101";
29 | $fa-var-angle-double-up: "\f102";
30 | $fa-var-angle-down: "\f107";
31 | $fa-var-angle-left: "\f104";
32 | $fa-var-angle-right: "\f105";
33 | $fa-var-angle-up: "\f106";
34 | $fa-var-apple: "\f179";
35 | $fa-var-archive: "\f187";
36 | $fa-var-area-chart: "\f1fe";
37 | $fa-var-arrow-circle-down: "\f0ab";
38 | $fa-var-arrow-circle-left: "\f0a8";
39 | $fa-var-arrow-circle-o-down: "\f01a";
40 | $fa-var-arrow-circle-o-left: "\f190";
41 | $fa-var-arrow-circle-o-right: "\f18e";
42 | $fa-var-arrow-circle-o-up: "\f01b";
43 | $fa-var-arrow-circle-right: "\f0a9";
44 | $fa-var-arrow-circle-up: "\f0aa";
45 | $fa-var-arrow-down: "\f063";
46 | $fa-var-arrow-left: "\f060";
47 | $fa-var-arrow-right: "\f061";
48 | $fa-var-arrow-up: "\f062";
49 | $fa-var-arrows: "\f047";
50 | $fa-var-arrows-alt: "\f0b2";
51 | $fa-var-arrows-h: "\f07e";
52 | $fa-var-arrows-v: "\f07d";
53 | $fa-var-asterisk: "\f069";
54 | $fa-var-at: "\f1fa";
55 | $fa-var-automobile: "\f1b9";
56 | $fa-var-backward: "\f04a";
57 | $fa-var-balance-scale: "\f24e";
58 | $fa-var-ban: "\f05e";
59 | $fa-var-bank: "\f19c";
60 | $fa-var-bar-chart: "\f080";
61 | $fa-var-bar-chart-o: "\f080";
62 | $fa-var-barcode: "\f02a";
63 | $fa-var-bars: "\f0c9";
64 | $fa-var-battery-0: "\f244";
65 | $fa-var-battery-1: "\f243";
66 | $fa-var-battery-2: "\f242";
67 | $fa-var-battery-3: "\f241";
68 | $fa-var-battery-4: "\f240";
69 | $fa-var-battery-empty: "\f244";
70 | $fa-var-battery-full: "\f240";
71 | $fa-var-battery-half: "\f242";
72 | $fa-var-battery-quarter: "\f243";
73 | $fa-var-battery-three-quarters: "\f241";
74 | $fa-var-bed: "\f236";
75 | $fa-var-beer: "\f0fc";
76 | $fa-var-behance: "\f1b4";
77 | $fa-var-behance-square: "\f1b5";
78 | $fa-var-bell: "\f0f3";
79 | $fa-var-bell-o: "\f0a2";
80 | $fa-var-bell-slash: "\f1f6";
81 | $fa-var-bell-slash-o: "\f1f7";
82 | $fa-var-bicycle: "\f206";
83 | $fa-var-binoculars: "\f1e5";
84 | $fa-var-birthday-cake: "\f1fd";
85 | $fa-var-bitbucket: "\f171";
86 | $fa-var-bitbucket-square: "\f172";
87 | $fa-var-bitcoin: "\f15a";
88 | $fa-var-black-tie: "\f27e";
89 | $fa-var-bluetooth: "\f293";
90 | $fa-var-bluetooth-b: "\f294";
91 | $fa-var-bold: "\f032";
92 | $fa-var-bolt: "\f0e7";
93 | $fa-var-bomb: "\f1e2";
94 | $fa-var-book: "\f02d";
95 | $fa-var-bookmark: "\f02e";
96 | $fa-var-bookmark-o: "\f097";
97 | $fa-var-briefcase: "\f0b1";
98 | $fa-var-btc: "\f15a";
99 | $fa-var-bug: "\f188";
100 | $fa-var-building: "\f1ad";
101 | $fa-var-building-o: "\f0f7";
102 | $fa-var-bullhorn: "\f0a1";
103 | $fa-var-bullseye: "\f140";
104 | $fa-var-bus: "\f207";
105 | $fa-var-buysellads: "\f20d";
106 | $fa-var-cab: "\f1ba";
107 | $fa-var-calculator: "\f1ec";
108 | $fa-var-calendar: "\f073";
109 | $fa-var-calendar-check-o: "\f274";
110 | $fa-var-calendar-minus-o: "\f272";
111 | $fa-var-calendar-o: "\f133";
112 | $fa-var-calendar-plus-o: "\f271";
113 | $fa-var-calendar-times-o: "\f273";
114 | $fa-var-camera: "\f030";
115 | $fa-var-camera-retro: "\f083";
116 | $fa-var-car: "\f1b9";
117 | $fa-var-caret-down: "\f0d7";
118 | $fa-var-caret-left: "\f0d9";
119 | $fa-var-caret-right: "\f0da";
120 | $fa-var-caret-square-o-down: "\f150";
121 | $fa-var-caret-square-o-left: "\f191";
122 | $fa-var-caret-square-o-right: "\f152";
123 | $fa-var-caret-square-o-up: "\f151";
124 | $fa-var-caret-up: "\f0d8";
125 | $fa-var-cart-arrow-down: "\f218";
126 | $fa-var-cart-plus: "\f217";
127 | $fa-var-cc: "\f20a";
128 | $fa-var-cc-amex: "\f1f3";
129 | $fa-var-cc-diners-club: "\f24c";
130 | $fa-var-cc-discover: "\f1f2";
131 | $fa-var-cc-jcb: "\f24b";
132 | $fa-var-cc-mastercard: "\f1f1";
133 | $fa-var-cc-paypal: "\f1f4";
134 | $fa-var-cc-stripe: "\f1f5";
135 | $fa-var-cc-visa: "\f1f0";
136 | $fa-var-certificate: "\f0a3";
137 | $fa-var-chain: "\f0c1";
138 | $fa-var-chain-broken: "\f127";
139 | $fa-var-check: "\f00c";
140 | $fa-var-check-circle: "\f058";
141 | $fa-var-check-circle-o: "\f05d";
142 | $fa-var-check-square: "\f14a";
143 | $fa-var-check-square-o: "\f046";
144 | $fa-var-chevron-circle-down: "\f13a";
145 | $fa-var-chevron-circle-left: "\f137";
146 | $fa-var-chevron-circle-right: "\f138";
147 | $fa-var-chevron-circle-up: "\f139";
148 | $fa-var-chevron-down: "\f078";
149 | $fa-var-chevron-left: "\f053";
150 | $fa-var-chevron-right: "\f054";
151 | $fa-var-chevron-up: "\f077";
152 | $fa-var-child: "\f1ae";
153 | $fa-var-chrome: "\f268";
154 | $fa-var-circle: "\f111";
155 | $fa-var-circle-o: "\f10c";
156 | $fa-var-circle-o-notch: "\f1ce";
157 | $fa-var-circle-thin: "\f1db";
158 | $fa-var-clipboard: "\f0ea";
159 | $fa-var-clock-o: "\f017";
160 | $fa-var-clone: "\f24d";
161 | $fa-var-close: "\f00d";
162 | $fa-var-cloud: "\f0c2";
163 | $fa-var-cloud-download: "\f0ed";
164 | $fa-var-cloud-upload: "\f0ee";
165 | $fa-var-cny: "\f157";
166 | $fa-var-code: "\f121";
167 | $fa-var-code-fork: "\f126";
168 | $fa-var-codepen: "\f1cb";
169 | $fa-var-codiepie: "\f284";
170 | $fa-var-coffee: "\f0f4";
171 | $fa-var-cog: "\f013";
172 | $fa-var-cogs: "\f085";
173 | $fa-var-columns: "\f0db";
174 | $fa-var-comment: "\f075";
175 | $fa-var-comment-o: "\f0e5";
176 | $fa-var-commenting: "\f27a";
177 | $fa-var-commenting-o: "\f27b";
178 | $fa-var-comments: "\f086";
179 | $fa-var-comments-o: "\f0e6";
180 | $fa-var-compass: "\f14e";
181 | $fa-var-compress: "\f066";
182 | $fa-var-connectdevelop: "\f20e";
183 | $fa-var-contao: "\f26d";
184 | $fa-var-copy: "\f0c5";
185 | $fa-var-copyright: "\f1f9";
186 | $fa-var-creative-commons: "\f25e";
187 | $fa-var-credit-card: "\f09d";
188 | $fa-var-credit-card-alt: "\f283";
189 | $fa-var-crop: "\f125";
190 | $fa-var-crosshairs: "\f05b";
191 | $fa-var-css3: "\f13c";
192 | $fa-var-cube: "\f1b2";
193 | $fa-var-cubes: "\f1b3";
194 | $fa-var-cut: "\f0c4";
195 | $fa-var-cutlery: "\f0f5";
196 | $fa-var-dashboard: "\f0e4";
197 | $fa-var-dashcube: "\f210";
198 | $fa-var-database: "\f1c0";
199 | $fa-var-dedent: "\f03b";
200 | $fa-var-delicious: "\f1a5";
201 | $fa-var-desktop: "\f108";
202 | $fa-var-deviantart: "\f1bd";
203 | $fa-var-diamond: "\f219";
204 | $fa-var-digg: "\f1a6";
205 | $fa-var-dollar: "\f155";
206 | $fa-var-dot-circle-o: "\f192";
207 | $fa-var-download: "\f019";
208 | $fa-var-dribbble: "\f17d";
209 | $fa-var-dropbox: "\f16b";
210 | $fa-var-drupal: "\f1a9";
211 | $fa-var-edge: "\f282";
212 | $fa-var-edit: "\f044";
213 | $fa-var-eject: "\f052";
214 | $fa-var-ellipsis-h: "\f141";
215 | $fa-var-ellipsis-v: "\f142";
216 | $fa-var-empire: "\f1d1";
217 | $fa-var-envelope: "\f0e0";
218 | $fa-var-envelope-o: "\f003";
219 | $fa-var-envelope-square: "\f199";
220 | $fa-var-eraser: "\f12d";
221 | $fa-var-eur: "\f153";
222 | $fa-var-euro: "\f153";
223 | $fa-var-exchange: "\f0ec";
224 | $fa-var-exclamation: "\f12a";
225 | $fa-var-exclamation-circle: "\f06a";
226 | $fa-var-exclamation-triangle: "\f071";
227 | $fa-var-expand: "\f065";
228 | $fa-var-expeditedssl: "\f23e";
229 | $fa-var-external-link: "\f08e";
230 | $fa-var-external-link-square: "\f14c";
231 | $fa-var-eye: "\f06e";
232 | $fa-var-eye-slash: "\f070";
233 | $fa-var-eyedropper: "\f1fb";
234 | $fa-var-facebook: "\f09a";
235 | $fa-var-facebook-f: "\f09a";
236 | $fa-var-facebook-official: "\f230";
237 | $fa-var-facebook-square: "\f082";
238 | $fa-var-fast-backward: "\f049";
239 | $fa-var-fast-forward: "\f050";
240 | $fa-var-fax: "\f1ac";
241 | $fa-var-feed: "\f09e";
242 | $fa-var-female: "\f182";
243 | $fa-var-fighter-jet: "\f0fb";
244 | $fa-var-file: "\f15b";
245 | $fa-var-file-archive-o: "\f1c6";
246 | $fa-var-file-audio-o: "\f1c7";
247 | $fa-var-file-code-o: "\f1c9";
248 | $fa-var-file-excel-o: "\f1c3";
249 | $fa-var-file-image-o: "\f1c5";
250 | $fa-var-file-movie-o: "\f1c8";
251 | $fa-var-file-o: "\f016";
252 | $fa-var-file-pdf-o: "\f1c1";
253 | $fa-var-file-photo-o: "\f1c5";
254 | $fa-var-file-picture-o: "\f1c5";
255 | $fa-var-file-powerpoint-o: "\f1c4";
256 | $fa-var-file-sound-o: "\f1c7";
257 | $fa-var-file-text: "\f15c";
258 | $fa-var-file-text-o: "\f0f6";
259 | $fa-var-file-video-o: "\f1c8";
260 | $fa-var-file-word-o: "\f1c2";
261 | $fa-var-file-zip-o: "\f1c6";
262 | $fa-var-files-o: "\f0c5";
263 | $fa-var-film: "\f008";
264 | $fa-var-filter: "\f0b0";
265 | $fa-var-fire: "\f06d";
266 | $fa-var-fire-extinguisher: "\f134";
267 | $fa-var-firefox: "\f269";
268 | $fa-var-flag: "\f024";
269 | $fa-var-flag-checkered: "\f11e";
270 | $fa-var-flag-o: "\f11d";
271 | $fa-var-flash: "\f0e7";
272 | $fa-var-flask: "\f0c3";
273 | $fa-var-flickr: "\f16e";
274 | $fa-var-floppy-o: "\f0c7";
275 | $fa-var-folder: "\f07b";
276 | $fa-var-folder-o: "\f114";
277 | $fa-var-folder-open: "\f07c";
278 | $fa-var-folder-open-o: "\f115";
279 | $fa-var-font: "\f031";
280 | $fa-var-fonticons: "\f280";
281 | $fa-var-fort-awesome: "\f286";
282 | $fa-var-forumbee: "\f211";
283 | $fa-var-forward: "\f04e";
284 | $fa-var-foursquare: "\f180";
285 | $fa-var-frown-o: "\f119";
286 | $fa-var-futbol-o: "\f1e3";
287 | $fa-var-gamepad: "\f11b";
288 | $fa-var-gavel: "\f0e3";
289 | $fa-var-gbp: "\f154";
290 | $fa-var-ge: "\f1d1";
291 | $fa-var-gear: "\f013";
292 | $fa-var-gears: "\f085";
293 | $fa-var-genderless: "\f22d";
294 | $fa-var-get-pocket: "\f265";
295 | $fa-var-gg: "\f260";
296 | $fa-var-gg-circle: "\f261";
297 | $fa-var-gift: "\f06b";
298 | $fa-var-git: "\f1d3";
299 | $fa-var-git-square: "\f1d2";
300 | $fa-var-github: "\f09b";
301 | $fa-var-github-alt: "\f113";
302 | $fa-var-github-square: "\f092";
303 | $fa-var-gittip: "\f184";
304 | $fa-var-glass: "\f000";
305 | $fa-var-globe: "\f0ac";
306 | $fa-var-google: "\f1a0";
307 | $fa-var-google-plus: "\f0d5";
308 | $fa-var-google-plus-square: "\f0d4";
309 | $fa-var-google-wallet: "\f1ee";
310 | $fa-var-graduation-cap: "\f19d";
311 | $fa-var-gratipay: "\f184";
312 | $fa-var-group: "\f0c0";
313 | $fa-var-h-square: "\f0fd";
314 | $fa-var-hacker-news: "\f1d4";
315 | $fa-var-hand-grab-o: "\f255";
316 | $fa-var-hand-lizard-o: "\f258";
317 | $fa-var-hand-o-down: "\f0a7";
318 | $fa-var-hand-o-left: "\f0a5";
319 | $fa-var-hand-o-right: "\f0a4";
320 | $fa-var-hand-o-up: "\f0a6";
321 | $fa-var-hand-paper-o: "\f256";
322 | $fa-var-hand-peace-o: "\f25b";
323 | $fa-var-hand-pointer-o: "\f25a";
324 | $fa-var-hand-rock-o: "\f255";
325 | $fa-var-hand-scissors-o: "\f257";
326 | $fa-var-hand-spock-o: "\f259";
327 | $fa-var-hand-stop-o: "\f256";
328 | $fa-var-hashtag: "\f292";
329 | $fa-var-hdd-o: "\f0a0";
330 | $fa-var-header: "\f1dc";
331 | $fa-var-headphones: "\f025";
332 | $fa-var-heart: "\f004";
333 | $fa-var-heart-o: "\f08a";
334 | $fa-var-heartbeat: "\f21e";
335 | $fa-var-history: "\f1da";
336 | $fa-var-home: "\f015";
337 | $fa-var-hospital-o: "\f0f8";
338 | $fa-var-hotel: "\f236";
339 | $fa-var-hourglass: "\f254";
340 | $fa-var-hourglass-1: "\f251";
341 | $fa-var-hourglass-2: "\f252";
342 | $fa-var-hourglass-3: "\f253";
343 | $fa-var-hourglass-end: "\f253";
344 | $fa-var-hourglass-half: "\f252";
345 | $fa-var-hourglass-o: "\f250";
346 | $fa-var-hourglass-start: "\f251";
347 | $fa-var-houzz: "\f27c";
348 | $fa-var-html5: "\f13b";
349 | $fa-var-i-cursor: "\f246";
350 | $fa-var-ils: "\f20b";
351 | $fa-var-image: "\f03e";
352 | $fa-var-inbox: "\f01c";
353 | $fa-var-indent: "\f03c";
354 | $fa-var-industry: "\f275";
355 | $fa-var-info: "\f129";
356 | $fa-var-info-circle: "\f05a";
357 | $fa-var-inr: "\f156";
358 | $fa-var-instagram: "\f16d";
359 | $fa-var-institution: "\f19c";
360 | $fa-var-internet-explorer: "\f26b";
361 | $fa-var-intersex: "\f224";
362 | $fa-var-ioxhost: "\f208";
363 | $fa-var-italic: "\f033";
364 | $fa-var-joomla: "\f1aa";
365 | $fa-var-jpy: "\f157";
366 | $fa-var-jsfiddle: "\f1cc";
367 | $fa-var-key: "\f084";
368 | $fa-var-keyboard-o: "\f11c";
369 | $fa-var-krw: "\f159";
370 | $fa-var-language: "\f1ab";
371 | $fa-var-laptop: "\f109";
372 | $fa-var-lastfm: "\f202";
373 | $fa-var-lastfm-square: "\f203";
374 | $fa-var-leaf: "\f06c";
375 | $fa-var-leanpub: "\f212";
376 | $fa-var-legal: "\f0e3";
377 | $fa-var-lemon-o: "\f094";
378 | $fa-var-level-down: "\f149";
379 | $fa-var-level-up: "\f148";
380 | $fa-var-life-bouy: "\f1cd";
381 | $fa-var-life-buoy: "\f1cd";
382 | $fa-var-life-ring: "\f1cd";
383 | $fa-var-life-saver: "\f1cd";
384 | $fa-var-lightbulb-o: "\f0eb";
385 | $fa-var-line-chart: "\f201";
386 | $fa-var-link: "\f0c1";
387 | $fa-var-linkedin: "\f0e1";
388 | $fa-var-linkedin-square: "\f08c";
389 | $fa-var-linux: "\f17c";
390 | $fa-var-list: "\f03a";
391 | $fa-var-list-alt: "\f022";
392 | $fa-var-list-ol: "\f0cb";
393 | $fa-var-list-ul: "\f0ca";
394 | $fa-var-location-arrow: "\f124";
395 | $fa-var-lock: "\f023";
396 | $fa-var-long-arrow-down: "\f175";
397 | $fa-var-long-arrow-left: "\f177";
398 | $fa-var-long-arrow-right: "\f178";
399 | $fa-var-long-arrow-up: "\f176";
400 | $fa-var-magic: "\f0d0";
401 | $fa-var-magnet: "\f076";
402 | $fa-var-mail-forward: "\f064";
403 | $fa-var-mail-reply: "\f112";
404 | $fa-var-mail-reply-all: "\f122";
405 | $fa-var-male: "\f183";
406 | $fa-var-map: "\f279";
407 | $fa-var-map-marker: "\f041";
408 | $fa-var-map-o: "\f278";
409 | $fa-var-map-pin: "\f276";
410 | $fa-var-map-signs: "\f277";
411 | $fa-var-mars: "\f222";
412 | $fa-var-mars-double: "\f227";
413 | $fa-var-mars-stroke: "\f229";
414 | $fa-var-mars-stroke-h: "\f22b";
415 | $fa-var-mars-stroke-v: "\f22a";
416 | $fa-var-maxcdn: "\f136";
417 | $fa-var-meanpath: "\f20c";
418 | $fa-var-medium: "\f23a";
419 | $fa-var-medkit: "\f0fa";
420 | $fa-var-meh-o: "\f11a";
421 | $fa-var-mercury: "\f223";
422 | $fa-var-microphone: "\f130";
423 | $fa-var-microphone-slash: "\f131";
424 | $fa-var-minus: "\f068";
425 | $fa-var-minus-circle: "\f056";
426 | $fa-var-minus-square: "\f146";
427 | $fa-var-minus-square-o: "\f147";
428 | $fa-var-mixcloud: "\f289";
429 | $fa-var-mobile: "\f10b";
430 | $fa-var-mobile-phone: "\f10b";
431 | $fa-var-modx: "\f285";
432 | $fa-var-money: "\f0d6";
433 | $fa-var-moon-o: "\f186";
434 | $fa-var-mortar-board: "\f19d";
435 | $fa-var-motorcycle: "\f21c";
436 | $fa-var-mouse-pointer: "\f245";
437 | $fa-var-music: "\f001";
438 | $fa-var-navicon: "\f0c9";
439 | $fa-var-neuter: "\f22c";
440 | $fa-var-newspaper-o: "\f1ea";
441 | $fa-var-object-group: "\f247";
442 | $fa-var-object-ungroup: "\f248";
443 | $fa-var-odnoklassniki: "\f263";
444 | $fa-var-odnoklassniki-square: "\f264";
445 | $fa-var-opencart: "\f23d";
446 | $fa-var-openid: "\f19b";
447 | $fa-var-opera: "\f26a";
448 | $fa-var-optin-monster: "\f23c";
449 | $fa-var-outdent: "\f03b";
450 | $fa-var-pagelines: "\f18c";
451 | $fa-var-paint-brush: "\f1fc";
452 | $fa-var-paper-plane: "\f1d8";
453 | $fa-var-paper-plane-o: "\f1d9";
454 | $fa-var-paperclip: "\f0c6";
455 | $fa-var-paragraph: "\f1dd";
456 | $fa-var-paste: "\f0ea";
457 | $fa-var-pause: "\f04c";
458 | $fa-var-pause-circle: "\f28b";
459 | $fa-var-pause-circle-o: "\f28c";
460 | $fa-var-paw: "\f1b0";
461 | $fa-var-paypal: "\f1ed";
462 | $fa-var-pencil: "\f040";
463 | $fa-var-pencil-square: "\f14b";
464 | $fa-var-pencil-square-o: "\f044";
465 | $fa-var-percent: "\f295";
466 | $fa-var-phone: "\f095";
467 | $fa-var-phone-square: "\f098";
468 | $fa-var-photo: "\f03e";
469 | $fa-var-picture-o: "\f03e";
470 | $fa-var-pie-chart: "\f200";
471 | $fa-var-pied-piper: "\f1a7";
472 | $fa-var-pied-piper-alt: "\f1a8";
473 | $fa-var-pinterest: "\f0d2";
474 | $fa-var-pinterest-p: "\f231";
475 | $fa-var-pinterest-square: "\f0d3";
476 | $fa-var-plane: "\f072";
477 | $fa-var-play: "\f04b";
478 | $fa-var-play-circle: "\f144";
479 | $fa-var-play-circle-o: "\f01d";
480 | $fa-var-plug: "\f1e6";
481 | $fa-var-plus: "\f067";
482 | $fa-var-plus-circle: "\f055";
483 | $fa-var-plus-square: "\f0fe";
484 | $fa-var-plus-square-o: "\f196";
485 | $fa-var-power-off: "\f011";
486 | $fa-var-print: "\f02f";
487 | $fa-var-product-hunt: "\f288";
488 | $fa-var-puzzle-piece: "\f12e";
489 | $fa-var-qq: "\f1d6";
490 | $fa-var-qrcode: "\f029";
491 | $fa-var-question: "\f128";
492 | $fa-var-question-circle: "\f059";
493 | $fa-var-quote-left: "\f10d";
494 | $fa-var-quote-right: "\f10e";
495 | $fa-var-ra: "\f1d0";
496 | $fa-var-random: "\f074";
497 | $fa-var-rebel: "\f1d0";
498 | $fa-var-recycle: "\f1b8";
499 | $fa-var-reddit: "\f1a1";
500 | $fa-var-reddit-alien: "\f281";
501 | $fa-var-reddit-square: "\f1a2";
502 | $fa-var-refresh: "\f021";
503 | $fa-var-registered: "\f25d";
504 | $fa-var-remove: "\f00d";
505 | $fa-var-renren: "\f18b";
506 | $fa-var-reorder: "\f0c9";
507 | $fa-var-repeat: "\f01e";
508 | $fa-var-reply: "\f112";
509 | $fa-var-reply-all: "\f122";
510 | $fa-var-retweet: "\f079";
511 | $fa-var-rmb: "\f157";
512 | $fa-var-road: "\f018";
513 | $fa-var-rocket: "\f135";
514 | $fa-var-rotate-left: "\f0e2";
515 | $fa-var-rotate-right: "\f01e";
516 | $fa-var-rouble: "\f158";
517 | $fa-var-rss: "\f09e";
518 | $fa-var-rss-square: "\f143";
519 | $fa-var-rub: "\f158";
520 | $fa-var-ruble: "\f158";
521 | $fa-var-rupee: "\f156";
522 | $fa-var-safari: "\f267";
523 | $fa-var-save: "\f0c7";
524 | $fa-var-scissors: "\f0c4";
525 | $fa-var-scribd: "\f28a";
526 | $fa-var-search: "\f002";
527 | $fa-var-search-minus: "\f010";
528 | $fa-var-search-plus: "\f00e";
529 | $fa-var-sellsy: "\f213";
530 | $fa-var-send: "\f1d8";
531 | $fa-var-send-o: "\f1d9";
532 | $fa-var-server: "\f233";
533 | $fa-var-share: "\f064";
534 | $fa-var-share-alt: "\f1e0";
535 | $fa-var-share-alt-square: "\f1e1";
536 | $fa-var-share-square: "\f14d";
537 | $fa-var-share-square-o: "\f045";
538 | $fa-var-shekel: "\f20b";
539 | $fa-var-sheqel: "\f20b";
540 | $fa-var-shield: "\f132";
541 | $fa-var-ship: "\f21a";
542 | $fa-var-shirtsinbulk: "\f214";
543 | $fa-var-shopping-bag: "\f290";
544 | $fa-var-shopping-basket: "\f291";
545 | $fa-var-shopping-cart: "\f07a";
546 | $fa-var-sign-in: "\f090";
547 | $fa-var-sign-out: "\f08b";
548 | $fa-var-signal: "\f012";
549 | $fa-var-simplybuilt: "\f215";
550 | $fa-var-sitemap: "\f0e8";
551 | $fa-var-skyatlas: "\f216";
552 | $fa-var-skype: "\f17e";
553 | $fa-var-slack: "\f198";
554 | $fa-var-sliders: "\f1de";
555 | $fa-var-slideshare: "\f1e7";
556 | $fa-var-smile-o: "\f118";
557 | $fa-var-soccer-ball-o: "\f1e3";
558 | $fa-var-sort: "\f0dc";
559 | $fa-var-sort-alpha-asc: "\f15d";
560 | $fa-var-sort-alpha-desc: "\f15e";
561 | $fa-var-sort-amount-asc: "\f160";
562 | $fa-var-sort-amount-desc: "\f161";
563 | $fa-var-sort-asc: "\f0de";
564 | $fa-var-sort-desc: "\f0dd";
565 | $fa-var-sort-down: "\f0dd";
566 | $fa-var-sort-numeric-asc: "\f162";
567 | $fa-var-sort-numeric-desc: "\f163";
568 | $fa-var-sort-up: "\f0de";
569 | $fa-var-soundcloud: "\f1be";
570 | $fa-var-space-shuttle: "\f197";
571 | $fa-var-spinner: "\f110";
572 | $fa-var-spoon: "\f1b1";
573 | $fa-var-spotify: "\f1bc";
574 | $fa-var-square: "\f0c8";
575 | $fa-var-square-o: "\f096";
576 | $fa-var-stack-exchange: "\f18d";
577 | $fa-var-stack-overflow: "\f16c";
578 | $fa-var-star: "\f005";
579 | $fa-var-star-half: "\f089";
580 | $fa-var-star-half-empty: "\f123";
581 | $fa-var-star-half-full: "\f123";
582 | $fa-var-star-half-o: "\f123";
583 | $fa-var-star-o: "\f006";
584 | $fa-var-steam: "\f1b6";
585 | $fa-var-steam-square: "\f1b7";
586 | $fa-var-step-backward: "\f048";
587 | $fa-var-step-forward: "\f051";
588 | $fa-var-stethoscope: "\f0f1";
589 | $fa-var-sticky-note: "\f249";
590 | $fa-var-sticky-note-o: "\f24a";
591 | $fa-var-stop: "\f04d";
592 | $fa-var-stop-circle: "\f28d";
593 | $fa-var-stop-circle-o: "\f28e";
594 | $fa-var-street-view: "\f21d";
595 | $fa-var-strikethrough: "\f0cc";
596 | $fa-var-stumbleupon: "\f1a4";
597 | $fa-var-stumbleupon-circle: "\f1a3";
598 | $fa-var-subscript: "\f12c";
599 | $fa-var-subway: "\f239";
600 | $fa-var-suitcase: "\f0f2";
601 | $fa-var-sun-o: "\f185";
602 | $fa-var-superscript: "\f12b";
603 | $fa-var-support: "\f1cd";
604 | $fa-var-table: "\f0ce";
605 | $fa-var-tablet: "\f10a";
606 | $fa-var-tachometer: "\f0e4";
607 | $fa-var-tag: "\f02b";
608 | $fa-var-tags: "\f02c";
609 | $fa-var-tasks: "\f0ae";
610 | $fa-var-taxi: "\f1ba";
611 | $fa-var-television: "\f26c";
612 | $fa-var-tencent-weibo: "\f1d5";
613 | $fa-var-terminal: "\f120";
614 | $fa-var-text-height: "\f034";
615 | $fa-var-text-width: "\f035";
616 | $fa-var-th: "\f00a";
617 | $fa-var-th-large: "\f009";
618 | $fa-var-th-list: "\f00b";
619 | $fa-var-thumb-tack: "\f08d";
620 | $fa-var-thumbs-down: "\f165";
621 | $fa-var-thumbs-o-down: "\f088";
622 | $fa-var-thumbs-o-up: "\f087";
623 | $fa-var-thumbs-up: "\f164";
624 | $fa-var-ticket: "\f145";
625 | $fa-var-times: "\f00d";
626 | $fa-var-times-circle: "\f057";
627 | $fa-var-times-circle-o: "\f05c";
628 | $fa-var-tint: "\f043";
629 | $fa-var-toggle-down: "\f150";
630 | $fa-var-toggle-left: "\f191";
631 | $fa-var-toggle-off: "\f204";
632 | $fa-var-toggle-on: "\f205";
633 | $fa-var-toggle-right: "\f152";
634 | $fa-var-toggle-up: "\f151";
635 | $fa-var-trademark: "\f25c";
636 | $fa-var-train: "\f238";
637 | $fa-var-transgender: "\f224";
638 | $fa-var-transgender-alt: "\f225";
639 | $fa-var-trash: "\f1f8";
640 | $fa-var-trash-o: "\f014";
641 | $fa-var-tree: "\f1bb";
642 | $fa-var-trello: "\f181";
643 | $fa-var-tripadvisor: "\f262";
644 | $fa-var-trophy: "\f091";
645 | $fa-var-truck: "\f0d1";
646 | $fa-var-try: "\f195";
647 | $fa-var-tty: "\f1e4";
648 | $fa-var-tumblr: "\f173";
649 | $fa-var-tumblr-square: "\f174";
650 | $fa-var-turkish-lira: "\f195";
651 | $fa-var-tv: "\f26c";
652 | $fa-var-twitch: "\f1e8";
653 | $fa-var-twitter: "\f099";
654 | $fa-var-twitter-square: "\f081";
655 | $fa-var-umbrella: "\f0e9";
656 | $fa-var-underline: "\f0cd";
657 | $fa-var-undo: "\f0e2";
658 | $fa-var-university: "\f19c";
659 | $fa-var-unlink: "\f127";
660 | $fa-var-unlock: "\f09c";
661 | $fa-var-unlock-alt: "\f13e";
662 | $fa-var-unsorted: "\f0dc";
663 | $fa-var-upload: "\f093";
664 | $fa-var-usb: "\f287";
665 | $fa-var-usd: "\f155";
666 | $fa-var-user: "\f007";
667 | $fa-var-user-md: "\f0f0";
668 | $fa-var-user-plus: "\f234";
669 | $fa-var-user-secret: "\f21b";
670 | $fa-var-user-times: "\f235";
671 | $fa-var-users: "\f0c0";
672 | $fa-var-venus: "\f221";
673 | $fa-var-venus-double: "\f226";
674 | $fa-var-venus-mars: "\f228";
675 | $fa-var-viacoin: "\f237";
676 | $fa-var-video-camera: "\f03d";
677 | $fa-var-vimeo: "\f27d";
678 | $fa-var-vimeo-square: "\f194";
679 | $fa-var-vine: "\f1ca";
680 | $fa-var-vk: "\f189";
681 | $fa-var-volume-down: "\f027";
682 | $fa-var-volume-off: "\f026";
683 | $fa-var-volume-up: "\f028";
684 | $fa-var-warning: "\f071";
685 | $fa-var-wechat: "\f1d7";
686 | $fa-var-weibo: "\f18a";
687 | $fa-var-weixin: "\f1d7";
688 | $fa-var-whatsapp: "\f232";
689 | $fa-var-wheelchair: "\f193";
690 | $fa-var-wifi: "\f1eb";
691 | $fa-var-wikipedia-w: "\f266";
692 | $fa-var-windows: "\f17a";
693 | $fa-var-won: "\f159";
694 | $fa-var-wordpress: "\f19a";
695 | $fa-var-wrench: "\f0ad";
696 | $fa-var-xing: "\f168";
697 | $fa-var-xing-square: "\f169";
698 | $fa-var-y-combinator: "\f23b";
699 | $fa-var-y-combinator-square: "\f1d4";
700 | $fa-var-yahoo: "\f19e";
701 | $fa-var-yc: "\f23b";
702 | $fa-var-yc-square: "\f1d4";
703 | $fa-var-yelp: "\f1e9";
704 | $fa-var-yen: "\f157";
705 | $fa-var-youtube: "\f167";
706 | $fa-var-youtube-play: "\f16a";
707 | $fa-var-youtube-square: "\f166";
708 |
709 |
--------------------------------------------------------------------------------
/app/assets/sass/styles.scss:
--------------------------------------------------------------------------------
1 | @import "compass/reset";
2 | @import "font-awesome/styles";
3 |
4 | @import "variables";
5 | @import "fonts";
6 | @import "common";
7 | @import "buttons";
8 | @import "forms";
9 | @import "nav";
10 |
11 | @import "plan";
12 | @import "plan-node";
13 | @import "menu";
14 | @import "page";
15 | @import "table";
16 | @import "modal";
17 | @import "footer";
18 | @import "highlight";
19 |
--------------------------------------------------------------------------------
/app/bootstrap.ts:
--------------------------------------------------------------------------------
1 | import {provide} from 'angular2/core';
2 | import {bootstrap} from 'angular2/platform/browser';
3 | import {ROUTER_PROVIDERS, LocationStrategy, HashLocationStrategy} from 'angular2/router';
4 | import {App} from './components/app/app';
5 |
6 | bootstrap(App, [
7 | ROUTER_PROVIDERS,
8 | provide(LocationStrategy, { useClass: HashLocationStrategy })
9 | ]);
10 |
--------------------------------------------------------------------------------
/app/components/about/about.html:
--------------------------------------------------------------------------------
1 |
2 |
Postgres EXPLAIN Visualizer (Pev)
3 |
4 | Pev is designed to make
5 | Postgres query plans easier to grok. It displays a plan as a tree, with each node representing a step that takes in a row set
6 | and produces another. Pev can show you a number of useful things:
7 |
8 | overall plan stats
9 | individual node stats (duration, row size, cost)
10 | explanation of what each node does
11 | outlier nodes
12 | graph of a specific metric (like cost) for all nodes
13 | for some nodes, highlighted part of the query which corresponds to the node
14 |
15 |
16 |
You can tweak display options using the menu on the right.
17 |
18 |
Usage tips
19 |
Pev currently accepts only JSON formatted plans. In fact, the get the most out of it,
20 | I recommend generating a query plan using the following line:
21 | EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON)
. I also recommend submitting a (decently formatted)
22 | SQL query that generated the plan. Doing so will make Pev more useful.
23 |
24 |
25 |
Pev will remember the plans you analyzed. They are stored locally and are not submitted to me. This is good
26 | because no one but you will see your queries. It's also bad because you can't share them with others.
27 |
28 |
Acknowledgements
29 |
Pev was inspired and heavily influenced by the excellent explain.depesz.com . Both the
30 | tool and the corresponding help files are a great resource to learn about Postgres and its planner.
31 |
32 |
33 |
Help me improve Pev
34 |
If you want to help, there are multiple ways to contribute:
35 |
36 | give me your plans - I need more test cases for the layout
37 | add descriptions for missing node types (especially ones from newer versions of Postgres)
38 | contribute to the code on github
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/app/components/about/about.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 |
3 | @Component({
4 | selector: 'about',
5 | templateUrl: './components/about/about.html'
6 | })
7 | export class About {}
8 |
--------------------------------------------------------------------------------
/app/components/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
16 |
--------------------------------------------------------------------------------
/app/components/app/app.ts:
--------------------------------------------------------------------------------
1 | import {Component, ViewEncapsulation} from 'angular2/core';
2 | import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
3 |
4 | import {PlanView} from '../plan-view/plan-view';
5 | import {PlanList} from '../plan-list/plan-list';
6 | import {PlanNew} from '../plan-new/plan-new';
7 | import {About} from '../about/about';
8 |
9 | @Component({
10 | selector: 'app',
11 | templateUrl: './components/app/app.html',
12 | encapsulation: ViewEncapsulation.None,
13 | directives: [ROUTER_DIRECTIVES]
14 | })
15 |
16 | @RouteConfig([
17 | { path: '/', redirectTo: ['/PlanList'] },
18 | { path: '/plans', component: PlanList, name: 'PlanList' },
19 | { path: '/plans/new', component: PlanNew, name: 'PlanNew' },
20 | { path: '/plans/:id', component: PlanView, name: 'PlanView' },
21 | { path: '/about', component: About, name: 'About'}
22 | ])
23 |
24 | export class App { }
25 |
--------------------------------------------------------------------------------
/app/components/plan-list/plan-list.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Welcome to PEV! Please
submit a plan for visualization
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
You're about to delete plan "{{planToDelete.name}}". Are you sure?
15 |
19 |
20 |
21 |
22 |
23 |
24 | {{plan.name}}
25 | created on {{plan.createdOn | momentDate }}
26 |
27 | delete
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/app/components/plan-list/plan-list.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from 'angular2/core';
2 | import {ROUTER_DIRECTIVES} from 'angular2/router';
3 |
4 | import {IPlan} from '../../interfaces/iplan';
5 | import {PlanService} from '../../services/plan-service';
6 | import {PlanNew} from '../plan-new/plan-new';
7 |
8 | import {MomentDatePipe} from '../../pipes';
9 |
10 | @Component({
11 | selector: 'plan-list',
12 | templateUrl: './components/plan-list/plan-list.html',
13 | providers: [PlanService],
14 | directives: [ROUTER_DIRECTIVES, PlanNew],
15 | pipes: [MomentDatePipe]
16 | })
17 | export class PlanList {
18 | plans: Array;
19 | newPlanName: string;
20 | newPlanContent: any;
21 | newPlanId: string;
22 | openDialog: boolean = false;
23 | planToDelete: IPlan;
24 |
25 | constructor(private _planService: PlanService) { }
26 |
27 | ngOnInit() {
28 | this.plans = this._planService.getPlans();
29 | }
30 |
31 | requestDelete(plan) {
32 | this.openDialog = true;
33 | this.planToDelete = plan;
34 | }
35 |
36 | deletePlan(plan) {
37 | this.openDialog = false;
38 | console.log(this.planToDelete);
39 | this._planService.deletePlan(this.planToDelete);
40 | this.plans = this._planService.getPlans();
41 | }
42 |
43 | cancelDelete() {
44 | this.openDialog = false;
45 | }
46 |
47 | deleteAllPlans() {
48 | this._planService.deleteAllPlans();
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/components/plan-new/plan-new.html:
--------------------------------------------------------------------------------
1 |
2 |
create a sample plan
3 |
4 |
For best results, use EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON)
5 | Psql users can export the plan to a file using psql -qAt -f explain.sql > analyze.json
6 |
DISCLAIMER: Pev stores your plans locally (localStorage) and will not send them anywhere.
7 |
8 |
9 | submit
10 |
11 |
12 |
{{validationMessage}}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/components/plan-new/plan-new.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from 'angular2/core';
2 | import {Router, ROUTER_DIRECTIVES} from 'angular2/router';
3 | import {IPlan} from '../../interfaces/iplan';
4 |
5 | import {PlanService} from '../../services/plan-service';
6 |
7 | @Component({
8 | selector: 'plan-new',
9 | templateUrl: './components/plan-new/plan-new.html',
10 | providers: [PlanService],
11 | directives: [ROUTER_DIRECTIVES]
12 | })
13 | export class PlanNew {
14 | planIds: string[];
15 | newPlanName: string;
16 | newPlanContent: string;
17 | newPlanQuery: string;
18 | newPlan: IPlan;
19 | validationMessage: string;
20 |
21 | constructor(private _router: Router, private _planService: PlanService) { }
22 |
23 | submitPlan() {
24 | // remove psql generated header
25 | this.newPlanContent = this.newPlanContent.replace('QUERY PLAN', '');
26 |
27 | if (!this._planService.isJsonString(this.newPlanContent)) {
28 | this.validationMessage = 'The string you submitted is not valid JSON'
29 | return;
30 | }
31 |
32 | this.newPlan = this._planService.createPlan(this.newPlanName, this.newPlanContent, this.newPlanQuery);
33 | this._router.navigate(['PlanView', { id: this.newPlan.id }]);
34 | }
35 |
36 | prefill() {
37 | this.newPlanName = 'Sample plan';
38 | this.newPlanContent = SAMPLE_JSON;
39 | this.newPlanQuery = SAMPLE_QUERY;
40 | }
41 | }
42 | export var SAMPLE_JSON = `[
43 | {
44 | "Plan": {
45 | "Node Type": "Limit",
46 | "Startup Cost": 17024.84,
47 | "Total Cost": 17024.87,
48 | "Plan Rows": 10,
49 | "Plan Width": 133,
50 | "Actual Startup Time": 725.773,
51 | "Actual Total Time": 725.775,
52 | "Actual Rows": 10,
53 | "Actual Loops": 1,
54 | "Output": ["c.state", "cat.categoryname", "(sum(o.netamount))", "(sum(o.totalamount))"],
55 | "Shared Hit Blocks": 23,
56 | "Shared Read Blocks": 1392,
57 | "Shared Dirtied Blocks": 0,
58 | "Shared Written Blocks": 0,
59 | "Local Hit Blocks": 0,
60 | "Local Read Blocks": 0,
61 | "Local Dirtied Blocks": 0,
62 | "Local Written Blocks": 0,
63 | "Temp Read Blocks": 0,
64 | "Temp Written Blocks": 0,
65 | "I/O Read Time": 0.000,
66 | "I/O Write Time": 0.000,
67 | "Plans": [
68 | {
69 | "Node Type": "Sort",
70 | "Parent Relationship": "Outer",
71 | "Startup Cost": 17024.84,
72 | "Total Cost": 17026.88,
73 | "Plan Rows": 816,
74 | "Plan Width": 133,
75 | "Actual Startup Time": 725.771,
76 | "Actual Total Time": 725.772,
77 | "Actual Rows": 11,
78 | "Actual Loops": 1,
79 | "Output": ["c.state", "cat.categoryname", "(sum(o.netamount))", "(sum(o.totalamount))"],
80 | "Sort Key": ["c.state", "(sum(o.totalamount))"],
81 | "Sort Method": "top-N heapsort",
82 | "Sort Space Used": 25,
83 | "Sort Space Type": "Memory",
84 | "Shared Hit Blocks": 23,
85 | "Shared Read Blocks": 1392,
86 | "Shared Dirtied Blocks": 0,
87 | "Shared Written Blocks": 0,
88 | "Local Hit Blocks": 0,
89 | "Local Read Blocks": 0,
90 | "Local Dirtied Blocks": 0,
91 | "Local Written Blocks": 0,
92 | "Temp Read Blocks": 0,
93 | "Temp Written Blocks": 0,
94 | "I/O Read Time": 0.000,
95 | "I/O Write Time": 0.000,
96 | "Plans": [
97 | {
98 | "Node Type": "Aggregate",
99 | "Strategy": "Hashed",
100 | "Parent Relationship": "Outer",
101 | "Startup Cost": 16994.41,
102 | "Total Cost": 17006.65,
103 | "Plan Rows": 816,
104 | "Plan Width": 133,
105 | "Actual Startup Time": 723.877,
106 | "Actual Total Time": 724.417,
107 | "Actual Rows": 832,
108 | "Actual Loops": 1,
109 | "Output": ["c.state", "cat.categoryname", "sum(o.netamount)", "sum(o.totalamount)"],
110 | "Group Key": ["c.state", "cat.categoryname"],
111 | "Shared Hit Blocks": 13,
112 | "Shared Read Blocks": 1392,
113 | "Shared Dirtied Blocks": 0,
114 | "Shared Written Blocks": 0,
115 | "Local Hit Blocks": 0,
116 | "Local Read Blocks": 0,
117 | "Local Dirtied Blocks": 0,
118 | "Local Written Blocks": 0,
119 | "Temp Read Blocks": 0,
120 | "Temp Written Blocks": 0,
121 | "I/O Read Time": 0.000,
122 | "I/O Write Time": 0.000,
123 | "Plans": [
124 | {
125 | "Node Type": "Hash Join",
126 | "Parent Relationship": "Outer",
127 | "Join Type": "Inner",
128 | "Startup Cost": 4966.48,
129 | "Total Cost": 13742.65,
130 | "Plan Rows": 325176,
131 | "Plan Width": 133,
132 | "Actual Startup Time": 118.314,
133 | "Actual Total Time": 354.285,
134 | "Actual Rows": 383270,
135 | "Actual Loops": 1,
136 | "Output": ["c.state", "o.netamount", "o.totalamount", "cat.categoryname"],
137 | "Hash Cond": "(o.orderid = ch.orderid)",
138 | "Shared Hit Blocks": 13,
139 | "Shared Read Blocks": 1392,
140 | "Shared Dirtied Blocks": 0,
141 | "Shared Written Blocks": 0,
142 | "Local Hit Blocks": 0,
143 | "Local Read Blocks": 0,
144 | "Local Dirtied Blocks": 0,
145 | "Local Written Blocks": 0,
146 | "Temp Read Blocks": 0,
147 | "Temp Written Blocks": 0,
148 | "I/O Read Time": 0.000,
149 | "I/O Write Time": 0.000,
150 | "Plans": [
151 | {
152 | "Node Type": "Hash Join",
153 | "Parent Relationship": "Outer",
154 | "Join Type": "Inner",
155 | "Startup Cost": 834.86,
156 | "Total Cost": 4539.11,
157 | "Plan Rows": 60350,
158 | "Plan Width": 138,
159 | "Actual Startup Time": 22.651,
160 | "Actual Total Time": 133.484,
161 | "Actual Rows": 60350,
162 | "Actual Loops": 1,
163 | "Output": ["o.netamount", "o.totalamount", "o.orderid", "ol.orderid", "cat.categoryname"],
164 | "Hash Cond": "(ol.orderid = o.orderid)",
165 | "Shared Hit Blocks": 9,
166 | "Shared Read Blocks": 581,
167 | "Shared Dirtied Blocks": 0,
168 | "Shared Written Blocks": 0,
169 | "Local Hit Blocks": 0,
170 | "Local Read Blocks": 0,
171 | "Local Dirtied Blocks": 0,
172 | "Local Written Blocks": 0,
173 | "Temp Read Blocks": 0,
174 | "Temp Written Blocks": 0,
175 | "I/O Read Time": 0.000,
176 | "I/O Write Time": 0.000,
177 | "Plans": [
178 | {
179 | "Node Type": "Hash Join",
180 | "Parent Relationship": "Outer",
181 | "Join Type": "Inner",
182 | "Startup Cost": 464.86,
183 | "Total Cost": 2962.11,
184 | "Plan Rows": 60350,
185 | "Plan Width": 122,
186 | "Actual Startup Time": 12.467,
187 | "Actual Total Time": 85.647,
188 | "Actual Rows": 60350,
189 | "Actual Loops": 1,
190 | "Output": ["ol.orderid", "cat.categoryname"],
191 | "Hash Cond": "(ol.prod_id = p.prod_id)",
192 | "Shared Hit Blocks": 4,
193 | "Shared Read Blocks": 483,
194 | "Shared Dirtied Blocks": 0,
195 | "Shared Written Blocks": 0,
196 | "Local Hit Blocks": 0,
197 | "Local Read Blocks": 0,
198 | "Local Dirtied Blocks": 0,
199 | "Local Written Blocks": 0,
200 | "Temp Read Blocks": 0,
201 | "Temp Written Blocks": 0,
202 | "I/O Read Time": 0.000,
203 | "I/O Write Time": 0.000,
204 | "Plans": [
205 | {
206 | "Node Type": "Seq Scan",
207 | "Parent Relationship": "Outer",
208 | "Relation Name": "orderlines",
209 | "Schema": "public",
210 | "Alias": "ol",
211 | "Startup Cost": 0.00,
212 | "Total Cost": 988.50,
213 | "Plan Rows": 60350,
214 | "Plan Width": 8,
215 | "Actual Startup Time": 0.005,
216 | "Actual Total Time": 14.054,
217 | "Actual Rows": 60350,
218 | "Actual Loops": 1,
219 | "Output": ["ol.orderlineid", "ol.orderid", "ol.prod_id", "ol.quantity", "ol.orderdate"],
220 | "Shared Hit Blocks": 2,
221 | "Shared Read Blocks": 383,
222 | "Shared Dirtied Blocks": 0,
223 | "Shared Written Blocks": 0,
224 | "Local Hit Blocks": 0,
225 | "Local Read Blocks": 0,
226 | "Local Dirtied Blocks": 0,
227 | "Local Written Blocks": 0,
228 | "Temp Read Blocks": 0,
229 | "Temp Written Blocks": 0,
230 | "I/O Read Time": 0.000,
231 | "I/O Write Time": 0.000
232 | },
233 | {
234 | "Node Type": "Hash",
235 | "Parent Relationship": "Inner",
236 | "Startup Cost": 339.86,
237 | "Total Cost": 339.86,
238 | "Plan Rows": 10000,
239 | "Plan Width": 122,
240 | "Actual Startup Time": 12.446,
241 | "Actual Total Time": 12.446,
242 | "Actual Rows": 10000,
243 | "Actual Loops": 1,
244 | "Output": ["p.prod_id", "cat.categoryname"],
245 | "Hash Buckets": 1024,
246 | "Hash Batches": 1,
247 | "Original Hash Batches": 1,
248 | "Peak Memory Usage": 425,
249 | "Shared Hit Blocks": 2,
250 | "Shared Read Blocks": 100,
251 | "Shared Dirtied Blocks": 0,
252 | "Shared Written Blocks": 0,
253 | "Local Hit Blocks": 0,
254 | "Local Read Blocks": 0,
255 | "Local Dirtied Blocks": 0,
256 | "Local Written Blocks": 0,
257 | "Temp Read Blocks": 0,
258 | "Temp Written Blocks": 0,
259 | "I/O Read Time": 0.000,
260 | "I/O Write Time": 0.000,
261 | "Plans": [
262 | {
263 | "Node Type": "Hash Join",
264 | "Parent Relationship": "Outer",
265 | "Join Type": "Inner",
266 | "Startup Cost": 1.36,
267 | "Total Cost": 339.86,
268 | "Plan Rows": 10000,
269 | "Plan Width": 122,
270 | "Actual Startup Time": 0.283,
271 | "Actual Total Time": 9.015,
272 | "Actual Rows": 10000,
273 | "Actual Loops": 1,
274 | "Output": ["p.prod_id", "cat.categoryname"],
275 | "Hash Cond": "(p.category = cat.category)",
276 | "Shared Hit Blocks": 2,
277 | "Shared Read Blocks": 100,
278 | "Shared Dirtied Blocks": 0,
279 | "Shared Written Blocks": 0,
280 | "Local Hit Blocks": 0,
281 | "Local Read Blocks": 0,
282 | "Local Dirtied Blocks": 0,
283 | "Local Written Blocks": 0,
284 | "Temp Read Blocks": 0,
285 | "Temp Written Blocks": 0,
286 | "I/O Read Time": 0.000,
287 | "I/O Write Time": 0.000,
288 | "Plans": [
289 | {
290 | "Node Type": "Seq Scan",
291 | "Parent Relationship": "Outer",
292 | "Relation Name": "products",
293 | "Schema": "public",
294 | "Alias": "p",
295 | "Startup Cost": 0.00,
296 | "Total Cost": 201.00,
297 | "Plan Rows": 10000,
298 | "Plan Width": 8,
299 | "Actual Startup Time": 0.003,
300 | "Actual Total Time": 4.330,
301 | "Actual Rows": 10000,
302 | "Actual Loops": 1,
303 | "Output": ["p.prod_id", "p.category", "p.title", "p.actor", "p.price", "p.special", "p.common_prod_id"],
304 | "Shared Hit Blocks": 2,
305 | "Shared Read Blocks": 99,
306 | "Shared Dirtied Blocks": 0,
307 | "Shared Written Blocks": 0,
308 | "Local Hit Blocks": 0,
309 | "Local Read Blocks": 0,
310 | "Local Dirtied Blocks": 0,
311 | "Local Written Blocks": 0,
312 | "Temp Read Blocks": 0,
313 | "Temp Written Blocks": 0,
314 | "I/O Read Time": 0.000,
315 | "I/O Write Time": 0.000
316 | },
317 | {
318 | "Node Type": "Hash",
319 | "Parent Relationship": "Inner",
320 | "Startup Cost": 1.16,
321 | "Total Cost": 1.16,
322 | "Plan Rows": 16,
323 | "Plan Width": 122,
324 | "Actual Startup Time": 0.265,
325 | "Actual Total Time": 0.265,
326 | "Actual Rows": 16,
327 | "Actual Loops": 1,
328 | "Output": ["cat.categoryname", "cat.category"],
329 | "Hash Buckets": 1024,
330 | "Hash Batches": 1,
331 | "Original Hash Batches": 1,
332 | "Peak Memory Usage": 1,
333 | "Shared Hit Blocks": 0,
334 | "Shared Read Blocks": 1,
335 | "Shared Dirtied Blocks": 0,
336 | "Shared Written Blocks": 0,
337 | "Local Hit Blocks": 0,
338 | "Local Read Blocks": 0,
339 | "Local Dirtied Blocks": 0,
340 | "Local Written Blocks": 0,
341 | "Temp Read Blocks": 0,
342 | "Temp Written Blocks": 0,
343 | "I/O Read Time": 0.000,
344 | "I/O Write Time": 0.000,
345 | "Plans": [
346 | {
347 | "Node Type": "Seq Scan",
348 | "Parent Relationship": "Outer",
349 | "Relation Name": "categories",
350 | "Schema": "public",
351 | "Alias": "cat",
352 | "Startup Cost": 0.00,
353 | "Total Cost": 1.16,
354 | "Plan Rows": 16,
355 | "Plan Width": 122,
356 | "Actual Startup Time": 0.250,
357 | "Actual Total Time": 0.252,
358 | "Actual Rows": 16,
359 | "Actual Loops": 1,
360 | "Output": ["cat.categoryname", "cat.category"],
361 | "Shared Hit Blocks": 0,
362 | "Shared Read Blocks": 1,
363 | "Shared Dirtied Blocks": 0,
364 | "Shared Written Blocks": 0,
365 | "Local Hit Blocks": 0,
366 | "Local Read Blocks": 0,
367 | "Local Dirtied Blocks": 0,
368 | "Local Written Blocks": 0,
369 | "Temp Read Blocks": 0,
370 | "Temp Written Blocks": 0,
371 | "I/O Read Time": 0.000,
372 | "I/O Write Time": 0.000
373 | }
374 | ]
375 | }
376 | ]
377 | }
378 | ]
379 | }
380 | ]
381 | },
382 | {
383 | "Node Type": "Hash",
384 | "Parent Relationship": "Inner",
385 | "Startup Cost": 220.00,
386 | "Total Cost": 220.00,
387 | "Plan Rows": 12000,
388 | "Plan Width": 16,
389 | "Actual Startup Time": 10.159,
390 | "Actual Total Time": 10.159,
391 | "Actual Rows": 12000,
392 | "Actual Loops": 1,
393 | "Output": ["o.netamount", "o.totalamount", "o.orderid"],
394 | "Hash Buckets": 2048,
395 | "Hash Batches": 1,
396 | "Original Hash Batches": 1,
397 | "Peak Memory Usage": 609,
398 | "Shared Hit Blocks": 2,
399 | "Shared Read Blocks": 98,
400 | "Shared Dirtied Blocks": 0,
401 | "Shared Written Blocks": 0,
402 | "Local Hit Blocks": 0,
403 | "Local Read Blocks": 0,
404 | "Local Dirtied Blocks": 0,
405 | "Local Written Blocks": 0,
406 | "Temp Read Blocks": 0,
407 | "Temp Written Blocks": 0,
408 | "I/O Read Time": 0.000,
409 | "I/O Write Time": 0.000,
410 | "Plans": [
411 | {
412 | "Node Type": "Seq Scan",
413 | "Parent Relationship": "Outer",
414 | "Relation Name": "orders",
415 | "Schema": "public",
416 | "Alias": "o",
417 | "Startup Cost": 0.00,
418 | "Total Cost": 220.00,
419 | "Plan Rows": 12000,
420 | "Plan Width": 16,
421 | "Actual Startup Time": 0.008,
422 | "Actual Total Time": 5.548,
423 | "Actual Rows": 12000,
424 | "Actual Loops": 1,
425 | "Output": ["o.netamount", "o.totalamount", "o.orderid"],
426 | "Shared Hit Blocks": 2,
427 | "Shared Read Blocks": 98,
428 | "Shared Dirtied Blocks": 0,
429 | "Shared Written Blocks": 0,
430 | "Local Hit Blocks": 0,
431 | "Local Read Blocks": 0,
432 | "Local Dirtied Blocks": 0,
433 | "Local Written Blocks": 0,
434 | "Temp Read Blocks": 0,
435 | "Temp Written Blocks": 0,
436 | "I/O Read Time": 0.000,
437 | "I/O Write Time": 0.000
438 | }
439 | ]
440 | }
441 | ]
442 | },
443 | {
444 | "Node Type": "Hash",
445 | "Parent Relationship": "Inner",
446 | "Startup Cost": 3377.25,
447 | "Total Cost": 3377.25,
448 | "Plan Rows": 60350,
449 | "Plan Width": 7,
450 | "Actual Startup Time": 95.610,
451 | "Actual Total Time": 95.610,
452 | "Actual Rows": 60350,
453 | "Actual Loops": 1,
454 | "Output": ["c.state", "ch.orderid"],
455 | "Hash Buckets": 8192,
456 | "Hash Batches": 1,
457 | "Original Hash Batches": 1,
458 | "Peak Memory Usage": 2239,
459 | "Shared Hit Blocks": 4,
460 | "Shared Read Blocks": 811,
461 | "Shared Dirtied Blocks": 0,
462 | "Shared Written Blocks": 0,
463 | "Local Hit Blocks": 0,
464 | "Local Read Blocks": 0,
465 | "Local Dirtied Blocks": 0,
466 | "Local Written Blocks": 0,
467 | "Temp Read Blocks": 0,
468 | "Temp Written Blocks": 0,
469 | "I/O Read Time": 0.000,
470 | "I/O Write Time": 0.000,
471 | "Plans": [
472 | {
473 | "Node Type": "Hash Join",
474 | "Parent Relationship": "Outer",
475 | "Join Type": "Inner",
476 | "Startup Cost": 938.00,
477 | "Total Cost": 3377.25,
478 | "Plan Rows": 60350,
479 | "Plan Width": 7,
480 | "Actual Startup Time": 24.115,
481 | "Actual Total Time": 74.639,
482 | "Actual Rows": 60350,
483 | "Actual Loops": 1,
484 | "Output": ["c.state", "ch.orderid"],
485 | "Hash Cond": "(ch.customerid = c.customerid)",
486 | "Shared Hit Blocks": 4,
487 | "Shared Read Blocks": 811,
488 | "Shared Dirtied Blocks": 0,
489 | "Shared Written Blocks": 0,
490 | "Local Hit Blocks": 0,
491 | "Local Read Blocks": 0,
492 | "Local Dirtied Blocks": 0,
493 | "Local Written Blocks": 0,
494 | "Temp Read Blocks": 0,
495 | "Temp Written Blocks": 0,
496 | "I/O Read Time": 0.000,
497 | "I/O Write Time": 0.000,
498 | "Plans": [
499 | {
500 | "Node Type": "Seq Scan",
501 | "Parent Relationship": "Outer",
502 | "Relation Name": "cust_hist",
503 | "Schema": "public",
504 | "Alias": "ch",
505 | "Startup Cost": 0.00,
506 | "Total Cost": 930.50,
507 | "Plan Rows": 60350,
508 | "Plan Width": 8,
509 | "Actual Startup Time": 0.294,
510 | "Actual Total Time": 11.812,
511 | "Actual Rows": 60350,
512 | "Actual Loops": 1,
513 | "Output": ["ch.customerid", "ch.orderid", "ch.prod_id"],
514 | "Shared Hit Blocks": 2,
515 | "Shared Read Blocks": 325,
516 | "Shared Dirtied Blocks": 0,
517 | "Shared Written Blocks": 0,
518 | "Local Hit Blocks": 0,
519 | "Local Read Blocks": 0,
520 | "Local Dirtied Blocks": 0,
521 | "Local Written Blocks": 0,
522 | "Temp Read Blocks": 0,
523 | "Temp Written Blocks": 0,
524 | "I/O Read Time": 0.000,
525 | "I/O Write Time": 0.000
526 | },
527 | {
528 | "Node Type": "Hash",
529 | "Parent Relationship": "Inner",
530 | "Startup Cost": 688.00,
531 | "Total Cost": 688.00,
532 | "Plan Rows": 20000,
533 | "Plan Width": 7,
534 | "Actual Startup Time": 23.786,
535 | "Actual Total Time": 23.786,
536 | "Actual Rows": 20000,
537 | "Actual Loops": 1,
538 | "Output": ["c.state", "c.customerid"],
539 | "Hash Buckets": 2048,
540 | "Hash Batches": 1,
541 | "Original Hash Batches": 1,
542 | "Peak Memory Usage": 743,
543 | "Shared Hit Blocks": 2,
544 | "Shared Read Blocks": 486,
545 | "Shared Dirtied Blocks": 0,
546 | "Shared Written Blocks": 0,
547 | "Local Hit Blocks": 0,
548 | "Local Read Blocks": 0,
549 | "Local Dirtied Blocks": 0,
550 | "Local Written Blocks": 0,
551 | "Temp Read Blocks": 0,
552 | "Temp Written Blocks": 0,
553 | "I/O Read Time": 0.000,
554 | "I/O Write Time": 0.000,
555 | "Plans": [
556 | {
557 | "Node Type": "Seq Scan",
558 | "Parent Relationship": "Outer",
559 | "Relation Name": "customers",
560 | "Schema": "public",
561 | "Alias": "c",
562 | "Startup Cost": 0.00,
563 | "Total Cost": 688.00,
564 | "Plan Rows": 20000,
565 | "Plan Width": 7,
566 | "Actual Startup Time": 0.005,
567 | "Actual Total Time": 16.771,
568 | "Actual Rows": 20000,
569 | "Actual Loops": 1,
570 | "Output": ["c.state", "c.customerid"],
571 | "Shared Hit Blocks": 2,
572 | "Shared Read Blocks": 486,
573 | "Shared Dirtied Blocks": 0,
574 | "Shared Written Blocks": 0,
575 | "Local Hit Blocks": 0,
576 | "Local Read Blocks": 0,
577 | "Local Dirtied Blocks": 0,
578 | "Local Written Blocks": 0,
579 | "Temp Read Blocks": 0,
580 | "Temp Written Blocks": 0,
581 | "I/O Read Time": 0.000,
582 | "I/O Write Time": 0.000
583 | }
584 | ]
585 | }
586 | ]
587 | }
588 | ]
589 | }
590 | ]
591 | }
592 | ]
593 | }
594 | ]
595 | }
596 | ]
597 | },
598 | "Planning Time": 26.171,
599 | "Triggers": [
600 | ],
601 | "Execution Time": 726.800
602 | }
603 | ]`;
604 |
605 | export var SAMPLE_QUERY = `SELECT c.state,
606 | cat.categoryname,
607 | sum(o.netamount),
608 | sum(o.totalamount)
609 | FROM customers c
610 | INNER JOIN cust_hist ch ON c.customerid = ch.customerid
611 | INNER JOIN orders o ON ch.orderid = o.orderid
612 | INNER JOIN orderlines ol ON ol.orderid = o.orderid
613 | INNER JOIN products p ON ol.prod_id = p.prod_id
614 | INNER JOIN categories cat ON p.category = cat.category
615 | GROUP BY c.state, cat.categoryname
616 | ORDER BY c.state, sum(o.totalamount) DESC
617 | LIMIT 10 OFFSET 1`;
618 |
--------------------------------------------------------------------------------
/app/components/plan-node/plan-node.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | {{getNodeName()}}
8 |
9 | {{node[_planService.ACTUAL_DURATION_PROP] | duration}}{{node[_planService.ACTUAL_DURATION_PROP] | durationUnit}}
10 | | {{executionTimePercent}}
11 | %
12 |
13 |
14 |
15 |
16 |
18 |
19 |
20 |
21 |
22 |
23 | on
24 | {{node[_planService.SCHEMA_PROP]}}. {{node[_planService.RELATION_NAME_PROP]}}
25 | ({{node[_planService.ALIAS_PROP]}})
26 |
27 |
28 |
29 | by {{node[_planService.GROUP_KEY_PROP]}}
30 |
31 | by {{node[_planService.SORT_KEY_PROP]}}
32 |
{{node[_planService.JOIN_TYPE_PROP]}}
33 | join
34 |
35 | using {{node[_planService.INDEX_NAME_PROP]}}
36 |
37 | on {{node[_planService.HASH_CONDITION_PROP]}}
38 |
39 | CTE {{node[_planService.CTE_NAME_PROP]}}
40 |
41 |
42 |
43 |
0">
44 | {{getTagName(tag)}}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{viewOptions.highlightType}}: {{highlightValue | number:'.0-2'}}
53 |
54 |
55 |
56 |
57 | over estimated rows
58 | under estimated rows
59 | by {{plannerRowEstimateValue | number}} x
60 |
61 |
62 |
63 |
64 | {{node[_planService.NODE_TYPE_PROP]}} Node
65 |
66 |
67 |
68 |
69 | {{prop.key}}
70 | {{prop.value}}
71 |
72 |
73 |
*Pev calculated value
74 |
75 |
76 |
77 |
78 |
79 |
80 |
query
81 |
82 |
83 |
84 |
85 |
90 |
--------------------------------------------------------------------------------
/app/components/plan-node/plan-node.ts:
--------------------------------------------------------------------------------
1 | import {IPlan} from '../../interfaces/iplan';
2 | import {Component, OnInit} from 'angular2/core';
3 | import {HighlightType, EstimateDirection, ViewMode} from '../../enums';
4 | import {DurationPipe, DurationUnitPipe} from '../../pipes';
5 |
6 | import {PlanService} from '../../services/plan-service';
7 | import {SyntaxHighlightService} from '../../services/syntax-highlight-service';
8 | import {HelpService} from '../../services/help-service';
9 | import {ColorService} from '../../services/color-service';
10 |
11 | ///
12 |
13 | @Component({
14 | selector: 'plan-node',
15 | inputs: ['plan', 'node', 'viewOptions'],
16 | templateUrl: './components/plan-node/plan-node.html',
17 | directives: [PlanNode],
18 | providers: [PlanService, SyntaxHighlightService, HelpService, ColorService],
19 | pipes: [DurationPipe, DurationUnitPipe]
20 | })
21 |
22 | export class PlanNode {
23 | // consts
24 | NORMAL_WIDTH: number = 220;
25 | COMPACT_WIDTH: number = 140;
26 | DOT_WIDTH: number = 30;
27 | EXPANDED_WIDTH: number = 400;
28 |
29 | MIN_ESTIMATE_MISS: number = 100;
30 | COSTLY_TAG: string = 'costliest';
31 | SLOW_TAG: string = 'slowest';
32 | LARGE_TAG: string = 'largest';
33 | ESTIMATE_TAG: string = 'bad estimate';
34 |
35 | // inputs
36 | plan: IPlan;
37 | node: any;
38 | viewOptions: any;
39 |
40 | // UI flags
41 | showDetails: boolean;
42 |
43 | // calculated properties
44 | executionTimePercent: number;
45 | backgroundColor: string;
46 | highlightValue: number;
47 | barContainerWidth: number;
48 | barWidth: number;
49 | props: Array;
50 | tags: Array;
51 | plannerRowEstimateValue: number;
52 | plannerRowEstimateDirection: EstimateDirection;
53 |
54 | // required for custom change detection
55 | currentHighlightType: string;
56 | currentCompactView: boolean;
57 | currentExpandedView: boolean;
58 |
59 | // expose enum to view
60 | estimateDirections = EstimateDirection;
61 | highlightTypes = HighlightType;
62 | viewModes = ViewMode;
63 |
64 | constructor(private _planService: PlanService,
65 | private _syntaxHighlightService: SyntaxHighlightService,
66 | private _helpService: HelpService,
67 | private _colorService: ColorService) { }
68 |
69 | ngOnInit() {
70 | this.currentHighlightType = this.viewOptions.highlightType;
71 | this.calculateBar();
72 | this.calculateProps();
73 | this.calculateDuration();
74 | this.calculateTags();
75 |
76 | this.plannerRowEstimateDirection = this.node[this._planService.PLANNER_ESIMATE_DIRECTION];
77 | this.plannerRowEstimateValue = _.round(this.node[this._planService.PLANNER_ESTIMATE_FACTOR]);
78 | }
79 |
80 | ngDoCheck() {
81 | if (this.currentHighlightType !== this.viewOptions.highlightType) {
82 | this.currentHighlightType = this.viewOptions.highlightType;
83 | this.calculateBar();
84 | }
85 |
86 | if (this.currentCompactView !== this.viewOptions.showCompactView) {
87 | this.currentCompactView = this.viewOptions.showCompactView;
88 | this.calculateBar();
89 | }
90 |
91 | if (this.currentExpandedView !== this.showDetails) {
92 | this.currentExpandedView = this.showDetails;
93 | this.calculateBar();
94 | }
95 | }
96 |
97 | getFormattedQuery() {
98 | var keyItems: Array = [];
99 |
100 | // relation name will be highlighted for SCAN nodes
101 | var relationName: string = this.node[this._planService.RELATION_NAME_PROP];
102 | if (relationName) {
103 | keyItems.push(this.node[this._planService.SCHEMA_PROP] + '.' + relationName);
104 | keyItems.push(' ' + relationName);
105 | keyItems.push(' ' + this.node[this._planService.ALIAS_PROP] + ' ');
106 | }
107 |
108 | // group key will be highlighted for AGGREGATE nodes
109 | var groupKey: Array = this.node[this._planService.GROUP_KEY_PROP];
110 | if (groupKey) {
111 | keyItems.push('GROUP BY ' + groupKey.join(','));
112 | }
113 |
114 | // hash condition will be highlighted for HASH JOIN nodes
115 | var hashCondition: string = this.node[this._planService.HASH_CONDITION_PROP];
116 | if (hashCondition) {
117 | keyItems.push(hashCondition.replace('(', '').replace(')', ''));
118 | }
119 |
120 | if (this.node[this._planService.NODE_TYPE_PROP].toUpperCase() === 'LIMIT') {
121 | keyItems.push('LIMIT');
122 | }
123 | return this._syntaxHighlightService.highlight(this.plan.query, keyItems);
124 | }
125 |
126 | calculateBar() {
127 | switch (this.viewOptions.viewMode) {
128 | case ViewMode.DOT:
129 | this.barContainerWidth = this.DOT_WIDTH;
130 | break;
131 | case ViewMode.COMPACT:
132 | this.barContainerWidth = this.COMPACT_WIDTH;
133 | break;
134 | default:
135 | this.barContainerWidth = this.NORMAL_WIDTH;
136 | break;
137 | }
138 |
139 | // expanded view width trumps others
140 | if (this.currentExpandedView) {
141 | this.barContainerWidth = this.EXPANDED_WIDTH;
142 | }
143 |
144 | switch (this.currentHighlightType) {
145 | case HighlightType.DURATION:
146 | this.highlightValue = (this.node[this._planService.ACTUAL_DURATION_PROP]);
147 | this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxDuration) * this.barContainerWidth);
148 | break;
149 | case HighlightType.ROWS:
150 | this.highlightValue = (this.node[this._planService.ACTUAL_ROWS_PROP]);
151 | this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxRows) * this.barContainerWidth);
152 | break;
153 | case HighlightType.COST:
154 | this.highlightValue = (this.node[this._planService.ACTUAL_COST_PROP]);
155 | this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxCost) * this.barContainerWidth);
156 | break;
157 | }
158 |
159 | if (this.barWidth < 1) {
160 | this.barWidth = 1;
161 | }
162 |
163 | this.backgroundColor = this._colorService.numberToColorHsl(1 - this.barWidth / this.barContainerWidth);
164 | }
165 |
166 | calculateDuration() {
167 | this.executionTimePercent = (_.round((this.node[this._planService.ACTUAL_DURATION_PROP] / this.plan.planStats.executionTime) * 100));
168 | }
169 |
170 | // create an array of node propeties so that they can be displayed in the view
171 | calculateProps() {
172 | this.props = _.chain(this.node)
173 | .omit(this._planService.PLANS_PROP)
174 | .map((value, key) => {
175 | return { key: key, value: value };
176 | })
177 | .value();
178 | }
179 |
180 | calculateTags() {
181 | this.tags = [];
182 | if (this.node[this._planService.SLOWEST_NODE_PROP]) {
183 | this.tags.push(this.SLOW_TAG);
184 | }
185 | if (this.node[this._planService.COSTLIEST_NODE_PROP]) {
186 | this.tags.push(this.COSTLY_TAG);
187 | }
188 | if (this.node[this._planService.LARGEST_NODE_PROP]) {
189 | this.tags.push(this.LARGE_TAG);
190 | }
191 | if (this.node[this._planService.PLANNER_ESTIMATE_FACTOR] >= this.MIN_ESTIMATE_MISS) {
192 | this.tags.push(this.ESTIMATE_TAG);
193 | }
194 | }
195 |
196 | getNodeTypeDescription() {
197 | return this._helpService.getNodeTypeDescription(this.node[this._planService.NODE_TYPE_PROP]);
198 | }
199 |
200 | getNodeName() {
201 | if (this.viewOptions.viewMode === ViewMode.DOT && !this.showDetails) {
202 | return this.node[this._planService.NODE_TYPE_PROP].replace(/[^A-Z]/g, '').toUpperCase();
203 | }
204 |
205 | return (this.node[this._planService.NODE_TYPE_PROP]).toUpperCase();
206 | }
207 |
208 | getTagName(tagName: String) {
209 | if (this.viewOptions.viewMode === ViewMode.DOT && !this.showDetails) {
210 | return tagName.charAt(0);
211 | }
212 | return tagName;
213 | }
214 |
215 | shouldShowPlannerEstimate() {
216 | if (this.viewOptions.showPlannerEstimate && this.showDetails) {
217 | return true;
218 | }
219 |
220 | if (this.viewOptions.viewMode === ViewMode.DOT) {
221 | return false;
222 | }
223 |
224 | return this.viewOptions.showPlannerEstimate;
225 | }
226 |
227 | shouldShowNodeBarLabel() {
228 | if (this.showDetails) {
229 | return true;
230 | }
231 |
232 | if (this.viewOptions.viewMode === ViewMode.DOT) {
233 | return false;
234 | }
235 |
236 | return true;
237 | }
238 | }
239 |
--------------------------------------------------------------------------------
/app/components/plan-view/plan-view.html:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
{{plan.name}}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | {{plan.planStats.executionTime | duration}}
52 | execution time ({{plan.planStats.executionTime | durationUnit}})
53 |
54 |
55 | {{plan.planStats.planningTime | number:'.0-2'}}
56 | planning time (ms)
57 |
58 |
59 | {{plan.planStats.maxDuration | duration}}
60 | slowest node ({{plan.planStats.maxDuration | durationUnit}})
61 |
62 |
63 | {{plan.planStats.maxRows | number:'.0-2'}}
64 | largest node (rows)
65 |
66 |
67 | {{plan.planStats.maxCost | number:'.0-2'}}
68 | costliest node
69 |
70 |
71 |
72 |
73 |
80 |
81 |
--------------------------------------------------------------------------------
/app/components/plan-view/plan-view.ts:
--------------------------------------------------------------------------------
1 | import {Component, OnInit} from 'angular2/core';
2 | import {ROUTER_DIRECTIVES, RouteParams} from 'angular2/router';
3 |
4 | import {IPlan} from '../../interfaces/iplan';
5 | import {HighlightType, ViewMode} from '../../enums';
6 | import {PlanNode} from '../plan-node/plan-node';
7 | import {PlanService} from '../../services/plan-service';
8 | import {SyntaxHighlightService} from '../../services/syntax-highlight-service';
9 | import {DurationPipe, DurationUnitPipe} from '../../pipes';
10 |
11 | @Component({
12 | selector: 'plan-view',
13 | templateUrl: './components/plan-view/plan-view.html',
14 | directives: [ROUTER_DIRECTIVES, PlanNode],
15 | providers: [PlanService, SyntaxHighlightService],
16 | pipes: [DurationPipe, DurationUnitPipe]
17 | })
18 | export class PlanView {
19 | id: string;
20 | plan: IPlan;
21 | rootContainer: any;
22 | hideMenu: boolean = true;
23 |
24 | viewOptions: any = {
25 | showPlanStats: true,
26 | showHighlightBar: true,
27 | showPlannerEstimate: false,
28 | showTags: true,
29 | highlightType: HighlightType.NONE,
30 | viewMode: ViewMode.FULL
31 | };
32 |
33 | showPlannerEstimate: boolean = true;
34 | showMenu: boolean = false;
35 |
36 | highlightTypes = HighlightType; // exposing the enum to the view
37 | viewModes = ViewMode;
38 |
39 | constructor(private _planService: PlanService, routeParams: RouteParams) {
40 | this.id = routeParams.get('id');
41 | }
42 |
43 | getPlan() {
44 | if (!this.id) {
45 | return;
46 | }
47 |
48 | this.plan = this._planService.getPlan(this.id);
49 | this.rootContainer = this.plan.content;
50 | this.plan.planStats = {
51 | executionTime: this.rootContainer['Execution Time'] || this.rootContainer['Total Runtime'],
52 | planningTime: this.rootContainer['Planning Time'] || 0,
53 | maxRows: this.rootContainer[this._planService.MAXIMUM_ROWS_PROP] || 0,
54 | maxCost: this.rootContainer[this._planService.MAXIMUM_COSTS_PROP] || 0,
55 | maxDuration: this.rootContainer[this._planService.MAXIMUM_DURATION_PROP] || 0
56 | };
57 | }
58 |
59 | ngOnInit() {
60 | this.getPlan();
61 | }
62 |
63 | toggleHighlight(type: HighlightType) {
64 | this.viewOptions.highlightType = type;
65 | }
66 |
67 | analyzePlan() {
68 | this._planService.analyzePlan(this.plan);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/app/enums.ts:
--------------------------------------------------------------------------------
1 | export class HighlightType {
2 | static NONE: string = 'none';
3 | static DURATION: string = 'duration';
4 | static ROWS: string = 'rows';
5 | static COST: string = 'cost';
6 | }
7 |
8 | export enum EstimateDirection {
9 | over,
10 | under
11 | }
12 |
13 | export class ViewMode {
14 | static FULL: string = 'full';
15 | static COMPACT: string = 'compact';
16 | static DOT: string = 'dot';
17 | }
18 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= APP_TITLE %>
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/interfaces/iplan.ts:
--------------------------------------------------------------------------------
1 | export interface IPlan {
2 | id: string;
3 | name: string;
4 | content: any;
5 | query: string;
6 | createdOn: Date;
7 | planStats: any;
8 | formattedQuery: string;
9 | }
10 |
--------------------------------------------------------------------------------
/app/pipes.ts:
--------------------------------------------------------------------------------
1 | import {Pipe} from 'angular2/core';
2 | ///
3 |
4 | @Pipe({ name: 'momentDate' })
5 | export class MomentDatePipe {
6 | transform(value: string, args: string[]): any {
7 | return moment(value).format('LLL');
8 | }
9 | }
10 |
11 | @Pipe({ name: 'duration' })
12 | export class DurationPipe {
13 | transform(value: number): string {
14 | var duration: string = '';
15 |
16 | if (value < 1) {
17 | duration = '<1';
18 | } else if (value > 1 && value < 1000) {
19 | duration = _.round(value, 2).toString();
20 | } else if (value >= 1000 && value < 60000) {
21 | duration = _.round(value / 1000, 2).toString();
22 | } else if (value >= 60000) {
23 | duration = _.round(value / 60000, 2).toString();
24 | }
25 | return duration;
26 | }
27 | }
28 |
29 | @Pipe({ name: 'durationUnit' })
30 | export class DurationUnitPipe {
31 | transform(value: number) {
32 | var unit: string = '';
33 |
34 | if (value < 1) {
35 | unit = 'ms';
36 | } else if (value > 1 && value < 1000) {
37 | unit = 'ms';
38 | } else if (value >= 1000 && value < 60000) {
39 | unit = 's';
40 | } else if (value >= 60000) {
41 | unit = 'min';
42 | }
43 | return unit;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/sample-plans/plan1.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Plan": {
4 | "Node Type": "Limit",
5 | "Startup Cost": 60970.08,
6 | "Total Cost": 60970.11,
7 | "Plan Rows": 10,
8 | "Plan Width": 468,
9 | "Actual Startup Time": 1941.751,
10 | "Actual Total Time": 1941.780,
11 | "Actual Rows": 10,
12 | "Actual Loops": 1,
13 | "Output": ["a.id", "(COALESCE(a.display_name, a.alert_name))", "a.external_alert_id", "a.venue_code", "altt.code", "altt.name", "altst.name", "a.stop_type", "a.deploy_mode", "a.estimated_cost", "(sum(s.total_alerts))", "(sum(s.total_followed))", "(sum(s.total_overridden))", "(sum(s.total_ignored))", "(sum(s.total_not_seen))", "(sum(s.total_unknown))", "(percenttotal((sum(s.total_followed)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_overridden)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_ignored)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_not_seen)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_unknown)), (sum(s.total_alerts))))", "(((sum(s.total_alerts)) * a.estimated_cost))"],
14 | "Shared Hit Blocks": 260587,
15 | "Shared Read Blocks": 0,
16 | "Shared Dirtied Blocks": 0,
17 | "Shared Written Blocks": 0,
18 | "Local Hit Blocks": 0,
19 | "Local Read Blocks": 0,
20 | "Local Dirtied Blocks": 0,
21 | "Local Written Blocks": 0,
22 | "Temp Read Blocks": 0,
23 | "Temp Written Blocks": 0,
24 | "I/O Read Time": 0.000,
25 | "I/O Write Time": 0.000,
26 | "Plans": [
27 | {
28 | "Node Type": "Sort",
29 | "Parent Relationship": "Outer",
30 | "Startup Cost": 60970.08,
31 | "Total Cost": 60977.40,
32 | "Plan Rows": 2927,
33 | "Plan Width": 468,
34 | "Actual Startup Time": 1941.747,
35 | "Actual Total Time": 1941.757,
36 | "Actual Rows": 10,
37 | "Actual Loops": 1,
38 | "Output": ["a.id", "(COALESCE(a.display_name, a.alert_name))", "a.external_alert_id", "a.venue_code", "altt.code", "altt.name", "altst.name", "a.stop_type", "a.deploy_mode", "a.estimated_cost", "(sum(s.total_alerts))", "(sum(s.total_followed))", "(sum(s.total_overridden))", "(sum(s.total_ignored))", "(sum(s.total_not_seen))", "(sum(s.total_unknown))", "(percenttotal((sum(s.total_followed)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_overridden)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_ignored)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_not_seen)), (sum(s.total_alerts))))", "(percenttotal((sum(s.total_unknown)), (sum(s.total_alerts))))", "(((sum(s.total_alerts)) * a.estimated_cost))"],
39 | "Sort Key": ["(sum(s.total_alerts))"],
40 | "Sort Method": "top-N heapsort",
41 | "Sort Space Used": 27,
42 | "Sort Space Type": "Memory",
43 | "Shared Hit Blocks": 260587,
44 | "Shared Read Blocks": 0,
45 | "Shared Dirtied Blocks": 0,
46 | "Shared Written Blocks": 0,
47 | "Local Hit Blocks": 0,
48 | "Local Read Blocks": 0,
49 | "Local Dirtied Blocks": 0,
50 | "Local Written Blocks": 0,
51 | "Temp Read Blocks": 0,
52 | "Temp Written Blocks": 0,
53 | "I/O Read Time": 0.000,
54 | "I/O Write Time": 0.000,
55 | "Plans": [
56 | {
57 | "Node Type": "Nested Loop",
58 | "Parent Relationship": "Outer",
59 | "Join Type": "Left",
60 | "Startup Cost": 37552.36,
61 | "Total Cost": 60906.83,
62 | "Plan Rows": 2927,
63 | "Plan Width": 468,
64 | "Actual Startup Time": 522.250,
65 | "Actual Total Time": 1865.979,
66 | "Actual Rows": 32352,
67 | "Actual Loops": 1,
68 | "Output": ["a.id", "COALESCE(a.display_name, a.alert_name)", "a.external_alert_id", "a.venue_code", "altt.code", "altt.name", "altst.name", "a.stop_type", "a.deploy_mode", "a.estimated_cost", "(sum(s.total_alerts))", "(sum(s.total_followed))", "(sum(s.total_overridden))", "(sum(s.total_ignored))", "(sum(s.total_not_seen))", "(sum(s.total_unknown))", "percenttotal((sum(s.total_followed)), (sum(s.total_alerts)))", "percenttotal((sum(s.total_overridden)), (sum(s.total_alerts)))", "percenttotal((sum(s.total_ignored)), (sum(s.total_alerts)))", "percenttotal((sum(s.total_not_seen)), (sum(s.total_alerts)))", "percenttotal((sum(s.total_unknown)), (sum(s.total_alerts)))", "((sum(s.total_alerts)) * a.estimated_cost)"],
69 | "Shared Hit Blocks": 260587,
70 | "Shared Read Blocks": 0,
71 | "Shared Dirtied Blocks": 0,
72 | "Shared Written Blocks": 0,
73 | "Local Hit Blocks": 0,
74 | "Local Read Blocks": 0,
75 | "Local Dirtied Blocks": 0,
76 | "Local Written Blocks": 0,
77 | "Temp Read Blocks": 0,
78 | "Temp Written Blocks": 0,
79 | "I/O Read Time": 0.000,
80 | "I/O Write Time": 0.000,
81 | "Plans": [
82 | {
83 | "Node Type": "Nested Loop",
84 | "Parent Relationship": "Outer",
85 | "Join Type": "Left",
86 | "Startup Cost": 37552.22,
87 | "Total Cost": 56750.41,
88 | "Plan Rows": 2927,
89 | "Plan Width": 461,
90 | "Actual Startup Time": 522.141,
91 | "Actual Total Time": 1161.187,
92 | "Actual Rows": 32352,
93 | "Actual Loops": 1,
94 | "Output": ["(sum(s.total_alerts))", "(sum(s.total_followed))", "(sum(s.total_overridden))", "(sum(s.total_ignored))", "(sum(s.total_not_seen))", "(sum(s.total_unknown))", "a.id", "a.display_name", "a.alert_name", "a.external_alert_id", "a.venue_code", "a.stop_type", "a.deploy_mode", "a.estimated_cost", "a.subtype_id", "altt.code", "altt.name"],
95 | "Shared Hit Blocks": 197115,
96 | "Shared Read Blocks": 0,
97 | "Shared Dirtied Blocks": 0,
98 | "Shared Written Blocks": 0,
99 | "Local Hit Blocks": 0,
100 | "Local Read Blocks": 0,
101 | "Local Dirtied Blocks": 0,
102 | "Local Written Blocks": 0,
103 | "Temp Read Blocks": 0,
104 | "Temp Written Blocks": 0,
105 | "I/O Read Time": 0.000,
106 | "I/O Write Time": 0.000,
107 | "Plans": [
108 | {
109 | "Node Type": "Nested Loop",
110 | "Parent Relationship": "Outer",
111 | "Join Type": "Inner",
112 | "Startup Cost": 37552.07,
113 | "Total Cost": 56230.56,
114 | "Plan Rows": 2927,
115 | "Plan Width": 405,
116 | "Actual Startup Time": 522.131,
117 | "Actual Total Time": 918.895,
118 | "Actual Rows": 32352,
119 | "Actual Loops": 1,
120 | "Output": ["(sum(s.total_alerts))", "(sum(s.total_followed))", "(sum(s.total_overridden))", "(sum(s.total_ignored))", "(sum(s.total_not_seen))", "(sum(s.total_unknown))", "a.id", "a.display_name", "a.alert_name", "a.external_alert_id", "a.venue_code", "a.stop_type", "a.deploy_mode", "a.estimated_cost", "a.type_id", "a.subtype_id"],
121 | "Shared Hit Blocks": 132411,
122 | "Shared Read Blocks": 0,
123 | "Shared Dirtied Blocks": 0,
124 | "Shared Written Blocks": 0,
125 | "Local Hit Blocks": 0,
126 | "Local Read Blocks": 0,
127 | "Local Dirtied Blocks": 0,
128 | "Local Written Blocks": 0,
129 | "Temp Read Blocks": 0,
130 | "Temp Written Blocks": 0,
131 | "I/O Read Time": 0.000,
132 | "I/O Write Time": 0.000,
133 | "Plans": [
134 | {
135 | "Node Type": "Aggregate",
136 | "Strategy": "Hashed",
137 | "Parent Relationship": "Outer",
138 | "Startup Cost": 37551.64,
139 | "Total Cost": 37624.82,
140 | "Plan Rows": 2927,
141 | "Plan Width": 56,
142 | "Actual Startup Time": 522.107,
143 | "Actual Total Time": 587.536,
144 | "Actual Rows": 32352,
145 | "Actual Loops": 1,
146 | "Output": ["s.alert_id", "sum(s.total_alerts)", "sum(s.total_followed)", "sum(s.total_overridden)", "sum(s.total_ignored)", "sum(s.total_not_seen)", "sum(s.total_unknown)"],
147 | "Group Key": ["s.alert_id"],
148 | "Shared Hit Blocks": 2784,
149 | "Shared Read Blocks": 0,
150 | "Shared Dirtied Blocks": 0,
151 | "Shared Written Blocks": 0,
152 | "Local Hit Blocks": 0,
153 | "Local Read Blocks": 0,
154 | "Local Dirtied Blocks": 0,
155 | "Local Written Blocks": 0,
156 | "Temp Read Blocks": 0,
157 | "Temp Written Blocks": 0,
158 | "I/O Read Time": 0.000,
159 | "I/O Write Time": 0.000,
160 | "Plans": [
161 | {
162 | "Node Type": "Bitmap Heap Scan",
163 | "Parent Relationship": "Outer",
164 | "Relation Name": "alert_daily_summaries",
165 | "Schema": "analytics",
166 | "Alias": "s",
167 | "Startup Cost": 3265.35,
168 | "Total Cost": 35524.50,
169 | "Plan Rows": 115837,
170 | "Plan Width": 56,
171 | "Actual Startup Time": 10.745,
172 | "Actual Total Time": 165.318,
173 | "Actual Rows": 140451,
174 | "Actual Loops": 1,
175 | "Output": ["s.id", "s.org_id", "s.alert_id", "s.alert_day", "s.total_alerts", "s.total_followed", "s.total_overridden", "s.total_ignored", "s.total_not_seen", "s.total_unknown", "s.override_comments"],
176 | "Recheck Cond": "((s.org_id = 2) AND (s.alert_day >= '2015-10-01 00:00:00'::timestamp without time zone) AND (s.alert_day <= '2015-12-01 00:00:00'::timestamp without time zone))",
177 | "Rows Removed by Index Recheck": 0,
178 | "Exact Heap Blocks": 2243,
179 | "Lossy Heap Blocks": 0,
180 | "Shared Hit Blocks": 2784,
181 | "Shared Read Blocks": 0,
182 | "Shared Dirtied Blocks": 0,
183 | "Shared Written Blocks": 0,
184 | "Local Hit Blocks": 0,
185 | "Local Read Blocks": 0,
186 | "Local Dirtied Blocks": 0,
187 | "Local Written Blocks": 0,
188 | "Temp Read Blocks": 0,
189 | "Temp Written Blocks": 0,
190 | "I/O Read Time": 0.000,
191 | "I/O Write Time": 0.000,
192 | "Plans": [
193 | {
194 | "Node Type": "Bitmap Index Scan",
195 | "Parent Relationship": "Outer",
196 | "Index Name": "alert_daily_summaries_org_id_alert_day_idx",
197 | "Startup Cost": 0.00,
198 | "Total Cost": 3236.39,
199 | "Plan Rows": 115837,
200 | "Plan Width": 0,
201 | "Actual Startup Time": 10.445,
202 | "Actual Total Time": 10.445,
203 | "Actual Rows": 140451,
204 | "Actual Loops": 1,
205 | "Index Cond": "((s.org_id = 2) AND (s.alert_day >= '2015-10-01 00:00:00'::timestamp without time zone) AND (s.alert_day <= '2015-12-01 00:00:00'::timestamp without time zone))",
206 | "Shared Hit Blocks": 541,
207 | "Shared Read Blocks": 0,
208 | "Shared Dirtied Blocks": 0,
209 | "Shared Written Blocks": 0,
210 | "Local Hit Blocks": 0,
211 | "Local Read Blocks": 0,
212 | "Local Dirtied Blocks": 0,
213 | "Local Written Blocks": 0,
214 | "Temp Read Blocks": 0,
215 | "Temp Written Blocks": 0,
216 | "I/O Read Time": 0.000,
217 | "I/O Write Time": 0.000
218 | }
219 | ]
220 | }
221 | ]
222 | },
223 | {
224 | "Node Type": "Index Scan",
225 | "Parent Relationship": "Inner",
226 | "Scan Direction": "Forward",
227 | "Index Name": "alerts_pkey1",
228 | "Relation Name": "alerts",
229 | "Schema": "analytics",
230 | "Alias": "a",
231 | "Startup Cost": 0.42,
232 | "Total Cost": 6.34,
233 | "Plan Rows": 1,
234 | "Plan Width": 213,
235 | "Actual Startup Time": 0.004,
236 | "Actual Total Time": 0.005,
237 | "Actual Rows": 1,
238 | "Actual Loops": 32352,
239 | "Output": ["a.id", "a.org_id", "a.external_alert_id", "a.alert_name", "a.display_name", "a.estimated_cost", "a.description", "a.source", "a.venue_code", "a.action_expected", "a.cancel_expected", "a.responsible_provider_type", "a.status", "a.version", "a.created_by", "a.created_date", "a.modified_by", "a.modified_date", "a.tracking_id", "a.record_date", "a.released_date", "a.released", "a.comments", "a.default_lockout_hours", "a.deploy_mode", "a.stop_type", "a.importance_level", "a.type_id", "a.subtype_id"],
240 | "Index Cond": "(a.id = s.alert_id)",
241 | "Rows Removed by Index Recheck": 0,
242 | "Shared Hit Blocks": 129627,
243 | "Shared Read Blocks": 0,
244 | "Shared Dirtied Blocks": 0,
245 | "Shared Written Blocks": 0,
246 | "Local Hit Blocks": 0,
247 | "Local Read Blocks": 0,
248 | "Local Dirtied Blocks": 0,
249 | "Local Written Blocks": 0,
250 | "Temp Read Blocks": 0,
251 | "Temp Written Blocks": 0,
252 | "I/O Read Time": 0.000,
253 | "I/O Write Time": 0.000
254 | }
255 | ]
256 | },
257 | {
258 | "Node Type": "Index Scan",
259 | "Parent Relationship": "Inner",
260 | "Scan Direction": "Forward",
261 | "Index Name": "alert_types_id_org_id_key",
262 | "Relation Name": "alert_types",
263 | "Schema": "analytics",
264 | "Alias": "altt",
265 | "Startup Cost": 0.15,
266 | "Total Cost": 0.17,
267 | "Plan Rows": 1,
268 | "Plan Width": 72,
269 | "Actual Startup Time": 0.002,
270 | "Actual Total Time": 0.003,
271 | "Actual Rows": 1,
272 | "Actual Loops": 32352,
273 | "Output": ["altt.id", "altt.version", "altt.code", "altt.name", "altt.org_id"],
274 | "Index Cond": "(a.type_id = altt.id)",
275 | "Rows Removed by Index Recheck": 0,
276 | "Shared Hit Blocks": 64704,
277 | "Shared Read Blocks": 0,
278 | "Shared Dirtied Blocks": 0,
279 | "Shared Written Blocks": 0,
280 | "Local Hit Blocks": 0,
281 | "Local Read Blocks": 0,
282 | "Local Dirtied Blocks": 0,
283 | "Local Written Blocks": 0,
284 | "Temp Read Blocks": 0,
285 | "Temp Written Blocks": 0,
286 | "I/O Read Time": 0.000,
287 | "I/O Write Time": 0.000
288 | }
289 | ]
290 | },
291 | {
292 | "Node Type": "Index Scan",
293 | "Parent Relationship": "Inner",
294 | "Scan Direction": "Forward",
295 | "Index Name": "alert_subtypes_id_org_id_key",
296 | "Relation Name": "alert_subtypes",
297 | "Schema": "analytics",
298 | "Alias": "altst",
299 | "Startup Cost": 0.14,
300 | "Total Cost": 0.16,
301 | "Plan Rows": 1,
302 | "Plan Width": 23,
303 | "Actual Startup Time": 0.002,
304 | "Actual Total Time": 0.003,
305 | "Actual Rows": 1,
306 | "Actual Loops": 32352,
307 | "Output": ["altst.id", "altst.version", "altst.code", "altst.name", "altst.org_id"],
308 | "Index Cond": "(a.subtype_id = altst.id)",
309 | "Rows Removed by Index Recheck": 0,
310 | "Shared Hit Blocks": 63472,
311 | "Shared Read Blocks": 0,
312 | "Shared Dirtied Blocks": 0,
313 | "Shared Written Blocks": 0,
314 | "Local Hit Blocks": 0,
315 | "Local Read Blocks": 0,
316 | "Local Dirtied Blocks": 0,
317 | "Local Written Blocks": 0,
318 | "Temp Read Blocks": 0,
319 | "Temp Written Blocks": 0,
320 | "I/O Read Time": 0.000,
321 | "I/O Write Time": 0.000
322 | }
323 | ]
324 | }
325 | ]
326 | }
327 | ]
328 | },
329 | "Planning Time": 0.507,
330 | "Triggers": [
331 | ],
332 | "Execution Time": 1953.684
333 | }
334 | ]
335 |
--------------------------------------------------------------------------------
/app/services/color-service.ts:
--------------------------------------------------------------------------------
1 | export class ColorService {
2 | /**
3 | * http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion
4 | *
5 | * Converts an HSL color value to RGB. Conversion formula
6 | * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
7 | * Assumes h, s, and l are contained in the set [0, 1] and
8 | * returns r, g, and b in the set [0, 255].
9 | *
10 | * @param Number h The hue
11 | * @param Number s The saturation
12 | * @param Number l The lightness
13 | * @return Array The RGB representation
14 | */
15 | hslToRgb(h, s, l) {
16 | var r, g, b;
17 |
18 | if (s === 0) {
19 | r = g = b = l; // achromatic
20 | } else {
21 | function hue2rgb(p, q, t) {
22 | if (t < 0) t += 1;
23 | if (t > 1) t -= 1;
24 | if (t < 1 / 6) return p + (q - p) * 6 * t;
25 | if (t < 1 / 2) return q;
26 | if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
27 | return p;
28 | }
29 |
30 | var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
31 | var p = 2 * l - q;
32 | r = hue2rgb(p, q, h + 1 / 3);
33 | g = hue2rgb(p, q, h);
34 | b = hue2rgb(p, q, h - 1 / 3);
35 | }
36 |
37 | return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)];
38 | }
39 |
40 | // convert a number to a color using hsl
41 | numberToColorHsl(i) {
42 | // as the function expects a value between 0 and 1, and red = 0° and green = 120°
43 | // we convert the input to the appropriate hue value
44 | var hue = i * 100 * 1.2 / 360;
45 | // we convert hsl to rgb (saturation 100%, lightness 50%)
46 | var rgb = this.hslToRgb(hue, .9, .4);
47 | // we format to css value and return
48 | return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/services/help-service.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | export class HelpService {
4 | getNodeTypeDescription(nodeType: string) {
5 | return NODE_DESCRIPTIONS[nodeType.toUpperCase()];
6 | }
7 | }
8 |
9 | export var NODE_DESCRIPTIONS = {
10 | 'LIMIT':'returns a specified number of rows from a record set.',
11 | 'SORT': 'sorts a record set based on the specified sort key.',
12 | 'NESTED LOOP': `merges two record sets by looping through every record in the first set and
13 | trying to find a match in the second set. All matching records are returned.`,
14 | 'MERGE JOIN': `merges two record sets by first sorting them on a join key .`,
15 | 'HASH': `generates a hash table from the records in the input recordset. Hash is used by
16 | Hash Join .`,
17 | 'HASH JOIN': `joins to record sets by hashing one of them (using a Hash Scan).`,
18 | 'AGGREGATE': `groups records together based on a GROUP BY or aggregate function (like sum()
).`,
19 | 'HASHAGGREGATE': `groups records together based on a GROUP BY or aggregate function (like sum()). Hash Aggregate uses
20 | a hash to first organize the records by a key.`,
21 | 'SEQ SCAN': `finds relevant records by sequentially scanning the input record set. When reading from a table,
22 | Seq Scans (unlike Index Scans) perform a single read operation (only the table is read).`,
23 | 'INDEX SCAN': `finds relevant records based on an Index . Index Scans perform 2 read operations: one to
24 | read the index and another to read the actual value from the table.`,
25 | 'INDEX ONLY SCAN': `finds relevant records based on an Index . Index Only Scans perform a single read operation
26 | from the index and do not read from the corresponding table.`,
27 | 'BITMAP HEAP SCAN': 'searches through the pages returned by the Bitmap Index Scan for relevant rows.',
28 | 'BITMAP INDEX SCAN': `uses a Bitmap Index (index which uses 1 bit per page) to find all relevant pages.
29 | Results of this node are fed to the Bitmap Heap Scan .`,
30 | 'CTE SCAN': `performs a sequential scan of Common Table Expression (CTE) query results. Note that
31 | results of a CTE are materialized (calculated and temporarily stored).`
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/app/services/plan-service.ts:
--------------------------------------------------------------------------------
1 | import {IPlan} from '../interfaces/iplan';
2 | import {EstimateDirection} from '../enums';
3 | ///
4 | ///
5 |
6 | export class PlanService {
7 | // plan property keys
8 | NODE_TYPE_PROP: string = 'Node Type';
9 | ACTUAL_ROWS_PROP: string = 'Actual Rows';
10 | PLAN_ROWS_PROP: string = 'Plan Rows';
11 | ACTUAL_TOTAL_TIME_PROP: string = 'Actual Total Time';
12 | ACTUAL_LOOPS_PROP: string = 'Actual Loops';
13 | TOTAL_COST_PROP: string = 'Total Cost';
14 | PLANS_PROP: string = 'Plans';
15 | RELATION_NAME_PROP: string = 'Relation Name';
16 | SCHEMA_PROP: string = 'Schema';
17 | ALIAS_PROP: string = 'Alias';
18 | GROUP_KEY_PROP: string = 'Group Key';
19 | SORT_KEY_PROP: string = 'Sort Key';
20 | JOIN_TYPE_PROP: string = 'Join Type';
21 | INDEX_NAME_PROP: string = 'Index Name';
22 | HASH_CONDITION_PROP: string = 'Hash Cond';
23 |
24 | // computed by pev
25 | COMPUTED_TAGS_PROP: string = '*Tags';
26 |
27 | COSTLIEST_NODE_PROP: string = '*Costiest Node (by cost)';
28 | LARGEST_NODE_PROP: string = '*Largest Node (by rows)';
29 | SLOWEST_NODE_PROP: string = '*Slowest Node (by duration)';
30 |
31 | MAXIMUM_COSTS_PROP: string = '*Most Expensive Node (cost)';
32 | MAXIMUM_ROWS_PROP: string = '*Largest Node (rows)';
33 | MAXIMUM_DURATION_PROP: string = '*Slowest Node (time)';
34 | ACTUAL_DURATION_PROP: string = '*Actual Duration';
35 | ACTUAL_COST_PROP: string = '*Actual Cost';
36 | PLANNER_ESTIMATE_FACTOR: string = '*Planner Row Estimate Factor';
37 | PLANNER_ESIMATE_DIRECTION: string = '*Planner Row Estimate Direction';
38 |
39 | CTE_SCAN_PROP = 'CTE Scan';
40 | CTE_NAME_PROP = 'CTE Name';
41 |
42 | ARRAY_INDEX_KEY: string = 'arrayIndex';
43 |
44 | PEV_PLAN_TAG: string = 'plan_';
45 |
46 | private _maxRows: number = 0;
47 | private _maxCost: number = 0;
48 | private _maxDuration: number = 0;
49 |
50 | getPlans(): Array {
51 | var plans: Array = [];
52 |
53 | for (var i in localStorage) {
54 | if (_.startsWith(i, this.PEV_PLAN_TAG)) {
55 | plans.push(JSON.parse(localStorage[i]));
56 | }
57 | }
58 |
59 | return _.chain(plans)
60 | .sortBy('createdOn')
61 | .reverse()
62 | .value();
63 | }
64 |
65 | getPlan(id: string): IPlan {
66 | return JSON.parse(localStorage.getItem(id));
67 | }
68 |
69 | createPlan(planName: string, planContent: string, planQuery): IPlan {
70 | var plan: IPlan = {
71 | id: this.PEV_PLAN_TAG + new Date().getTime().toString(),
72 | name: planName || 'plan created on ' + moment().format('LLL'),
73 | createdOn: new Date(),
74 | content: JSON.parse(planContent)[0],
75 | query: planQuery
76 | };
77 |
78 | this.analyzePlan(plan);
79 | return plan;
80 | }
81 |
82 | isJsonString(str) {
83 | try {
84 | JSON.parse(str);
85 | } catch (e) {
86 | return false;
87 | }
88 | return true;
89 | }
90 |
91 | analyzePlan(plan: IPlan) {
92 | this.processNode(plan.content.Plan);
93 | plan.content[this.MAXIMUM_ROWS_PROP] = this._maxRows;
94 | plan.content[this.MAXIMUM_COSTS_PROP] = this._maxCost;
95 | plan.content[this.MAXIMUM_DURATION_PROP] = this._maxDuration;
96 |
97 | this.findOutlierNodes(plan.content.Plan);
98 |
99 | localStorage.setItem(plan.id, JSON.stringify(plan));
100 | }
101 |
102 | deletePlan(plan: IPlan) {
103 | localStorage.removeItem(plan.id);
104 | }
105 |
106 | deleteAllPlans() {
107 | localStorage.clear();
108 | }
109 |
110 | // recursively walk down the plan to compute various metrics
111 | processNode(node) {
112 | this.calculatePlannerEstimate(node);
113 | this.calculateActuals(node);
114 |
115 | _.each(node, (value, key) => {
116 | this.calculateMaximums(node, key, value);
117 |
118 | if (key === this.PLANS_PROP) {
119 | _.each(value, (value) => {
120 | this.processNode(value);
121 | });
122 | }
123 | });
124 | }
125 |
126 | calculateMaximums(node, key, value) {
127 | if (key === this.ACTUAL_ROWS_PROP && this._maxRows < value) {
128 | this._maxRows = value;
129 | }
130 | if (key === this.ACTUAL_COST_PROP && this._maxCost < value) {
131 | this._maxCost = value;
132 | }
133 |
134 | if (key === this.ACTUAL_DURATION_PROP && this._maxDuration < value) {
135 | this._maxDuration = value;
136 | }
137 | }
138 |
139 | findOutlierNodes(node) {
140 | node[this.SLOWEST_NODE_PROP] = false;
141 | node[this.LARGEST_NODE_PROP] = false;
142 | node[this.COSTLIEST_NODE_PROP] = false;
143 |
144 | if (node[this.ACTUAL_COST_PROP] === this._maxCost) {
145 | node[this.COSTLIEST_NODE_PROP] = true;
146 | }
147 | if (node[this.ACTUAL_ROWS_PROP] === this._maxRows) {
148 | node[this.LARGEST_NODE_PROP] = true;
149 | }
150 | if (node[this.ACTUAL_DURATION_PROP] === this._maxDuration) {
151 | node[this.SLOWEST_NODE_PROP] = true;
152 | }
153 |
154 | _.each(node, (value, key) => {
155 | if (key === this.PLANS_PROP) {
156 | _.each(value, (value) => {
157 | this.findOutlierNodes(value);
158 | });
159 | }
160 | });
161 | }
162 |
163 | // actual duration and actual cost are calculated by subtracting child values from the total
164 | calculateActuals(node) {
165 | node[this.ACTUAL_DURATION_PROP] = node[this.ACTUAL_TOTAL_TIME_PROP];
166 | node[this.ACTUAL_COST_PROP] = node[this.TOTAL_COST_PROP];
167 |
168 | console.log (node);
169 | _.each(node.Plans, subPlan => {
170 | console.log('processing chldren', subPlan)
171 | // since CTE scan duration is already included in its subnodes, it should be be
172 | // subtracted from the duration of this node
173 | if (subPlan[this.NODE_TYPE_PROP] !== this.CTE_SCAN_PROP) {
174 | node[this.ACTUAL_DURATION_PROP] = node[this.ACTUAL_DURATION_PROP] - subPlan[this.ACTUAL_TOTAL_TIME_PROP];
175 | node[this.ACTUAL_COST_PROP] = node[this.ACTUAL_COST_PROP] - subPlan[this.TOTAL_COST_PROP];
176 | }
177 | });
178 |
179 | if (node[this.ACTUAL_COST_PROP] < 0) {
180 | node[this.ACTUAL_COST_PROP] = 0;
181 | }
182 |
183 | // since time is reported for an invidual loop, actual duration must be adjusted by number of loops
184 | node[this.ACTUAL_DURATION_PROP] = node[this.ACTUAL_DURATION_PROP] * node[this.ACTUAL_LOOPS_PROP];
185 | }
186 |
187 | // figure out order of magnitude by which the planner mis-estimated how many rows would be
188 | // invloved in this node
189 | calculatePlannerEstimate(node) {
190 | node[this.PLANNER_ESTIMATE_FACTOR] = node[this.ACTUAL_ROWS_PROP] / node[this.PLAN_ROWS_PROP];
191 | node[this.PLANNER_ESIMATE_DIRECTION] = EstimateDirection.under;
192 |
193 | if (node[this.PLANNER_ESTIMATE_FACTOR] < 1) {
194 | node[this.PLANNER_ESIMATE_DIRECTION] = EstimateDirection.over;
195 | node[this.PLANNER_ESTIMATE_FACTOR] = node[this.PLAN_ROWS_PROP] / node[this.ACTUAL_ROWS_PROP];
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/app/services/syntax-highlight-service.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | export class SyntaxHighlightService {
5 | OPEN_TAG: string = ' _OPEN_TAG_';
6 | CLOSE_TAG: string = '_CLOSE_TAG_';
7 |
8 | highlight(code: string, keyItems: Array) {
9 | hljs.registerLanguage('sql', LANG_SQL);
10 | hljs.configure({
11 | tabReplace: ' '
12 | });
13 |
14 | // prior to syntax highlighting, we want to tag key items in the raw code. making the
15 | // query upper case and ensuring that all comma separated values have a space
16 | // makes it simpler to find the items we're looing for
17 | var result: string = code.toUpperCase().replace(', ', ',');
18 | _.each(keyItems, (keyItem: string) => {
19 | result = result.replace(keyItem.toUpperCase(), `${this.OPEN_TAG}${keyItem}${this.CLOSE_TAG}`);
20 | });
21 |
22 | result = hljs.highlightAuto(result).value;
23 | result = result.replace(new RegExp(this.OPEN_TAG, 'g'), ``);
24 | result = result.replace(new RegExp(this.CLOSE_TAG, 'g'), ' ');
25 |
26 | return result;
27 | }
28 | }
29 |
30 | export var LANG_SQL = function(hljs) {
31 | var COMMENT_MODE = hljs.COMMENT('--', '$');
32 | return {
33 | case_insensitive: true,
34 | illegal: /[<>{}*]/,
35 | contains: [
36 | {
37 | beginKeywords:
38 | 'begin end start commit rollback savepoint lock alter create drop rename call ' +
39 | 'delete do handler insert load replace select truncate update set show pragma grant ' +
40 | 'merge describe use explain help declare prepare execute deallocate release ' +
41 | 'unlock purge reset change stop analyze cache flush optimize repair kill ' +
42 | 'install uninstall checksum restore check backup revoke',
43 | end: /;/, endsWithParent: true,
44 | keywords: {
45 | keyword:
46 | 'abort abs absolute acc acce accep accept access accessed accessible account acos action activate add ' +
47 | 'addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias ' +
48 | 'allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply ' +
49 | 'archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan ' +
50 | 'atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid ' +
51 | 'authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile ' +
52 | 'before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float ' +
53 | 'binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound ' +
54 | 'buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel ' +
55 | 'capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base ' +
56 | 'char_length character_length characters characterset charindex charset charsetform charsetid check ' +
57 | 'checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close ' +
58 | 'cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation ' +
59 | 'collect colu colum column column_value columns columns_updated comment commit compact compatibility ' +
60 | 'compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn ' +
61 | 'connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection ' +
62 | 'consider consistent constant constraint constraints constructor container content contents context ' +
63 | 'contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost ' +
64 | 'count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation ' +
65 | 'critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user ' +
66 | 'cursor curtime customdatum cycle d data database databases datafile datafiles datalength date_add ' +
67 | 'date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts ' +
68 | 'day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate ' +
69 | 'declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults ' +
70 | 'deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank ' +
71 | 'depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor ' +
72 | 'deterministic diagnostics difference dimension direct_load directory disable disable_all ' +
73 | 'disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div ' +
74 | 'do document domain dotnet double downgrade drop dumpfile duplicate duration e each edition editionable ' +
75 | 'editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt ' +
76 | 'end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors ' +
77 | 'escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding ' +
78 | 'execu execut execute exempt exists exit exp expire explain export export_set extended extent external ' +
79 | 'external_1 external_2 externally extract f failed failed_login_attempts failover failure far fast ' +
80 | 'feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final ' +
81 | 'finish first first_value fixed flash_cache flashback floor flush following follows for forall force ' +
82 | 'form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ' +
83 | 'ftp full function g general generated get get_format get_lock getdate getutcdate global global_name ' +
84 | 'globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups ' +
85 | 'gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex ' +
86 | 'hierarchy high high_priority hosts hour http i id ident_current ident_incr ident_seed identified ' +
87 | 'identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment ' +
88 | 'index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile ' +
89 | 'initial initialized initially initrans inmemory inner innodb input insert install instance instantiable ' +
90 | 'instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat ' +
91 | 'is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists ' +
92 | 'k keep keep_duplicates key keys kill l language large last last_day last_insert_id last_value lax lcase ' +
93 | 'lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit ' +
94 | 'lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate ' +
95 | 'locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call ' +
96 | 'logoff logon logs long loop low low_priority lower lpad lrtrim ltrim m main make_set makedate maketime ' +
97 | 'managed management manual map mapping mask master master_pos_wait match matched materialized max ' +
98 | 'maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans ' +
99 | 'md5 measures median medium member memcompress memory merge microsecond mid migration min minextents ' +
100 | 'minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month ' +
101 | 'months mount move movement multiset mutex n name name_const names nan national native natural nav nchar ' +
102 | 'nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile ' +
103 | 'nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile ' +
104 | 'nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder ' +
105 | 'nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck ' +
106 | 'noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe ' +
107 | 'nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ' +
108 | 'ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old ' +
109 | 'on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date ' +
110 | 'oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary ' +
111 | 'out outer outfile outline output over overflow overriding p package pad parallel parallel_enable ' +
112 | 'parameters parent parse partial partition partitions pascal passing password password_grace_time ' +
113 | 'password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex ' +
114 | 'pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc ' +
115 | 'performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin ' +
116 | 'policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction ' +
117 | 'prediction_cost prediction_details prediction_probability prediction_set prepare present preserve ' +
118 | 'prior priority private private_sga privileges procedural procedure procedure_analyze processlist ' +
119 | 'profiles project prompt protection public publishingservername purge quarter query quick quiesce quota ' +
120 | 'quotename radians raise rand range rank raw read reads readsize rebuild record records ' +
121 | 'recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh ' +
122 | 'regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy ' +
123 | 'reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename ' +
124 | 'repair repeat replace replicate replication required reset resetlogs resize resource respect restore ' +
125 | 'restricted result result_cache resumable resume retention return returning returns reuse reverse revoke ' +
126 | 'right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows ' +
127 | 'rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll ' +
128 | 'sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select ' +
129 | 'self sequence sequential serializable server servererror session session_user sessions_per_user set ' +
130 | 'sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor ' +
131 | 'si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin ' +
132 | 'size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex ' +
133 | 'source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows ' +
134 | 'sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone ' +
135 | 'standby start starting startup statement static statistics stats_binomial_test stats_crosstab ' +
136 | 'stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep ' +
137 | 'stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev ' +
138 | 'stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate ' +
139 | 'subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum ' +
140 | 'suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate ' +
141 | 'sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime t table tables tablespace tan tdo ' +
142 | 'template temporary terminated tertiary_weights test than then thread through tier ties time time_format ' +
143 | 'time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr ' +
144 | 'timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking ' +
145 | 'transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate ' +
146 | 'try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress ' +
147 | 'under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot ' +
148 | 'unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert ' +
149 | 'url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date ' +
150 | 'utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var ' +
151 | 'var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray ' +
152 | 'verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear ' +
153 | 'wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped ' +
154 | 'xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces ' +
155 | 'xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek',
156 | literal:
157 | 'true false null',
158 | built_in:
159 | 'array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number ' +
160 | 'numeric real record serial serial8 smallint text varchar varying void'
161 | },
162 | contains: [
163 | {
164 | className: 'string',
165 | begin: '\'', end: '\'',
166 | contains: [hljs.BACKSLASH_ESCAPE, { begin: '\'\'' }]
167 | },
168 | {
169 | className: 'string',
170 | begin: '"', end: '"',
171 | contains: [hljs.BACKSLASH_ESCAPE, { begin: '""' }]
172 | },
173 | {
174 | className: 'string',
175 | begin: '`', end: '`',
176 | contains: [hljs.BACKSLASH_ESCAPE]
177 | },
178 | hljs.C_NUMBER_MODE,
179 | hljs.C_BLOCK_COMMENT_MODE,
180 | COMMENT_MODE
181 | ]
182 | },
183 | hljs.C_BLOCK_COMMENT_MODE,
184 | COMMENT_MODE
185 | ]
186 | };
187 | };
188 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | pev:
2 | build: .
3 | ports:
4 | - "8000:8000"
5 | volumes:
6 | - ./:/var/www/pev/
7 |
--------------------------------------------------------------------------------
/gulpfile.ts:
--------------------------------------------------------------------------------
1 | import * as gulp from 'gulp';
2 | import {runSequence, task} from './tools/utils';
3 |
4 | // --------------
5 | // Clean (override).
6 | gulp.task('clean', task('clean', 'all'));
7 | gulp.task('clean.dist', task('clean', 'dist'));
8 | gulp.task('clean.test', task('clean', 'test'));
9 | gulp.task('clean.tmp', task('clean', 'tmp'));
10 |
11 | gulp.task('check.versions', task('check.versions'));
12 |
13 | // --------------
14 | // Postinstall.
15 | gulp.task('postinstall', done =>
16 | runSequence('clean',
17 | 'npm',
18 | done));
19 |
20 | // --------------
21 | // Build dev.
22 | gulp.task('build.dev', done =>
23 | runSequence('clean.dist',
24 | 'tslint',
25 | 'build.sass.dev',
26 | 'build.img.dev',
27 | 'build.fonts.dev',
28 | 'build.js.dev',
29 | 'build.index',
30 | done));
31 |
32 | // --------------
33 | // Build prod.
34 | gulp.task('build.prod', done =>
35 | runSequence('clean.dist',
36 | 'clean.tmp',
37 | 'tslint',
38 | 'build.sass.dev',
39 | 'build.img.dev',
40 | 'build.fonts.dev',
41 | 'build.html_css.prod',
42 | 'build.deps',
43 | 'build.js.prod',
44 | 'build.bundles',
45 | 'build.index',
46 | done));
47 |
48 | // --------------
49 | // Watch.
50 | gulp.task('build.dev.watch', done =>
51 | runSequence('build.dev',
52 | 'watch.dev',
53 | done));
54 |
55 | gulp.task('build.test.watch', done =>
56 | runSequence('build.test',
57 | 'watch.test',
58 | done));
59 |
60 | // --------------
61 | // Test.
62 | gulp.task('test', done =>
63 | runSequence('clean.test',
64 | 'tslint',
65 | 'build.test',
66 | 'karma.start',
67 | done));
68 |
69 | // --------------
70 | // Serve.
71 | gulp.task('serve', done =>
72 | runSequence('build.dev',
73 | 'server.start',
74 | 'watch.serve',
75 | done));
76 |
77 | // --------------
78 | // Docs
79 | // Disabled until https://github.com/sebastian-lenz/typedoc/issues/162 gets resolved
80 | // gulp.task('docs', done =>
81 | // runSequence('build.docs',
82 | // 'serve.docs',
83 | // done));
84 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Wed Jul 15 2015 09:44:02 GMT+0200 (Romance Daylight Time)
3 | 'use strict';
4 |
5 | module.exports = function(config) {
6 | config.set({
7 |
8 | // base path that will be used to resolve all patterns (eg. files, exclude)
9 | basePath: './',
10 |
11 |
12 | // frameworks to use
13 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
14 | frameworks: ['jasmine'],
15 |
16 |
17 | // list of files / patterns to load in the browser
18 | files: [
19 | 'node_modules/zone.js/dist/zone-microtask.js',
20 | 'node_modules/zone.js/dist/long-stack-trace-zone.js',
21 | 'node_modules/zone.js/dist/jasmine-patch.js',
22 | 'node_modules/es6-module-loader/dist/es6-module-loader.js',
23 | 'node_modules/traceur/bin/traceur-runtime.js', // Required by PhantomJS2, otherwise it shouts ReferenceError: Can't find variable: require
24 | 'node_modules/traceur/bin/traceur.js',
25 | 'node_modules/systemjs/dist/system.src.js',
26 | 'node_modules/reflect-metadata/Reflect.js',
27 |
28 | { pattern: 'node_modules/angular2/**/*.js', included: false, watched: false },
29 | { pattern: 'node_modules/rxjs/**/*.js', included: false, watched: false },
30 | { pattern: 'test/**/*.js', included: false, watched: true },
31 | { pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: false, watched: false }, // PhantomJS2 (and possibly others) might require it
32 |
33 | 'test-main.js'
34 | ],
35 |
36 |
37 | // list of files to exclude
38 | exclude: [
39 | 'node_modules/angular2/**/*_spec.js'
40 | ],
41 |
42 |
43 | // preprocess matching files before serving them to the browser
44 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
45 | preprocessors: {
46 | },
47 |
48 |
49 | // test results reporter to use
50 | // possible values: 'dots', 'progress'
51 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
52 | reporters: ['mocha'],
53 |
54 |
55 | // web server port
56 | port: 9876,
57 |
58 |
59 | // enable / disable colors in the output (reporters and logs)
60 | colors: true,
61 |
62 |
63 | // level of logging
64 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
65 | logLevel: config.LOG_INFO,
66 |
67 |
68 | // enable / disable watching file and executing tests whenever any file changes
69 | autoWatch: true,
70 |
71 |
72 | // start these browsers
73 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
74 | browsers: [
75 | 'PhantomJS2',
76 | 'Chrome'
77 | ],
78 |
79 |
80 | customLaunchers: {
81 | Chrome_travis_ci: {
82 | base: 'Chrome',
83 | flags: ['--no-sandbox']
84 | }
85 | },
86 |
87 |
88 | // Continuous Integration mode
89 | // if true, Karma captures browsers, runs the tests and exits
90 | singleRun: false
91 | });
92 |
93 | if (process.env.APPVEYOR) {
94 | config.browsers = ['IE'];
95 | config.singleRun = true;
96 | config.browserNoActivityTimeout = 90000; // Note: default value (10000) is not enough
97 | }
98 |
99 | if (process.env.TRAVIS || process.env.CIRCLECI) {
100 | config.browsers = ['Chrome_travis_ci'];
101 | config.singleRun = true;
102 | }
103 | };
104 |
--------------------------------------------------------------------------------
/logo.ai:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexTatiyants/pev/6d31cdd75f557761d7581da6c46586792e5f2dad/logo.ai
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pev",
3 | "version": "0.0.0",
4 | "description": "Postgres Explain Visualizer",
5 | "repository": {
6 | "url": "http://www.tatiyants.com"
7 | },
8 | "scripts": {
9 | "build.dev": "gulp build.dev",
10 | "build.dev.watch": "gulp build.dev.watch",
11 | "build.prod": "gulp build.prod --env prod",
12 | "build.test": "gulp build.test",
13 | "build.test.watch": "gulp build.test.watch",
14 | "docs": "npm run gulp -- build.docs && npm run gulp -- serve.docs",
15 | "gulp": "gulp",
16 | "karma": "karma",
17 | "karma.start": "karma start",
18 | "lint": "gulp tslint",
19 | "postinstall": "tsd reinstall --clean && tsd link && tsd rebundle && gulp check.versions && gulp postinstall",
20 | "reinstall": "rimraf node_modules && npm cache clean && npm install",
21 | "start": "gulp serve --env dev",
22 | "serve.dev": "gulp serve --env dev",
23 | "tasks.list": "gulp --tasks-simple",
24 | "test": "gulp test",
25 | "tsd": "tsd"
26 | },
27 | "author": "Alex Tatiyants",
28 | "license": "MIT",
29 | "devDependencies": {
30 | "async": "^1.4.2",
31 | "chalk": "^1.1.1",
32 | "connect-livereload": "^0.5.3",
33 | "del": "^1.1.1",
34 | "express": "~4.13.1",
35 | "extend": "^3.0.0",
36 | "gulp": "^3.9.0",
37 | "gulp-compass": "^2.1.0",
38 | "gulp-concat": "^2.5.2",
39 | "gulp-filter": "^2.0.2",
40 | "gulp-inject": "^1.3.1",
41 | "gulp-inline-ng2-template": "^0.0.7",
42 | "gulp-load-plugins": "^0.10.0",
43 | "gulp-minify-css": "^1.1.6",
44 | "gulp-minify-html": "^1.0.3",
45 | "gulp-plumber": "~1.0.1",
46 | "gulp-sass": "^2.0.4",
47 | "gulp-shell": "~0.4.3",
48 | "gulp-sourcemaps": "~1.5.2",
49 | "gulp-template": "^3.0.0",
50 | "gulp-tslint": "^3.3.0",
51 | "gulp-tslint-stylish": "^1.0.4",
52 | "gulp-typescript": "~2.8.2",
53 | "gulp-uglify": "^1.2.0",
54 | "gulp-util": "^3.0.7",
55 | "gulp-watch": "^4.2.4",
56 | "jasmine-core": "~2.3.4",
57 | "karma": "~0.13.15",
58 | "karma-chrome-launcher": "~0.2.0",
59 | "karma-ie-launcher": "^0.2.0",
60 | "karma-jasmine": "~0.3.6",
61 | "karma-mocha-reporter": "^1.1.1",
62 | "karma-phantomjs2-launcher": "^0.3.2",
63 | "merge-stream": "^1.0.0",
64 | "open": "0.0.5",
65 | "rimraf": "^2.4.3",
66 | "run-sequence": "^1.1.0",
67 | "semver": "^5.0.3",
68 | "serve-static": "^1.9.2",
69 | "slash": "~1.0.0",
70 | "stream-series": "^0.1.1",
71 | "systemjs-builder": "^0.14.8",
72 | "tiny-lr": "^0.2.1",
73 | "traceur": "^0.0.91",
74 | "ts-node": "^0.5.4",
75 | "tsd": "^0.6.4",
76 | "typedoc": "^0.3.12",
77 | "typescript": "~1.7.3",
78 | "typescript-register": "^1.1.0",
79 | "yargs": "^3.25.0"
80 | },
81 | "dependencies": {
82 | "angular2": "2.0.0-beta.0",
83 | "bootstrap": "^3.3.5",
84 | "es6-module-loader": "^0.17.8",
85 | "es6-shim": "^0.33.3",
86 | "highlight.js": "^9.1.0",
87 | "lodash": "^3.10.1",
88 | "moment": "^2.10.6",
89 | "reflect-metadata": "0.1.2",
90 | "rxjs": "5.0.0-beta.0",
91 | "systemjs": "^0.19.4",
92 | "zone.js": "0.5.10"
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/test-main.js:
--------------------------------------------------------------------------------
1 | // Turn on full stack traces in errors to help debugging
2 | Error.stackTraceLimit=Infinity;
3 |
4 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 100;
5 |
6 | // Cancel Karma's synchronous start,
7 | // we will call `__karma__.start()` later, once all the specs are loaded.
8 | __karma__.loaded = function() {};
9 |
10 | System.config({
11 | baseURL: '/base/',
12 | defaultJSExtensions: true,
13 | paths: {
14 | 'angular2/*': 'node_modules/angular2/*.js',
15 | 'rxjs/*': 'node_modules/rxjs/*.js'
16 | }
17 | });
18 |
19 | System.import('angular2/src/platform/browser/browser_adapter').then(function(browser_adapter) {
20 | browser_adapter.BrowserDomAdapter.makeCurrent();
21 | }).then(function() {
22 | return Promise.all(
23 | Object.keys(window.__karma__.files) // All files served by Karma.
24 | .filter(onlySpecFiles)
25 | .map(file2moduleName)
26 | .map(function(path) {
27 | return System.import(path).then(function(module) {
28 | if (module.hasOwnProperty('main')) {
29 | module.main();
30 | } else {
31 | throw new Error('Module ' + path + ' does not implement main() method.');
32 | }
33 | });
34 | }));
35 | })
36 | .then(function() {
37 | __karma__.start();
38 | }, function(error) {
39 | console.error(error.stack || error);
40 | __karma__.start();
41 | });
42 |
43 |
44 | function onlySpecFiles(path) {
45 | return /[\.|_]spec\.js$/.test(path);
46 | }
47 |
48 | // Normalize paths to module names.
49 | function file2moduleName(filePath) {
50 | return filePath.replace(/\\/g, '/')
51 | .replace(/^\/base\//, '')
52 | .replace(/\.js/, '');
53 | }
54 |
--------------------------------------------------------------------------------
/tools/config.ts:
--------------------------------------------------------------------------------
1 | import {readFileSync} from 'fs';
2 | import {argv} from 'yargs';
3 |
4 | // --------------
5 | // Configuration.
6 | export const ENV = argv['env'] || 'dev';
7 | export const DEBUG = argv['debug'] || false;
8 | export const PORT = argv['port'] || 5555;
9 | export const LIVE_RELOAD_PORT = argv['reload-port'] || 4002;
10 | export const DOCS_PORT = argv['docs-port'] || 4003;
11 | export const APP_BASE = argv['base'] || '/';
12 |
13 | export const APP_TITLE = 'Postgres EXPLAIN Visualizer (pev)';
14 |
15 | export const APP_SRC = 'app';
16 | export const ASSETS_SRC = `${APP_SRC}/assets`;
17 |
18 | export const TOOLS_DIR = 'tools';
19 | export const TMP_DIR = 'tmp';
20 | export const TEST_DEST = 'test';
21 | export const DOCS_DEST = 'docs';
22 | export const APP_DEST = `dist/${ENV}`;
23 | export const ASSETS_DEST = `${APP_DEST}/assets`;
24 | export const BUNDLES_DEST = `${APP_DEST}/bundles`;
25 | export const CSS_DEST = `${APP_DEST}/css`;
26 | export const FONTS_DEST = `${APP_DEST}/fonts`;
27 | export const LIB_DEST = `${APP_DEST}/lib`;
28 | export const APP_ROOT = ENV === 'dev' ? `${APP_BASE}${APP_DEST}/` : `${APP_BASE}`;
29 | export const VERSION = appVersion();
30 |
31 | export const VERSION_NPM = '2.14.7';
32 | export const VERSION_NODE = '4.0.0';
33 |
34 | // Declare NPM dependencies (Note that globs should not be injected).
35 | export const NPM_DEPENDENCIES = [
36 | { src: 'systemjs/dist/system-polyfills.js', dest: LIB_DEST },
37 |
38 | { src: 'es6-shim/es6-shim.min.js', inject: 'shims', dest: LIB_DEST },
39 | { src: 'reflect-metadata/Reflect.js', inject: 'shims', dest: LIB_DEST },
40 | { src: 'systemjs/dist/system.src.js', inject: 'shims', dest: LIB_DEST },
41 | { src: 'angular2/bundles/angular2-polyfills.js', inject: 'shims', dest: LIB_DEST },
42 |
43 | // Faster dev page load
44 | { src: 'rxjs/bundles/Rx.min.js', inject: 'libs', dest: LIB_DEST },
45 | { src: 'angular2/bundles/angular2.min.js', inject: 'libs', dest: LIB_DEST },
46 | { src: 'angular2/bundles/router.js', inject: 'libs', dest: LIB_DEST }, // use router.min.js with alpha47
47 | { src: 'angular2/bundles/http.min.js', inject: 'libs', dest: LIB_DEST },
48 |
49 | { src: 'lodash/index.js', inject: 'libs', dest: LIB_DEST },
50 | { src: 'moment/moment.js', inject: 'libs', dest: LIB_DEST },
51 | { src: 'highlight.js/lib/highlight.js', inject: 'libs', dest: LIB_DEST },
52 | ];
53 |
54 | // Declare local files that needs to be injected
55 | export const APP_ASSETS = [
56 | { src: `${ASSETS_SRC}/css/styles.css`, inject: true, dest: CSS_DEST }
57 | ];
58 |
59 | NPM_DEPENDENCIES
60 | .filter(d => !/\*/.test(d.src)) // Skip globs
61 | .forEach(d => d.src = require.resolve(d.src));
62 |
63 | export const DEPENDENCIES = NPM_DEPENDENCIES.concat(APP_ASSETS);
64 |
65 | // ----------------
66 | // SystemsJS Configuration.
67 | const SYSTEM_CONFIG_DEV = {
68 | defaultJSExtensions: true,
69 | paths: {
70 | 'bootstrap': `${APP_ROOT}bootstrap`,
71 | '*': `${APP_BASE}node_modules/*`
72 | }
73 | };
74 |
75 | const SYSTEM_CONFIG_PROD = {
76 | defaultJSExtensions: true,
77 | bundles: {
78 | 'bundles/app': ['bootstrap']
79 | }
80 | };
81 |
82 | export const SYSTEM_CONFIG = ENV === 'dev' ? SYSTEM_CONFIG_DEV : SYSTEM_CONFIG_PROD;
83 |
84 | // This is important to keep clean module names as 'module name == module uri'.
85 | export const SYSTEM_CONFIG_BUILDER = {
86 | defaultJSExtensions: true,
87 | paths: {
88 | '*': `${TMP_DIR}/*`,
89 | 'angular2/*': 'node_modules/angular2/*',
90 | 'rxjs/*': 'node_modules/rxjs/*'
91 | }
92 | };
93 |
94 | // --------------
95 | // Private.
96 | function appVersion(): number | string {
97 | var pkg = JSON.parse(readFileSync('package.json').toString());
98 | return pkg.version;
99 | }
100 |
--------------------------------------------------------------------------------
/tools/tasks/build.bundles.ts:
--------------------------------------------------------------------------------
1 | import {parallel} from 'async';
2 | import {join} from 'path';
3 | import * as Builder from 'systemjs-builder';
4 | import {BUNDLES_DEST, SYSTEM_CONFIG_BUILDER} from '../config';
5 |
6 | const BUNDLE_OPTS = {
7 | minify: true,
8 | sourceMaps: true,
9 | format: 'cjs'
10 | };
11 |
12 | export = function bundles(gulp, plugins) {
13 | return function (done) {
14 | let builder = new Builder(SYSTEM_CONFIG_BUILDER);
15 |
16 | parallel([
17 | bundleApp
18 | ], () => done());
19 |
20 | function bundleApp(done) {
21 | builder.bundle(
22 | 'bootstrap - angular2/*',
23 | join(BUNDLES_DEST, 'app.js'), BUNDLE_OPTS).then(done);
24 | }
25 | };
26 | };
27 |
--------------------------------------------------------------------------------
/tools/tasks/build.deps.ts:
--------------------------------------------------------------------------------
1 | import * as merge from 'merge-stream';
2 | import {DEPENDENCIES} from '../config';
3 |
4 | export = function buildDepsProd(gulp, plugins) {
5 | return function () {
6 | let stream = merge();
7 |
8 | DEPENDENCIES.forEach(dep => {
9 | stream.add(addStream(dep));
10 | });
11 |
12 | return stream;
13 |
14 | function addStream(dep) {
15 | let stream = gulp.src(dep.src);
16 | stream.pipe(gulp.dest(dep.dest));
17 | return stream;
18 | }
19 | };
20 | };
21 |
--------------------------------------------------------------------------------
/tools/tasks/build.docs.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, APP_TITLE, DOCS_DEST} from '../config';
3 |
4 | export = function buildDocs(gulp, plugins, option) {
5 | return function() {
6 |
7 | let src = [
8 | join(APP_SRC, '**/*.ts'),
9 | '!' + join(APP_SRC, '**/*_spec.ts')
10 | ];
11 |
12 | return gulp.src(src)
13 | .pipe(plugins.typedoc({
14 | // TypeScript options (see typescript docs)
15 | module: 'commonjs',
16 | target: 'es5',
17 | includeDeclarations: true,
18 | // Output options (see typedoc docs)
19 | out: DOCS_DEST,
20 | json: join(DOCS_DEST , 'data/docs.json' ),
21 | name: APP_TITLE,
22 | ignoreCompilerErrors: false,
23 | experimentalDecorators: true,
24 | version: true
25 | }));
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/tools/tasks/build.fonts.dev.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, APP_DEST} from '../config';
3 |
4 | export = function buildFontsDev(gulp, plugins) {
5 | return function () {
6 | return gulp.src([
7 | join(APP_SRC, '**/*.eot'),
8 | join(APP_SRC, '**/*.ttf'),
9 | join(APP_SRC, '**/*.woff'),
10 | join(APP_SRC, '**/*.woff2'),
11 | join(APP_SRC, '**/*.otf')
12 | ])
13 | .pipe(gulp.dest(APP_DEST));
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/tools/tasks/build.html_css.prod.ts:
--------------------------------------------------------------------------------
1 | import * as merge from 'merge-stream';
2 | import {join} from 'path';
3 | import {APP_SRC, TMP_DIR} from '../config';
4 |
5 | // const HTML_MINIFIER_OPTS = { empty: true };
6 |
7 | export = function buildJSDev(gulp, plugins) {
8 | return function () {
9 |
10 | return merge(minifyHtml(), minifyCss());
11 |
12 | function minifyHtml() {
13 | return gulp.src(join(APP_SRC, '**/*.html'))
14 | // .pipe(plugins.minifyHtml(HTML_MINIFIER_OPTS))
15 | .pipe(gulp.dest(TMP_DIR));
16 | }
17 |
18 | function minifyCss() {
19 | return gulp.src(join(APP_SRC, '**/*.css'))
20 | .pipe(plugins.minifyCss())
21 | .pipe(gulp.dest(TMP_DIR));
22 | }
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/tools/tasks/build.img.dev.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, APP_DEST} from '../config';
3 |
4 | export = function buildImagesDev(gulp, plugins) {
5 | return function () {
6 | return gulp.src([
7 | join(APP_SRC, '**/*.gif'),
8 | join(APP_SRC, '**/*.jpg'),
9 | join(APP_SRC, '**/*.png'),
10 | join(APP_SRC, '**/*.svg')
11 | ])
12 | .pipe(gulp.dest(APP_DEST));
13 | };
14 | }
15 |
--------------------------------------------------------------------------------
/tools/tasks/build.index.ts:
--------------------------------------------------------------------------------
1 | import {join, sep} from 'path';
2 | import {APP_SRC, APP_DEST, DEPENDENCIES, ENV} from '../config';
3 | import {transformPath, templateLocals} from '../utils';
4 |
5 | export = function buildIndexDev(gulp, plugins) {
6 | return function () {
7 | return gulp.src(join(APP_SRC, 'index.html'))
8 | // NOTE: There might be a way to pipe in loop.
9 | .pipe(inject('shims'))
10 | .pipe(inject('libs'))
11 | .pipe(inject())
12 | .pipe(plugins.template(templateLocals()))
13 | .pipe(gulp.dest(APP_DEST));
14 | };
15 |
16 | function inject(name?: string) {
17 | return plugins.inject(gulp.src(getInjectablesDependenciesRef(name), { read: false }), {
18 | name,
19 | transform: transformPath(plugins, 'dev')
20 | });
21 | }
22 |
23 | function getInjectablesDependenciesRef(name?: string) {
24 | return DEPENDENCIES
25 | .filter(dep => dep['inject'] && dep['inject'] === (name || true))
26 | .map(mapPath);
27 | }
28 |
29 | function mapPath(dep) {
30 | let prodPath = join(dep.dest, dep.src.split(sep).pop());
31 | return ('prod' === ENV ? prodPath : dep.src );
32 | }
33 | };
34 |
--------------------------------------------------------------------------------
/tools/tasks/build.js.dev.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, APP_DEST} from '../config';
3 | import {templateLocals, tsProjectFn} from '../utils';
4 |
5 | export = function buildJSDev(gulp, plugins) {
6 | let tsProject = tsProjectFn(plugins);
7 | return function () {
8 | let src = [
9 | join(APP_SRC, '**/*.ts'),
10 | '!' + join(APP_SRC, '**/*_spec.ts')
11 | ];
12 |
13 | let result = gulp.src(src)
14 | .pipe(plugins.plumber())
15 | // Won't be required for non-production build after the change
16 | .pipe(plugins.inlineNg2Template({ base: APP_SRC }))
17 | .pipe(plugins.sourcemaps.init())
18 | .pipe(plugins.typescript(tsProject));
19 |
20 | return result.js
21 | .pipe(plugins.sourcemaps.write())
22 | .pipe(plugins.template(templateLocals()))
23 | .pipe(gulp.dest(APP_DEST));
24 | };
25 | };
26 |
--------------------------------------------------------------------------------
/tools/tasks/build.js.prod.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, TMP_DIR} from '../config';
3 | import {templateLocals, tsProjectFn} from '../utils';
4 |
5 | export = function buildJSDev(gulp, plugins) {
6 | return function () {
7 | let tsProject = tsProjectFn(plugins);
8 | let src = [
9 | join(APP_SRC, '**/*.ts'),
10 | '!' + join(APP_SRC, '**/*_spec.ts')
11 | ];
12 |
13 | let result = gulp.src(src)
14 | .pipe(plugins.plumber())
15 | .pipe(plugins.inlineNg2Template({ base: TMP_DIR }))
16 | .pipe(plugins.typescript(tsProject));
17 |
18 | return result.js
19 | .pipe(plugins.template(templateLocals()))
20 | .pipe(gulp.dest(TMP_DIR));
21 | };
22 | };
23 |
--------------------------------------------------------------------------------
/tools/tasks/build.sass.dev.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC} from '../config';
3 |
4 | export = function buildSassDev(gulp, plugins, option) {
5 | return function() {
6 | return gulp.src(join(APP_SRC, '**', '*.scss'))
7 | .pipe(plugins.plumber({
8 | // this allows gulp not to crash on sass compilation errors
9 | errorHandler: function(error) {
10 | console.log(error.message);
11 | this.emit('end');
12 | }
13 | }))
14 | .pipe(plugins.compass({
15 | style: 'compressed',
16 | css: 'app/assets/css',
17 | sass: join(APP_SRC, 'assets/sass')
18 | }))
19 | .pipe(gulp.dest(join(APP_SRC, 'assets')));
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/tools/tasks/build.test.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, TEST_DEST} from '../config';
3 | import {tsProjectFn} from '../utils';
4 |
5 | export = function buildTest(gulp, plugins) {
6 | return function () {
7 | let tsProject = tsProjectFn(plugins);
8 | let src = [
9 | join(APP_SRC, '**/*.ts'),
10 | '!' + join(APP_SRC, 'bootstrap.ts')
11 | ];
12 |
13 | let result = gulp.src(src)
14 | .pipe(plugins.plumber())
15 | .pipe(plugins.inlineNg2Template({ base: APP_SRC }))
16 | .pipe(plugins.typescript(tsProject));
17 |
18 | return result.js
19 | .pipe(gulp.dest(TEST_DEST));
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/tools/tasks/check.versions.ts:
--------------------------------------------------------------------------------
1 | import {VERSION_NPM, VERSION_NODE} from '../config';
2 |
3 | function reportError(message: string) {
4 | console.error(require('chalk').white.bgRed.bold(message));
5 | process.exit(1);
6 | }
7 |
8 | module.exports = function check(gulp, plugins) {
9 | return function () {
10 | let exec = require('child_process').exec;
11 | let semver = require('semver');
12 |
13 | exec('npm --version',
14 | function (error, stdout, stderr) {
15 | if (error !== null) {
16 | reportError('npm preinstall error: ' + error + stderr);
17 | }
18 |
19 | if (!semver.gte(stdout, VERSION_NPM)) {
20 | reportError('NPM is not in required version! Required is ' + VERSION_NPM + ' and you\'re using ' + stdout);
21 | }
22 | });
23 |
24 | exec('node --version',
25 | function (error, stdout, stderr) {
26 | if (error !== null) {
27 | reportError('npm preinstall error: ' + error + stderr);
28 | }
29 |
30 | if (!semver.gte(stdout, VERSION_NODE)) {
31 | reportError('NODE is not in required version! Required is ' + VERSION_NODE + ' and you\'re using ' + stdout);
32 | }
33 | });
34 | };
35 | };
36 |
--------------------------------------------------------------------------------
/tools/tasks/clean.ts:
--------------------------------------------------------------------------------
1 | import * as async from 'async';
2 | import * as del from 'del';
3 | import {APP_DEST, TEST_DEST, TMP_DIR} from '../config';
4 |
5 | export = function clean(gulp, plugins, option) {
6 | return function (done) {
7 |
8 | switch(option) {
9 | case 'all' : cleanAll(done); break;
10 | case 'dist' : cleanDist(done); break;
11 | case 'test' : cleanTest(done); break;
12 | case 'tmp' : cleanTmp(done); break;
13 | default: done();
14 | }
15 |
16 | };
17 | };
18 |
19 | function cleanAll(done) {
20 | async.parallel([
21 | cleanDist,
22 | cleanTest,
23 | cleanTmp
24 | ], done);
25 | }
26 | function cleanDist(done) {
27 | del(APP_DEST, done);
28 | }
29 | function cleanTest(done) {
30 | del(TEST_DEST, done);
31 | }
32 | function cleanTmp(done) {
33 | del(TMP_DIR, done);
34 | }
35 |
--------------------------------------------------------------------------------
/tools/tasks/karma.start.ts:
--------------------------------------------------------------------------------
1 | import * as karma from 'karma';
2 | import {join} from 'path';
3 |
4 | export = function karmaStart() {
5 | return function (done) {
6 | new (karma).Server({
7 | configFile: join(process.cwd(), 'karma.conf.js'),
8 | singleRun: true
9 | }).start(done);
10 | };
11 | };
12 |
--------------------------------------------------------------------------------
/tools/tasks/npm.ts:
--------------------------------------------------------------------------------
1 | export = function npm(gulp, plugins) {
2 | return plugins.shell.task([
3 | 'npm prune'
4 | ]);
5 | };
6 |
--------------------------------------------------------------------------------
/tools/tasks/serve.docs.ts:
--------------------------------------------------------------------------------
1 | import {serveDocs} from '../utils';
2 |
3 | export = function serverStart(gulp, plugins) {
4 | return function () {
5 | serveDocs();
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/tools/tasks/server.start.ts:
--------------------------------------------------------------------------------
1 | import {serveSPA} from '../utils';
2 |
3 | export = function serverStart(gulp, plugins) {
4 | return function () {
5 | serveSPA();
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/tools/tasks/tsd.ts:
--------------------------------------------------------------------------------
1 | export = function tsd(gulp, plugins) {
2 | return plugins.shell.task([
3 | 'tsd reinstall --clean',
4 | 'tsd link',
5 | 'tsd rebundle'
6 | ]);
7 | };
8 |
--------------------------------------------------------------------------------
/tools/tasks/tslint.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC, TOOLS_DIR} from '../config';
3 |
4 | export = function tslint(gulp, plugins) {
5 | return function () {
6 | let src = [
7 | join(APP_SRC, '**/*.ts'),
8 | '!' + join(APP_SRC, '**/*.d.ts'),
9 | join(TOOLS_DIR, '**/*.ts'),
10 | '!' + join(TOOLS_DIR, '**/*.d.ts')
11 | ];
12 |
13 | return gulp.src(src)
14 | .pipe(plugins.tslint())
15 | .pipe(plugins.tslint.report(plugins.tslintStylish, {
16 | emitError: false,
17 | sort: true,
18 | bell: true
19 | }));
20 | };
21 | };
22 |
--------------------------------------------------------------------------------
/tools/tasks/watch.dev.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC} from '../config';
3 |
4 | export = function watchDev(gulp, plugins) {
5 | return function () {
6 | plugins.watch(join(APP_SRC, '**/*'), () => gulp.start('build.dev'));
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/tools/tasks/watch.serve.ts:
--------------------------------------------------------------------------------
1 | import * as runSequence from 'run-sequence';
2 | import {join} from 'path';
3 | import {APP_SRC} from '../config';
4 | import {notifyLiveReload} from '../utils';
5 |
6 | export = function watchServe(gulp, plugins) {
7 | return function () {
8 | plugins.watch(join(APP_SRC, '**'), e =>
9 | runSequence('build.dev', () => notifyLiveReload(e))
10 | );
11 | };
12 | };
13 |
--------------------------------------------------------------------------------
/tools/tasks/watch.test.ts:
--------------------------------------------------------------------------------
1 | import {join} from 'path';
2 | import {APP_SRC} from '../config';
3 |
4 | export = function watchTest(gulp, plugins) {
5 | return function () {
6 | plugins.watch(join(APP_SRC, '**/*.ts'), () => gulp.start('build.test'));
7 | };
8 | };
9 |
--------------------------------------------------------------------------------
/tools/typings/connect-livereload.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'connect-livereload' {
2 | function connectLivereload(options?: any): any;
3 | module connectLivereload {}
4 | export = connectLivereload;
5 | }
6 |
--------------------------------------------------------------------------------
/tools/typings/gulp-load-plugins.d.ts:
--------------------------------------------------------------------------------
1 | // Type definitions for gulp-load-plugins
2 | // Project: https://github.com/jackfranklin/gulp-load-plugins
3 | // Definitions by: Joe Skeen
4 | // Definitions: https://github.com/borisyankov/DefinitelyTyped
5 |
6 | // Does not support ES2015 import (import * as open from 'open').
7 |
8 | /** Loads in any gulp plugins and attaches them to an object, freeing you up from having to manually require each gulp plugin. */
9 | declare module 'gulp-load-plugins' {
10 |
11 | interface IOptions {
12 | /** the glob(s) to search for, default ['gulp-*', 'gulp.*'] */
13 | pattern?: string[];
14 | /** where to find the plugins, searched up from process.cwd(), default 'package.json' */
15 | config?: string;
16 | /** which keys in the config to look within, default ['dependencies', 'devDependencies', 'peerDependencies'] */
17 | scope?: string[];
18 | /** what to remove from the name of the module when adding it to the context, default /^gulp(-|\.)/ */
19 | replaceString?: RegExp;
20 | /** if true, transforms hyphenated plugin names to camel case, default true */
21 | camelize?: boolean;
22 | /** whether the plugins should be lazy loaded on demand, default true */
23 | lazy?: boolean;
24 | /** a mapping of plugins to rename, the key being the NPM name of the package, and the value being an alias you define */
25 | rename?: IPluginNameMappings;
26 | }
27 |
28 | interface IPluginNameMappings {
29 | [npmPackageName: string]: string;
30 | }
31 |
32 | /** Loads in any gulp plugins and attaches them to an object, freeing you up from having to manually require each gulp plugin. */
33 | function gulpLoadPlugins(options?: IOptions): T;
34 | module gulpLoadPlugins {}
35 | export = gulpLoadPlugins;
36 | }
37 |
38 | /**
39 | * Extend this interface to use Gulp plugins in your gulpfile.js
40 | */
41 | interface IGulpPlugins {
42 | }
43 |
--------------------------------------------------------------------------------
/tools/typings/karma.d.ts:
--------------------------------------------------------------------------------
1 | // Use this minimalistic definition file as bluebird dependency
2 | // generate a lot of errors.
3 |
4 | declare module 'karma' {
5 | var karma: IKarma;
6 | export = karma;
7 | interface IKarma {
8 | server: {
9 | start(options: any, callback: Function): void
10 | };
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/tools/typings/merge-stream.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'merge-stream' {
2 | function mergeStream(...streams: NodeJS.ReadWriteStream[]): MergeStream;
3 | interface MergeStream extends NodeJS.ReadWriteStream {
4 | add(stream: NodeJS.ReadWriteStream): MergeStream;
5 | }
6 | module mergeStream {}
7 | export = mergeStream;
8 | }
--------------------------------------------------------------------------------
/tools/typings/open.d.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/borisyankov/DefinitelyTyped/tree/master/open
2 | // Does not support ES2015 import (import * as open from 'open').
3 |
4 | declare module 'open' {
5 | function open(target: string, app?: string): void;
6 | module open {}
7 | export = open;
8 | }
9 |
--------------------------------------------------------------------------------
/tools/typings/run-sequence.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'run-sequence' {
2 | function runSequence(...args: any[]): void;
3 | module runSequence {}
4 | export = runSequence;
5 | }
6 |
--------------------------------------------------------------------------------
/tools/typings/slash.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'slash' {
2 | function slash(path: string): string;
3 | module slash {}
4 | export = slash;
5 | }
6 |
--------------------------------------------------------------------------------
/tools/typings/systemjs-builder.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'systemjs-builder' {
2 | class Builder {
3 | constructor(configObject?: any, baseUrl?: string, configPath?: string);
4 | bundle(source: string, target: string, options?: any): Promise;
5 | buildStatic(source: string, target: string, options?: any): Promise;
6 | }
7 |
8 | module Builder {}
9 | export = Builder;
10 | }
11 |
--------------------------------------------------------------------------------
/tools/typings/tiny-lr.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'tiny-lr' {
2 | function tinylr(): ITinylr;
3 | module tinylr {}
4 | export = tinylr;
5 |
6 | interface ITinylr {
7 | changed(options: any): void;
8 | listen(port: number): void;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tools/typings/yargs.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'yargs' {
2 | var yargs: IYargs;
3 | export = yargs;
4 |
5 | // Minimalistic but serves the usage in the seed.
6 | interface IYargs {
7 | argv: any;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tools/utils.ts:
--------------------------------------------------------------------------------
1 | export * from './utils/template-injectables';
2 | export * from './utils/template-locals';
3 | export * from './utils/server';
4 | export * from './utils/tasks_tools';
5 |
6 |
7 | export function tsProjectFn(plugins) {
8 | return plugins.typescript.createProject('tsconfig.json', {
9 | typescript: require('typescript')
10 | });
11 | }
12 |
--------------------------------------------------------------------------------
/tools/utils/server.ts:
--------------------------------------------------------------------------------
1 | import * as connectLivereload from 'connect-livereload';
2 | import * as express from 'express';
3 | import * as tinylrFn from 'tiny-lr';
4 | import * as openResource from 'open';
5 | import * as serveStatic from 'serve-static';
6 | import {resolve} from 'path';
7 | import {APP_BASE, APP_DEST, DOCS_DEST, LIVE_RELOAD_PORT, DOCS_PORT, PORT} from '../config';
8 |
9 | let tinylr = tinylrFn();
10 |
11 |
12 | export function serveSPA() {
13 | let server = express();
14 | tinylr.listen(LIVE_RELOAD_PORT);
15 |
16 | server.use(
17 | APP_BASE,
18 | connectLivereload({ port: LIVE_RELOAD_PORT }),
19 | express.static(process.cwd())
20 | );
21 |
22 | server.listen(PORT, () =>
23 | openResource('http://localhost:' + PORT + APP_BASE + APP_DEST)
24 | );
25 | }
26 |
27 | export function notifyLiveReload(e) {
28 | let fileName = e.path;
29 | tinylr.changed({
30 | body: { files: [fileName] }
31 | });
32 | }
33 |
34 | export function serveDocs() {
35 | let server = express();
36 |
37 | server.use(
38 | APP_BASE,
39 | serveStatic(resolve(process.cwd(), DOCS_DEST))
40 | );
41 |
42 | server.listen(DOCS_PORT, () =>
43 | openResource('http://localhost:' + DOCS_PORT + APP_BASE)
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/tools/utils/tasks_tools.ts:
--------------------------------------------------------------------------------
1 | import * as gulp from 'gulp';
2 | import * as util from 'gulp-util';
3 | import * as chalk from 'chalk';
4 | import * as gulpLoadPlugins from 'gulp-load-plugins';
5 | import * as _runSequence from 'run-sequence';
6 | import {readdirSync, existsSync, lstatSync} from 'fs';
7 | import {join} from 'path';
8 | import {TOOLS_DIR} from '../config';
9 |
10 | const TASKS_PATH = join(TOOLS_DIR, 'tasks');
11 |
12 | // NOTE: Remove if no issues with runSequence function below.
13 | // export function loadTasks(): void {
14 | // scanDir(TASKS_PATH, (taskname) => registerTask(taskname));
15 | // }
16 |
17 | export function task(taskname: string, option?: string) {
18 | util.log('Loading task', chalk.yellow(taskname, option || ''));
19 | return require(join('..', 'tasks', taskname))(gulp, gulpLoadPlugins(), option);
20 | }
21 |
22 | export function runSequence(...sequence: any[]) {
23 | let tasks = [];
24 | let _sequence = sequence.slice(0);
25 | sequence.pop();
26 |
27 | scanDir(TASKS_PATH, taskname => tasks.push(taskname));
28 |
29 | sequence.forEach(task => {
30 | if (tasks.indexOf(task) > -1) { registerTask(task); }
31 | });
32 |
33 | return _runSequence(..._sequence);
34 | }
35 |
36 | // ----------
37 | // Private.
38 |
39 | function registerTask(taskname: string, filename?: string, option: string = ''): void {
40 | gulp.task(taskname, task(filename || taskname, option));
41 | }
42 |
43 | // TODO: add recursive lookup ? or enforce pattern file + folder (ie ./tools/utils & ./tools/utils.ts )
44 | function scanDir(root: string, cb: (taskname: string) => void) {
45 | if (!existsSync(root)) return;
46 |
47 | walk(root);
48 |
49 | function walk(path) {
50 | let files = readdirSync(path);
51 | for (let i = 0; i < files.length; i += 1) {
52 | let file = files[i];
53 | let curPath = join(path, file);
54 | // if (lstatSync(curPath).isDirectory()) { // recurse
55 | // path = file;
56 | // walk(curPath);
57 | // }
58 | if (lstatSync(curPath).isFile() && /\.ts$/.test(file)) {
59 | let taskname = file.replace(/(\.ts)/, '');
60 | cb(taskname);
61 | }
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tools/utils/template-injectables.ts:
--------------------------------------------------------------------------------
1 | import * as slash from 'slash';
2 | import {join} from 'path';
3 | import {APP_BASE, APP_DEST, ENV} from '../config';
4 |
5 | let injectables: string[] = [];
6 |
7 | export function injectableAssetsRef() {
8 | return injectables;
9 | }
10 |
11 | export function registerInjectableAssetsRef(paths: string[], target: string = '') {
12 | injectables = injectables.concat(
13 | paths
14 | .filter(path => !/(\.map)$/.test(path))
15 | .map(path => join(target, slash(path).split('/').pop()))
16 | );
17 | }
18 |
19 | export function transformPath(plugins, env) {
20 | return function (filepath) {
21 | filepath = ENV === 'prod' ? filepath.replace(`/${APP_DEST}`, '') : filepath;
22 | return slash(plugins.inject.transform.apply(plugins.inject.transform, arguments));
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/tools/utils/template-locals.ts:
--------------------------------------------------------------------------------
1 | import {APP_BASE, APP_DEST, APP_ROOT, APP_TITLE, SYSTEM_CONFIG, VERSION} from '../config';
2 |
3 | // TODO: Add an interface to register more template locals.
4 | export function templateLocals() {
5 | return {
6 | APP_BASE,
7 | APP_DEST,
8 | APP_ROOT,
9 | APP_TITLE,
10 | SYSTEM_CONFIG,
11 | VERSION
12 | };
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "declaration": false,
6 | "noImplicitAny": false,
7 | "removeComments": true,
8 | "noLib": false,
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "sourceMap": true
12 | },
13 | "exclude": [
14 | "node_modules"
15 | ],
16 | "compileOnSave": false
17 | }
18 |
--------------------------------------------------------------------------------
/tsd.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "v4",
3 | "repo": "DefinitelyTyped/DefinitelyTyped",
4 | "ref": "master",
5 | "path": "tools/typings/tsd",
6 | "bundle": "tools/typings/tsd/tsd.d.ts",
7 | "installed": {
8 | "systemjs/systemjs.d.ts": {
9 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
10 | },
11 | "gulp/gulp.d.ts": {
12 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
13 | },
14 | "q/Q.d.ts": {
15 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
16 | },
17 | "orchestrator/orchestrator.d.ts": {
18 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
19 | },
20 | "gulp-shell/gulp-shell.d.ts": {
21 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
22 | },
23 | "mime/mime.d.ts": {
24 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
25 | },
26 | "express/express.d.ts": {
27 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
28 | },
29 | "serve-static/serve-static.d.ts": {
30 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
31 | },
32 | "del/del.d.ts": {
33 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
34 | },
35 | "glob/glob.d.ts": {
36 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
37 | },
38 | "minimatch/minimatch.d.ts": {
39 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
40 | },
41 | "async/async.d.ts": {
42 | "commit": "5c3e47967affa3c4128a3875d1664ba206ae1b0f"
43 | },
44 | "es6-promise/es6-promise.d.ts": {
45 | "commit": "923c5431d9447db9d5cf41adc5914e3c94c1ff10"
46 | },
47 | "node/node.d.ts": {
48 | "commit": "5a8fc5ee71701431e4fdbb80c506e3c13f85a9ff"
49 | },
50 | "gulp-util/gulp-util.d.ts": {
51 | "commit": "5a8fc5ee71701431e4fdbb80c506e3c13f85a9ff"
52 | },
53 | "vinyl/vinyl.d.ts": {
54 | "commit": "5a8fc5ee71701431e4fdbb80c506e3c13f85a9ff"
55 | },
56 | "through2/through2.d.ts": {
57 | "commit": "5a8fc5ee71701431e4fdbb80c506e3c13f85a9ff"
58 | },
59 | "chalk/chalk.d.ts": {
60 | "commit": "5a8fc5ee71701431e4fdbb80c506e3c13f85a9ff"
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/tsdrc:
--------------------------------------------------------------------------------
1 | {
2 | "strictSSL": false
3 | }
4 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "curly": false,
5 | "eofline": true,
6 | "indent": "spaces",
7 | "max-line-length": [true, 140],
8 | "member-ordering": [true,
9 | "public-before-private",
10 | "static-before-instance",
11 | "variables-before-functions"
12 | ],
13 | "no-arg": true,
14 | "no-construct": true,
15 | "no-duplicate-key": true,
16 | "no-duplicate-variable": true,
17 | "no-empty": true,
18 | "no-eval": true,
19 | "no-trailing-comma": true,
20 | "no-trailing-whitespace": true,
21 | "no-unused-expression": true,
22 | "no-unused-variable": true,
23 | "no-unreachable": true,
24 | "no-use-before-declare": true,
25 | "one-line": [true,
26 | "check-open-brace",
27 | "check-catch",
28 | "check-else",
29 | "check-whitespace"
30 | ],
31 | "quotemark": [true, "single"],
32 | "semicolon": true,
33 | "triple-equals": true,
34 | "variable-name": false
35 | }
36 | }
37 |
--------------------------------------------------------------------------------