Schultz преди 8 години
родител
ревизия
4d6296debe
променени са 82 файла, в които са добавени 13740 реда и са изтрити 24 реда
  1. BIN
      FlaskTest/.app.py.swp
  2. 167
    0
      FlaskTest/app.py
  3. 19
    0
      FlaskTest/bash.exe.stackdump
  4. 197
    0
      FlaskTest/static/css/style.css
  5. 616
    0
      FlaskTest/static/data/commits_by_author.dat
  6. 616
    0
      FlaskTest/static/data/lines_of_code_by_author.dat
  7. 617
    0
      FlaskTest/static/data/test_repo/commits_by_author_copy.tsv
  8. 169
    0
      FlaskTest/static/data/test_repo/data2_dummy.tsv
  9. 169
    0
      FlaskTest/static/data/test_repo/data_dummy.tsv
  10. 8
    0
      FlaskTest/static/data/test_repo/day_of_week_copy.tsv
  11. 617
    0
      FlaskTest/static/data/test_repo/lines_of_code_by_author_copy.tsv
  12. BIN
      FlaskTest/static/img/zebra.png
  13. BIN
      FlaskTest/static/img/zebra_white.png
  14. 475
    0
      FlaskTest/static/js/script.js
  15. 61
    0
      FlaskTest/templates/base.html
  16. 160
    0
      FlaskTest/templates/combined_dashboard.html
  17. 156
    0
      FlaskTest/templates/dashboard.html
  18. 48
    24
      gitstats
  19. 33
    0
      gitstats-wrapper.py
  20. 20
    0
      test_repository/.gitignore
  21. 6
    0
      test_repository/app/.gitignore
  22. 167
    0
      test_repository/app/admin/admin-shell.html
  23. 28
    0
      test_repository/app/admin/index.html
  24. 294
    0
      test_repository/app/app-shell.html
  25. 46
    0
      test_repository/app/bower.json
  26. 147
    0
      test_repository/app/elements/admin/admin-add-tool-form.html
  27. 165
    0
      test_repository/app/elements/admin/admin-login.html
  28. 101
    0
      test_repository/app/elements/admin/admin-remove-tool-form.html
  29. 215
    0
      test_repository/app/elements/admin/markdown/admin-markdown-add.html
  30. 154
    0
      test_repository/app/elements/admin/markdown/admin-markdown-delete.html
  31. 190
    0
      test_repository/app/elements/admin/markdown/admin-markdown-editor.html
  32. 19
    0
      test_repository/app/elements/colors.html
  33. 75
    0
      test_repository/app/elements/elements.html
  34. 145
    0
      test_repository/app/elements/markdown-editor.html
  35. 35
    0
      test_repository/app/elements/paper-card-list.html
  36. 148
    0
      test_repository/app/elements/portal/portal-forms.html
  37. 68
    0
      test_repository/app/elements/portal/portal-home.html
  38. 83
    0
      test_repository/app/elements/portal/portal-news-list.html
  39. 180
    0
      test_repository/app/elements/portal/portal-tools.html
  40. 74
    0
      test_repository/app/elements/sample-content.html
  41. 233
    0
      test_repository/app/elements/tools/jira/jira-create-issue-form.html
  42. 197
    0
      test_repository/app/elements/tools/jira/jira-login-dialog.html
  43. 32
    0
      test_repository/app/elements/user-profile.html
  44. BIN
      test_repository/app/favicon.ico
  45. BIN
      test_repository/app/images/avatars/placeholder.png
  46. BIN
      test_repository/app/images/zebra_logo_64.png
  47. 29
    0
      test_repository/app/index.html
  48. 151
    0
      test_repository/app/scripts/js-cookie.js
  49. 4195
    0
      test_repository/app/scripts/moment.min.js
  50. 14
    0
      test_repository/app/scripts/webcomponents.min.js
  51. 143
    0
      test_repository/gulpfile.js
  52. 36
    0
      test_repository/package.json
  53. 39
    0
      test_repository/server/config/index.js
  54. 85
    0
      test_repository/server/controllers/admin.js
  55. 70
    0
      test_repository/server/controllers/news.js
  56. 267
    0
      test_repository/server/controllers/tool.js
  57. 85
    0
      test_repository/server/controllers/tools.js
  58. 31
    0
      test_repository/server/guards/adminGuard.js
  59. 28
    0
      test_repository/server/models/Admin.js
  60. 22
    0
      test_repository/server/models/NewsItem.js
  61. 21
    0
      test_repository/server/models/Tool.js
  62. 20
    0
      test_repository/server/routes/admin/guarded.js
  63. 8
    0
      test_repository/server/routes/admin/index.js
  64. 19
    0
      test_repository/server/routes/admin/unguarded.js
  65. 23
    0
      test_repository/server/routes/admins/index.js
  66. 37
    0
      test_repository/server/routes/index.js
  67. 363
    0
      test_repository/server/routes/jira/index.js
  68. 21
    0
      test_repository/server/routes/news/guarded.js
  69. 8
    0
      test_repository/server/routes/news/index.js
  70. 19
    0
      test_repository/server/routes/news/unguarded.js
  71. 43
    0
      test_repository/server/routes/tool/guarded.js
  72. 8
    0
      test_repository/server/routes/tool/index.js
  73. 580
    0
      test_repository/server/routes/tool/index_BACK.js
  74. 39
    0
      test_repository/server/routes/tool/unguarded.js
  75. 42
    0
      test_repository/server/routes/tools/index.js
  76. 77
    0
      test_repository/server/server.js
  77. 25
    0
      test_repository/server/utils/gen_admin_pass.js
  78. 58
    0
      test_repository/server/utils/logger.js
  79. 19
    0
      test_repository/server/utils/passCrypt/index.js
  80. 126
    0
      test_repository/server/validation/index.js
  81. 2
    0
      test_repository/test.js
  82. 37
    0
      toWriteTest.py

BIN
FlaskTest/.app.py.swp Целия файл


+ 167
- 0
FlaskTest/app.py Целия файл

@@ -0,0 +1,167 @@
1
+from flask import Flask, render_template
2
+app = Flask (__name__)
3
+
4
+
5
+data= {
6
+	"main_repo" : 
7
+	[{
8
+		"name" : "Simul-scan",
9
+		"summary" : "Summary: This project is about making Zebra really fast and efficient",
10
+		"description" : "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
11
+		"visualizations" : 
12
+		[
13
+			{
14
+				"heatmap" : {
15
+					"divID" : "heatmap",
16
+					"title" : "Commits by Hour of Week - dummy data",
17
+					# NOTE these paths are relative to where the script.js file is
18
+					"data_path1" :'data/test_repo/data_dummy.tsv',
19
+					"data_path2" : 'data/test_repo/data_dummy2.tsv',
20
+					"summary" : "This data shows cool stuff",
21
+					"description" : "As you van see, this data focuses on business hours",
22
+					"timestamp" : "System last updated at 11:34am"
23
+				},
24
+				"barchart" : {
25
+					"divID" : "day_of_week",
26
+					"title" : "# Commits by day - real data",
27
+					"data_path" : "data/test_repo/day_of_week_copy.tsv",
28
+					"summary" : "Bar Chart",
29
+					"description" : "What week is this from? Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
30
+					"timestamp" : "System last updated at 11:34am"
31
+				},
32
+				"linegraph1" : {
33
+					"divID" : "lineChart1",
34
+					"title" : "Commits per Author - real data",
35
+					"data_path" : "data/test_repo/commits_by_author_copy.tsv",
36
+					"summary" : "Multi Series Line Chart",
37
+					"description" : "Theres a few authors on this project but 1 stands out",
38
+					"timestamp" : "System last updated at 11:34am"
39
+				},
40
+				"linegraph2" : {
41
+					"divID" : "lineChart2",
42
+					"title" : "Commits per Author - real data",
43
+					"data_path" : "data/test_repo/lines_of_code_by_author_copy.tsv",
44
+					"summary" : "Multi Series Line Chart",
45
+					"description" : "Theres a few authors on this project but 1 stands out",
46
+					"timestamp" : "System last updated at 11:34am"
47
+				}
48
+			}
49
+		]
50
+	}],
51
+
52
+	"sub_repos" : 
53
+	[
54
+		# each of these is going to be a sub-repo
55
+		{
56
+			"name" : "sub-1",
57
+			"summary" : "Test Summary1",
58
+			"description" : "Test Description 1",
59
+			"visualizations" : 
60
+			[
61
+				{
62
+					"heatmap" : {
63
+						"divID" : "heatmap",
64
+						"title" : "Commits by Hour of Week - dummy data",
65
+						# NOTE these paths are relative to where the script.js file is
66
+						"data_path1" : "data/test_repo/data_dummy.tsv",
67
+						"data_path2" : "data/test_repo/data2_dummy.tsv",
68
+						"summary" : "This data shows cool stuff",
69
+						"description" : "As you van see, this data focuses on business hours",
70
+						"timestamp" : "System last updated at 11:34am"
71
+					},
72
+					"barchart" : {
73
+						"divID" : "day_of_week",
74
+						"title" : "# Commits by day - real data",
75
+						"data_path" : "data/test_repo/day_of_week_copy.tsv",
76
+						"summary" : "Bar Chart",
77
+						"description" : "What week is this from? Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
78
+						"timestamp" : "System last updated at 11:34am"
79
+					},
80
+					"linegraph1" : {
81
+						"divID" : "lineChart1",
82
+						"title" : "Commits per Author - real data",
83
+						"data_path" : "data/test_repo/commits_by_author_copy.tsv",
84
+						"summary" : "Multi Series Line Chart",
85
+						"description" : "Theres a few authors on this project but 1 stands out",
86
+						"timestamp" : "System last updated at 11:34am"
87
+					},
88
+					"linegraph2" : {
89
+						"divID" : "lineChart2",
90
+						"title" : "Commits per Author - real data",
91
+						"data_path" : "data/test_repo/lines_of_code_by_author_copy.tsv",
92
+						"summary" : "Multi Series Line Chart",
93
+						"description" : "Theres a few authors on this project but 1 stands out",
94
+						"timestamp" : "System last updated at 11:34am"
95
+					}
96
+				}
97
+			]
98
+		},
99
+		{
100
+			"name" : "sub-2",
101
+			"summary" : "Test Summary 2",
102
+			"description" : "Test Description 2",
103
+			"visualizations" : 
104
+			[
105
+				{
106
+					"heatmap" : {
107
+						"divID" : "heatmap",
108
+						"title" : "Commits by Hour of Week - dummy data",
109
+						# NOTE these paths are relative to where the script.js file is
110
+						"data_path1" : "data/test_repo/data_dummy.tsv",
111
+						"data_path2" : "data/test_repo/data2_dummy.tsv",
112
+						"summary" : "This data shows cool stuff",
113
+						"description" : "As you van see, this data focuses on business hours",
114
+						"timestamp" : "System last updated at 11:34am"
115
+					},
116
+					"barchart" : {
117
+						"divID" : "day_of_week",
118
+						"title" : "# Commits by day - real data",
119
+						"data_path" : "data/test_repo/day_of_week_copy.tsv",
120
+						"summary" : "Bar Chart",
121
+						"description" : "What week is this from? Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
122
+						"timestamp" : "System last updated at 11:34am"
123
+					},
124
+					"linegraph1" : {
125
+						"divID" : "lineChart1",
126
+						"title" : "Commits per Author - real data",
127
+						"data_path" : "data/test_repo/commits_by_author_copy.tsv",
128
+						"summary" : "Multi Series Line Chart",
129
+						"description" : "Theres a few authors on this project but 1 stands out",
130
+						"timestamp" : "System last updated at 11:34am"
131
+					},
132
+					"linegraph2" : {
133
+						"divID" : "lineChart2",
134
+						"title" : "Commits per Author - real data",
135
+						"data_path" : "data/test_repo/lines_of_code_by_author_copy.tsv",
136
+						"summary" : "Multi Series Line Chart",
137
+						"description" : "Theres a few authors on this project but 1 stands out",
138
+						"timestamp" : "System last updated at 11:34am"
139
+					}
140
+				}
141
+			]
142
+		}
143
+	]
144
+}
145
+
146
+@app.route('/dashboard/<repo>')	
147
+def sub_repo(repo):
148
+	global data
149
+	sub_repo_data= None
150
+	for sub_repo in data['sub_repos']:
151
+		if sub_repo['name'] == repo:
152
+			#this should happen in one case, else this isnt a valid sub repo...
153
+			sub_repo_data=sub_repo
154
+	return render_template("dashboard.html", sub=sub_repo_data, nav=data)
155
+
156
+@app.route("/dashboard")
157
+def dashboard():
158
+	global data
159
+	main_repo=data['main_repo'][0]
160
+	return render_template("dashboard.html", sub=main_repo, nav=data )
161
+
162
+
163
+
164
+
165
+if __name__ == "__main__":
166
+	app.run()
167
+

+ 19
- 0
FlaskTest/bash.exe.stackdump Целия файл

@@ -0,0 +1,19 @@
1
+Stack trace:
2
+Frame        Function    Args
3
+000FFFF5B20  0018005D14C (000FFFFE3F4, 0000000F7D4, 00000000F14, 000FFFFDE50)
4
+000FFFF5BC0  0018005E74B (001800A9B05, 00000000000, 00000000CA8, 00000000000)
5
+000FFFF5E10  00180121F60 (001801FEA40, 00000000068, 00000000000, 006FFFFFFB7)
6
+000FFFF6070  00180122549 (000FFFF5FF0, 00000000001, 00000000001, 00180361338)
7
+000FFFF6070  001800AAC8C (000FFFF6320, 000FFFF656C, 000FFFF62A8, 00000000000)
8
+000FFFF6270  0018011A60B (000FFFF6320, 000FFFF656C, 000FFFF62A8, 00000000000)
9
+000FFFF6270  00100428FB2 (000000015F0, 00000000000, 001005F1F98, 00000000000)
10
+00000000001  0010043257E (00600149670, 00600051000, 001800BA52C, 00000000000)
11
+0060004AAE0  00100437B8A (00600051000, 0060021FA97, 00000000076, 000FFFF6578)
12
+00600051000  00100438BE4 (00000000001, 00600000000, 000FFFF66B8, 00000000000)
13
+00600220250  00100439A7C (00000000000, 00000000000, 00000000000, 00000000000)
14
+001005F6B74  0010043A3E6 (00000000083, 00000000009, 0060021F220, 0060021FA20)
15
+001005F6B74  0010043A53F (001800F2D60, 03000000000, 000801FE8D0, 0060021F220)
16
+001005F6B74  001004337B1 (00100000000, 0010043A4E0, 006002124C0, 006002124C0)
17
+0060021F220  00100433C41 (00000000000, 000FFFFFF01, 00000000000, 001004DB7B8)
18
+0060021F380  0010043C2C7 (0000000001F, 0000000012C, 00000000000, 000FFFFFFFF)
19
+End of stack trace (more stack frames may be present)

+ 197
- 0
FlaskTest/static/css/style.css Целия файл

@@ -0,0 +1,197 @@
1
+@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,700');
2
+@import url('//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.min.css');
3
+
4
+/*side bar menu*/
5
+.sidebar-toggle {
6
+  margin-left: -240px;
7
+}
8
+.sidebar {
9
+  width: 240px;
10
+  height: 100%;
11
+
12
+  /*background: #293949;*/
13
+
14
+  background: #000;
15
+  background: -moz-linear-gradient(-45deg,#000 0,#ffff00 100%);
16
+  background: -webkit-linear-gradient(-45deg,#000 0,#ffff00 100%);
17
+  background: linear-gradient(135deg,#000 0,#ffff00 100%);
18
+
19
+
20
+  position: fixed;
21
+  -webkit-transition: all 0.3s ease-in-out;
22
+  -moz-transition: all 0.3s ease-in-out;
23
+  -o-transition: all 0.3s ease-in-out;
24
+  -ms-transition: all 0.3s ease-in-out;
25
+  transition: all 0.3s ease-in-out;
26
+  z-index: 100;
27
+  /*top:80px;*/
28
+}
29
+.sidebar #leftside-navigation ul,
30
+.sidebar #leftside-navigation ul ul {
31
+  margin: -2px 0 0;
32
+  padding: 0;
33
+}
34
+.sidebar #leftside-navigation ul li {
35
+  list-style-type: none;
36
+  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
37
+}
38
+.sidebar #leftside-navigation ul li.active > a {
39
+  color: #1abc9c;
40
+}
41
+.sidebar #leftside-navigation ul li.active ul {
42
+  display: block;
43
+}
44
+.sidebar #leftside-navigation ul li a {
45
+  color: #FFD200;
46
+  text-decoration: none;
47
+  display: block;
48
+  padding: 18px 0 18px 25px;
49
+  font-size: 12px;
50
+  outline: 0;
51
+  -webkit-transition: all 200ms ease-in;
52
+  -moz-transition: all 200ms ease-in;
53
+  -o-transition: all 200ms ease-in;
54
+  -ms-transition: all 200ms ease-in;
55
+  transition: all 200ms ease-in;
56
+}
57
+.sidebar #leftside-navigation ul li a:hover {
58
+  color: #1abc9c;
59
+}
60
+.sidebar #leftside-navigation ul li a span {
61
+  display: inline-block;
62
+}
63
+.sidebar #leftside-navigation ul li a i {
64
+  width: 20px;
65
+}
66
+.sidebar #leftside-navigation ul li a i .fa-angle-left,
67
+.sidebar #leftside-navigation ul li a i .fa-angle-right {
68
+  padding-top: 3px;
69
+}
70
+.sidebar #leftside-navigation ul ul {
71
+  display: none;
72
+}
73
+.sidebar #leftside-navigation ul ul li {
74
+  /*background: #23313f;*/
75
+  margin-bottom: 0;
76
+  margin-left: 0;
77
+  margin-right: 0;
78
+  border-bottom: none;
79
+}
80
+.sidebar #leftside-navigation ul ul li a {
81
+  font-size: 12px;
82
+  padding-top: 13px;
83
+  padding-bottom: 13px;
84
+  /*color: #aeb2b7;*/
85
+  color: #eee;
86
+}
87
+
88
+/*end menu*/
89
+
90
+
91
+/*tile SVG  */
92
+
93
+rect.bordered {
94
+  stroke: #E6E6E6;
95
+  stroke-width:2px;
96
+}
97
+
98
+text.mono {
99
+  font-size: 9pt;
100
+  font-family: Consolas, courier;
101
+  fill: #aaa;
102
+}
103
+
104
+text.axis-workweek {
105
+  fill: #000;
106
+}
107
+
108
+text.axis-worktime {
109
+  fill: #000;
110
+}
111
+div.tooltip {
112
+  position: absolute;
113
+  text-align: center;
114
+  width: 80px;
115
+  height: 50px;
116
+  padding: 2px;
117
+  font: 12px sans-serif;
118
+  background: lightsteelblue;
119
+  border: 0px;
120
+  border-radius: 8px;
121
+  pointer-events: none;
122
+}
123
+
124
+/*day_of_week svg*/
125
+div.rect_tooltip {
126
+  position: absolute;
127
+  text-align: center;
128
+  width: 80px;
129
+  height: 35px;
130
+  padding: 2px;
131
+  font: 12px sans-serif;
132
+  background: orange;
133
+  border: 0px;
134
+  border-radius: 8px;
135
+  pointer-events: none;
136
+}
137
+
138
+
139
+
140
+
141
+
142
+
143
+/*end svg
144
+
145
+
146
+/*dashboard styling*/
147
+
148
+.main_dash{
149
+  padding-left: 250px;
150
+  padding-top: 30px;
151
+}
152
+
153
+.zebra_img{
154
+  width:100px;
155
+  height:30px;
156
+}
157
+.black{
158
+    background-color: #222;
159
+    border-color: #080808;
160
+    position: fixed;
161
+}
162
+.top_spacer{
163
+  margin-top:2%;
164
+}
165
+.details_spacer{
166
+  padding: 25px 0 0 0;
167
+  border-top:1px solid #cecece;
168
+}
169
+.text_content{
170
+  padding: 10px 0px;
171
+}
172
+.metric{
173
+  height: 400px;
174
+}
175
+.drop-shadow{
176
+  box-shadow: 1px 1px 5px #888888;
177
+}
178
+
179
+
180
+
181
+/*line chart*/
182
+.axis--x path {
183
+  display: none;
184
+}
185
+
186
+.line {
187
+  fill: none;
188
+  stroke-width: 1.5px;
189
+}
190
+.grid line {
191
+  stroke: lightgrey;
192
+  stroke-opacity: 0.7;
193
+  shape-rendering: crispEdges;
194
+}
195
+.grid path {
196
+  stroke-width: 0;
197
+}

+ 616
- 0
FlaskTest/static/data/commits_by_author.dat Целия файл

@@ -0,0 +1,616 @@
1
+1392392111 16 0 0 0 0 0 0
2
+1392397527 17 0 0 0 0 0 0
3
+1392830779 18 0 0 0 0 0 0
4
+1392841719 19 0 0 0 0 0 0
5
+1392844458 20 0 0 0 0 0 0
6
+1392852040 21 0 0 0 0 0 0
7
+1392933375 22 0 0 0 0 0 0
8
+1393016220 23 0 0 0 0 0 0
9
+1393018032 24 0 0 0 0 0 0
10
+1393272179 25 0 0 0 0 0 0
11
+1393356877 26 0 0 0 0 0 0
12
+1393357414 27 0 0 0 0 0 0
13
+1393437911 28 0 0 0 0 0 0
14
+1393442422 29 0 0 0 0 0 0
15
+1393515175 30 0 0 0 0 0 0
16
+1393531146 31 0 0 0 0 0 0
17
+1393532869 32 0 0 0 0 0 0
18
+1393534919 33 0 0 0 0 0 0
19
+1393540722 34 0 0 0 0 0 0
20
+1393861723 35 0 0 0 0 0 0
21
+1394035569 36 0 0 0 0 0 0
22
+1394039661 37 0 0 0 0 0 0
23
+1394054732 38 0 0 0 0 0 0
24
+1394055920 39 0 0 0 0 0 0
25
+1394056588 40 0 0 0 0 0 0
26
+1394119672 41 0 0 0 0 0 0
27
+1394125181 42 0 0 0 0 0 0
28
+1394145913 43 0 0 0 0 0 0
29
+1394460626 44 0 0 0 0 0 0
30
+1394465228 45 0 0 0 0 0 0
31
+1394467610 46 0 0 0 0 0 0
32
+1394470538 47 0 0 0 0 0 0
33
+1394478517 48 0 0 0 0 0 0
34
+1394487470 49 0 0 0 0 0 0
35
+1394546208 50 0 0 0 0 0 0
36
+1394549933 51 0 0 0 0 0 0
37
+1394573638 52 0 0 0 0 0 0
38
+1394730184 53 0 0 0 0 0 0
39
+1394732727 55 0 0 0 0 0 0
40
+1395071699 56 0 0 0 0 0 0
41
+1395080218 57 0 0 0 0 0 0
42
+1395084092 58 0 0 0 0 0 0
43
+1395243935 59 0 0 0 0 0 0
44
+1395261876 60 0 0 0 0 0 0
45
+1395324514 61 0 0 0 0 0 0
46
+1395347240 62 0 0 0 0 0 0
47
+1395418555 63 0 0 0 0 0 0
48
+1395675797 64 0 0 0 0 0 0
49
+1395682889 65 0 0 0 0 0 0
50
+1395687258 66 0 0 0 0 0 0
51
+1395687758 67 0 0 0 0 0 0
52
+1395695371 68 0 0 0 0 0 0
53
+1395759924 69 0 0 0 0 0 0
54
+1395781761 70 0 0 0 0 0 0
55
+1395931149 71 0 0 0 0 0 0
56
+1395955619 72 0 0 0 0 0 0
57
+1396017083 73 0 0 0 0 0 0
58
+1396018452 74 0 0 0 0 0 0
59
+1396033193 78 0 0 0 0 0 0
60
+1396034898 79 0 0 0 0 0 0
61
+1396036712 80 0 0 0 0 0 0
62
+1396293025 81 0 0 0 0 0 0
63
+1396379166 82 0 0 0 0 0 0
64
+1396379379 83 0 0 0 0 0 0
65
+1396469103 84 0 0 0 0 0 0
66
+1396469896 85 0 0 0 0 0 0
67
+1396472129 86 0 0 0 0 0 0
68
+1396474065 87 0 0 0 0 0 0
69
+1396541016 88 0 0 0 0 0 0
70
+1396638448 89 0 0 0 0 0 0
71
+1396639995 90 0 0 0 0 0 0
72
+1396885855 91 0 0 0 0 0 0
73
+1396899853 92 0 0 0 0 0 0
74
+1396984335 94 0 0 0 0 0 0
75
+1397247297 95 0 0 0 0 0 0
76
+1397248748 96 0 0 0 0 0 0
77
+1397484607 97 0 0 0 0 0 0
78
+1397491609 98 0 0 0 0 0 0
79
+1397501654 99 0 0 0 0 0 0
80
+1397502552 100 0 0 0 0 0 0
81
+1397508171 101 0 0 0 0 0 0
82
+1397593977 102 0 0 0 0 0 0
83
+1397596834 103 0 0 0 0 0 0
84
+1397681905 104 0 0 0 0 0 0
85
+1397747221 105 0 0 0 0 0 0
86
+1397751241 106 0 0 0 0 0 0
87
+1397754140 107 0 0 0 0 0 0
88
+1398106573 108 0 0 0 0 0 0
89
+1398190593 109 0 0 0 0 0 0
90
+1398265425 110 0 0 0 0 0 0
91
+1398265460 111 0 0 0 0 0 0
92
+1398268479 112 0 0 0 0 0 0
93
+1398271735 113 0 0 0 0 0 0
94
+1398278671 114 0 0 0 0 0 0
95
+1398282984 115 0 0 0 0 0 0
96
+1398373748 116 0 0 0 0 0 0
97
+1398434861 117 0 0 0 0 0 0
98
+1398437720 118 0 0 0 0 0 0
99
+1398697019 119 0 0 0 0 0 0
100
+1398699790 120 0 0 0 0 0 0
101
+1398715690 121 0 0 0 0 0 0
102
+1398716189 122 0 0 0 0 0 0
103
+1398718090 123 0 0 0 0 0 0
104
+1398797186 124 0 0 0 0 0 0
105
+1398799360 125 0 0 0 0 0 0
106
+1398800327 129 0 0 0 0 0 0
107
+1399305448 130 0 0 0 0 0 0
108
+1399315892 131 0 0 0 0 0 0
109
+1399315910 132 0 0 0 0 0 0
110
+1399316102 133 0 0 0 0 0 0
111
+1399387116 134 0 0 0 0 0 0
112
+1399388283 135 0 0 0 0 0 0
113
+1399389375 136 0 0 0 0 0 0
114
+1399564137 137 0 0 0 0 0 0
115
+1399666061 138 0 0 0 0 0 0
116
+1399905905 140 0 0 0 0 0 0
117
+1399991114 141 0 0 0 0 0 0
118
+1400189261 142 0 0 0 0 0 0
119
+1400529215 144 0 0 0 0 0 0
120
+1401293283 145 0 0 0 0 0 0
121
+1401296335 146 0 0 0 0 0 0
122
+1401386785 147 0 0 0 0 0 0
123
+1402341065 148 0 0 0 0 0 0
124
+1402342238 149 0 0 0 0 0 0
125
+1402428124 150 0 0 0 0 0 0
126
+1402431333 151 0 0 0 0 0 0
127
+1402511788 152 0 0 0 0 0 0
128
+1402512041 153 0 0 0 0 0 0
129
+1402595862 154 0 0 0 0 0 0
130
+1403192924 155 0 0 0 0 0 0
131
+1403206094 157 0 0 0 0 0 0
132
+1403280442 158 0 0 0 0 0 0
133
+1403535284 159 0 0 0 0 0 0
134
+1403541811 160 0 0 0 0 0 0
135
+1403636781 161 0 0 0 0 0 0
136
+1403637528 162 0 0 0 0 0 0
137
+1403724192 163 0 0 0 0 0 0
138
+1403732386 164 0 0 0 0 0 0
139
+1404142813 165 0 0 0 0 0 0
140
+1404248505 166 0 0 0 0 0 0
141
+1404915912 167 0 0 0 0 0 0
142
+1404923241 168 0 0 0 0 0 0
143
+1404923409 169 0 0 0 0 0 0
144
+1405002584 170 0 0 0 0 0 0
145
+1405003854 171 0 0 0 0 0 0
146
+1405021172 172 0 0 0 0 0 0
147
+1405023400 173 0 0 0 0 0 0
148
+1405113290 174 0 0 0 0 0 0
149
+1405348976 175 0 0 0 0 0 0
150
+1405361989 176 0 0 0 0 0 0
151
+1405364123 177 0 0 0 0 0 0
152
+1405447703 178 0 0 0 0 0 0
153
+1405540116 179 0 0 0 0 0 0
154
+1406040988 180 0 0 0 0 0 0
155
+1406041836 181 0 0 0 0 0 0
156
+1406052691 182 0 0 0 0 0 0
157
+1406055617 183 0 0 0 0 0 0
158
+1406055678 184 0 0 0 0 0 0
159
+1406225786 185 0 0 0 0 0 0
160
+1406231654 186 0 0 0 0 0 0
161
+1406235975 191 0 0 0 0 0 0
162
+1406297884 192 0 0 0 0 0 0
163
+1406312943 194 0 0 0 0 0 0
164
+1406325886 195 0 0 0 0 0 0
165
+1406563924 196 0 0 0 0 0 0
166
+1406564839 197 0 0 0 0 0 0
167
+1406670247 198 0 0 0 0 0 0
168
+1406734781 199 0 0 0 0 0 0
169
+1406754307 200 0 0 0 0 0 0
170
+1406834212 201 0 0 0 0 0 0
171
+1407185931 202 0 0 0 0 0 0
172
+1407255136 203 0 0 0 0 0 0
173
+1407268137 204 0 0 0 0 0 0
174
+1408040736 205 0 0 0 0 0 0
175
+1408138667 206 0 0 0 0 0 0
176
+1408481183 207 0 0 0 0 0 0
177
+1408976252 208 0 0 0 0 0 0
178
+1408976366 209 0 0 0 0 0 0
179
+1408976476 210 0 0 0 0 0 0
180
+1408983121 211 0 0 0 0 0 0
181
+1409078217 212 0 0 0 0 0 0
182
+1409244667 213 0 0 0 0 0 0
183
+1409253116 215 0 0 0 0 0 0
184
+1409259944 216 0 0 0 0 0 0
185
+1409672806 218 0 0 0 0 0 0
186
+1410206464 219 0 0 0 0 0 0
187
+1410207080 220 0 0 0 0 0 0
188
+1410287098 221 0 0 0 0 0 0
189
+1410551535 222 0 0 0 0 0 0
190
+1411486906 223 0 0 0 0 0 0
191
+1411658801 226 0 0 0 0 0 0
192
+1412085855 227 0 0 0 0 0 0
193
+1412093603 229 0 0 0 0 0 0
194
+1412103242 230 0 0 0 0 0 0
195
+1412103800 231 0 0 0 0 0 0
196
+1412106592 232 0 0 0 0 0 0
197
+1412110451 233 0 0 0 0 0 0
198
+1412110505 234 0 0 0 0 0 0
199
+1412110521 235 0 0 0 0 0 0
200
+1412171582 236 0 0 0 0 0 0
201
+1412178008 237 0 0 0 0 0 0
202
+1412182838 238 0 0 0 0 0 0
203
+1412265983 239 0 0 0 0 0 0
204
+1412275768 240 0 0 0 0 0 0
205
+1412369691 241 0 0 0 0 0 0
206
+1412625476 242 0 0 0 0 0 0
207
+1412715931 243 0 0 0 0 0 0
208
+1413222749 245 0 0 0 0 0 0
209
+1414522504 247 0 0 0 0 0 0
210
+1415144579 248 0 0 0 0 0 0
211
+1415218924 249 0 0 0 0 0 0
212
+1415219022 250 0 0 0 0 0 0
213
+1415220994 252 0 0 0 0 0 0
214
+1415319788 253 0 0 0 0 0 0
215
+1415319862 254 0 0 0 0 0 0
216
+1415321087 255 0 0 0 0 0 0
217
+1415395231 256 0 0 0 0 0 0
218
+1415649515 257 0 0 0 0 0 0
219
+1415652685 258 0 0 0 0 0 0
220
+1415717488 259 0 0 0 0 0 0
221
+1415724534 260 0 0 0 0 0 0
222
+1415726692 261 0 0 0 0 0 0
223
+1415728253 262 0 0 0 0 0 0
224
+1415734870 263 0 0 0 0 0 0
225
+1415736053 264 0 0 0 0 0 0
226
+1415738826 265 0 0 0 0 0 0
227
+1415742817 266 0 0 0 0 0 0
228
+1415745888 267 0 0 0 0 0 0
229
+1415823605 268 0 0 0 0 0 0
230
+1415832007 269 0 0 0 0 0 0
231
+1416239521 270 0 0 0 0 0 0
232
+1416245778 271 0 0 0 0 0 0
233
+1416350686 272 0 0 0 0 0 0
234
+1416612373 273 0 0 0 0 0 0
235
+1417464052 274 0 0 0 0 0 0
236
+1417464390 275 0 0 0 0 0 0
237
+1417534843 276 0 0 0 0 0 0
238
+1417549520 277 0 0 0 0 0 0
239
+1417553492 278 0 0 0 0 0 0
240
+1417564577 279 0 0 0 0 0 0
241
+1417640760 280 0 0 0 0 0 0
242
+1418053512 281 0 0 0 0 0 0
243
+1418318204 282 0 0 0 0 0 0
244
+1418318222 283 0 0 0 0 0 0
245
+1418318240 284 0 0 0 0 0 0
246
+1418318297 285 0 0 0 0 0 0
247
+1418318361 286 0 0 0 0 0 0
248
+1418318417 287 0 0 0 0 0 0
249
+1418318440 288 0 0 0 0 0 0
250
+1418318684 290 0 0 0 0 0 0
251
+1418421690 291 0 0 0 0 0 0
252
+1418421802 294 0 0 0 0 0 0
253
+1418746234 295 0 0 0 0 0 0
254
+1418746500 296 0 0 0 0 0 0
255
+1418752434 297 0 0 0 0 0 0
256
+1418755439 298 0 0 0 0 0 0
257
+1418849653 299 0 0 0 0 0 0
258
+1418918217 300 0 0 0 0 0 0
259
+1418940694 301 0 0 0 0 0 0
260
+1418941834 302 0 0 0 0 0 0
261
+1419356537 308 0 0 0 0 0 0
262
+1421161999 309 0 0 0 0 0 0
263
+1421164187 310 0 0 0 0 0 0
264
+1421170472 311 0 0 0 0 0 0
265
+1421182429 312 0 0 0 0 0 0
266
+1421186075 313 0 0 0 0 0 0
267
+1421333600 314 0 0 0 0 0 0
268
+1421362394 315 0 0 0 0 0 0
269
+1421781546 316 0 0 0 0 0 0
270
+1421794398 317 0 0 0 0 0 0
271
+1421858140 318 0 0 0 0 0 0
272
+1421861618 319 0 0 0 0 0 0
273
+1421865987 320 0 0 0 0 0 0
274
+1421870518 321 0 0 0 0 0 0
275
+1421942808 322 0 0 0 0 0 0
276
+1421965255 323 0 0 0 0 0 0
277
+1421965286 324 0 0 0 0 0 0
278
+1421966627 325 0 0 0 0 0 0
279
+1422288635 326 0 0 0 0 0 0
280
+1422288804 327 0 0 0 0 0 0
281
+1422289212 328 0 0 0 0 0 0
282
+1422289952 329 0 0 0 0 0 0
283
+1422290748 330 0 0 0 0 0 0
284
+1422291553 331 0 0 0 0 0 0
285
+1422310926 332 0 0 0 0 0 0
286
+1422372141 333 0 0 0 0 0 0
287
+1422375118 334 0 0 0 0 0 0
288
+1422384120 335 0 0 0 0 0 0
289
+1422386950 336 0 0 0 0 0 0
290
+1422387963 337 0 0 0 0 0 0
291
+1422463984 338 0 0 0 0 0 0
292
+1422478361 339 0 0 0 0 0 0
293
+1422481852 340 0 0 0 0 0 0
294
+1422544534 341 0 0 0 0 0 0
295
+1422565449 342 0 0 0 0 0 0
296
+1422569286 343 0 0 0 0 0 0
297
+1422569372 344 0 0 0 0 0 0
298
+1422861179 344 0 0 0 1 0 0
299
+1422894999 346 0 0 0 1 0 0
300
+1422989658 347 0 0 0 1 0 0
301
+1423167301 348 0 0 0 1 0 0
302
+1423585125 349 0 0 0 1 0 0
303
+1423590234 350 0 0 0 1 0 0
304
+1423596075 351 0 0 0 1 0 0
305
+1423603626 352 0 0 0 1 0 0
306
+1423672020 353 0 0 0 1 0 0
307
+1423753966 354 0 0 0 1 0 0
308
+1424114643 355 0 0 0 1 0 0
309
+1424188717 356 0 0 0 1 0 0
310
+1424193093 357 0 0 0 1 0 0
311
+1424201771 358 0 0 0 1 0 0
312
+1424273633 359 0 0 0 1 0 0
313
+1424279063 360 0 0 0 1 0 0
314
+1424280071 361 0 0 0 1 0 0
315
+1424281679 362 0 0 0 1 0 0
316
+1424288424 363 0 0 0 1 0 0
317
+1424288546 364 0 0 0 1 0 0
318
+1424295016 365 0 0 0 1 0 0
319
+1424465722 366 0 0 0 1 0 0
320
+1424466815 367 0 0 0 1 0 0
321
+1424467415 368 0 0 0 1 0 0
322
+1424712442 369 0 0 0 1 0 0
323
+1424716942 370 0 0 0 1 0 0
324
+1424721680 371 0 0 0 1 0 0
325
+1424791974 372 0 0 0 1 0 0
326
+1424796525 373 0 0 0 1 0 0
327
+1424798803 374 0 0 0 1 0 0
328
+1424798945 375 0 0 0 1 0 0
329
+1424803751 376 0 0 0 1 0 0
330
+1424810387 377 0 0 0 1 0 0
331
+1424810657 378 0 0 0 1 0 0
332
+1424816551 379 0 0 0 1 0 0
333
+1424818066 380 0 0 0 1 0 0
334
+1424878655 381 0 0 0 1 0 0
335
+1424884467 382 0 0 0 1 0 0
336
+1424884849 383 0 0 0 1 0 0
337
+1424892815 384 0 0 0 1 0 0
338
+1424898072 385 0 0 0 1 0 0
339
+1425051573 386 0 0 0 1 0 0
340
+1425067581 387 0 0 0 1 0 0
341
+1425067690 388 0 0 0 1 0 0
342
+1425077014 389 0 0 0 1 0 0
343
+1425309326 390 0 0 0 1 0 0
344
+1425313422 391 0 0 0 1 0 0
345
+1425329531 392 0 0 0 1 0 0
346
+1425337212 393 0 0 0 1 0 0
347
+1425342674 394 0 0 0 1 0 0
348
+1425914337 395 0 0 0 1 0 0
349
+1425917461 396 0 0 0 1 0 0
350
+1425999980 397 0 0 0 1 0 0
351
+1426267819 398 0 0 0 1 0 0
352
+1426273408 399 0 0 0 1 0 0
353
+1426280545 400 0 0 0 1 0 0
354
+1426628250 402 0 0 0 1 0 0
355
+1426778183 403 0 0 0 1 0 0
356
+1426791438 404 0 0 0 1 0 0
357
+1426803375 405 0 0 0 1 0 0
358
+1426804222 406 0 0 0 1 0 0
359
+1426890119 407 0 0 0 1 0 0
360
+1427129667 408 0 0 0 1 0 0
361
+1427133177 409 0 0 0 1 0 0
362
+1427135813 410 0 0 0 1 0 0
363
+1427137830 411 0 0 0 1 0 0
364
+1427139221 412 0 0 0 1 0 0
365
+1427141290 413 0 0 0 1 0 0
366
+1427141834 414 0 0 0 1 0 0
367
+1427206811 415 0 0 0 1 0 0
368
+1427225995 416 0 0 0 1 0 0
369
+1427226516 417 0 0 0 1 0 0
370
+1427228366 419 0 0 0 1 0 0
371
+1427267795 419 0 0 0 2 0 0
372
+1427267837 419 0 0 0 3 0 0
373
+1427294459 420 0 0 0 3 0 0
374
+1427317966 421 0 0 0 3 0 0
375
+1427335799 422 0 0 0 3 0 0
376
+1427349002 422 0 0 0 4 0 0
377
+1427349639 422 0 0 0 5 0 0
378
+1427362185 422 0 0 0 6 0 0
379
+1427362305 422 0 0 0 7 0 0
380
+1427426891 424 0 0 0 8 0 0
381
+1427488267 425 0 0 0 8 0 0
382
+1427789357 425 0 0 0 9 0 0
383
+1427789410 426 0 0 0 10 0 0
384
+1427833035 427 0 0 0 10 0 0
385
+1427917146 428 0 0 0 10 0 0
386
+1427997681 429 0 0 0 10 0 0
387
+1428443057 430 0 0 0 10 0 0
388
+1428513842 431 0 0 0 10 0 0
389
+1428521668 432 0 0 0 10 0 0
390
+1428934715 433 0 0 0 10 0 0
391
+1429112004 434 0 0 0 10 0 0
392
+1429284766 435 0 0 0 10 0 0
393
+1429304949 436 0 0 0 10 0 0
394
+1429310422 437 0 0 0 10 0 0
395
+1430403827 438 0 0 0 10 0 0
396
+1431109116 438 1 0 0 10 0 0
397
+1431110062 439 2 0 0 10 0 0
398
+1431398393 439 3 0 0 10 0 0
399
+1432310614 439 4 0 0 10 0 0
400
+1432310966 439 5 0 0 10 0 0
401
+1432311075 439 6 0 0 10 0 0
402
+1432311088 439 7 0 0 10 0 0
403
+1432311100 439 8 0 0 10 0 0
404
+1432312050 439 9 0 0 10 0 0
405
+1432588799 439 9 1 0 10 0 0
406
+1432588896 439 9 2 0 10 0 0
407
+1432588954 439 9 3 0 10 0 0
408
+1432831392 439 10 3 0 10 0 0
409
+1432831427 439 11 3 0 10 0 0
410
+1432907636 439 12 3 0 10 0 0
411
+1432929105 439 13 3 0 10 0 0
412
+1433175771 439 14 3 0 10 0 0
413
+1433464018 439 14 4 0 10 0 0
414
+1433464310 439 14 5 0 10 0 0
415
+1434132785 439 15 5 0 10 0 0
416
+1434134581 439 16 5 0 10 0 0
417
+1434138920 439 17 5 0 10 0 0
418
+1434138982 439 18 5 0 10 0 0
419
+1434139189 439 19 5 0 10 0 0
420
+1434376340 439 20 5 0 10 0 0
421
+1435836687 439 21 5 0 10 0 0
422
+1435843824 439 22 5 0 10 0 0
423
+1436849943 439 22 5 0 10 1 0
424
+1438269784 439 23 5 0 10 1 0
425
+1438269918 439 24 5 0 10 1 0
426
+1438271263 439 25 5 0 10 1 0
427
+1438273348 439 26 5 0 10 1 0
428
+1438282294 439 27 5 0 10 1 0
429
+1438303550 439 28 5 0 10 1 0
430
+1439397263 439 29 5 0 10 1 0
431
+1439399146 439 30 5 0 10 1 0
432
+1441737290 439 31 5 0 10 1 0
433
+1442267605 439 32 5 0 10 1 0
434
+1442597513 439 34 5 0 10 1 0
435
+1442954764 439 35 5 0 10 1 0
436
+1443018794 439 36 5 0 10 1 0
437
+1443031570 439 37 5 0 10 1 0
438
+1443032056 439 38 5 0 10 1 0
439
+1443119585 439 39 5 0 10 1 0
440
+1443711951 439 40 5 0 10 1 0
441
+1443798856 439 41 5 0 10 1 0
442
+1449685453 439 42 5 0 10 1 0
443
+1450283272 439 43 5 0 10 1 0
444
+1450664183 439 44 5 0 10 1 0
445
+1450666600 439 45 5 0 10 1 0
446
+1452027169 439 45 6 0 10 1 0
447
+1452028386 439 45 7 0 10 1 0
448
+1452200566 439 45 8 0 10 1 0
449
+1452207430 439 45 9 0 10 1 0
450
+1452207602 439 47 10 0 10 1 0
451
+1452212539 439 48 10 0 10 1 0
452
+1452280833 439 48 11 0 10 1 0
453
+1452793688 439 49 11 0 10 1 0
454
+1453224499 439 50 11 0 10 1 0
455
+1453225168 439 52 11 0 10 1 0
456
+1453230613 439 53 11 0 10 1 0
457
+1453323055 439 54 11 0 10 1 0
458
+1453493571 439 55 11 0 10 1 0
459
+1453704637 439 55 11 0 10 1 1
460
+1453825100 439 56 11 0 10 1 1
461
+1453837575 439 60 11 0 10 1 1
462
+1453848840 439 61 11 0 10 1 1
463
+1453953447 439 63 12 0 10 1 1
464
+1454689867 439 63 13 0 10 1 1
465
+1455057770 439 63 14 0 10 1 1
466
+1455660753 439 64 14 0 10 1 1
467
+1455830872 439 65 14 0 10 1 1
468
+1455831309 439 66 14 0 10 1 1
469
+1455831437 439 67 14 0 10 1 1
470
+1455894228 439 67 15 0 10 1 1
471
+1455897059 439 67 16 0 10 1 1
472
+1456110772 439 68 16 0 10 1 1
473
+1456266997 439 68 17 0 10 1 1
474
+1456267705 439 68 18 0 10 1 1
475
+1456269047 439 69 18 0 10 1 1
476
+1456506668 439 69 19 0 10 1 1
477
+1456806554 439 70 19 0 10 1 1
478
+1457141043 439 71 19 0 10 1 1
479
+1457452520 439 71 20 0 10 1 1
480
+1457488422 439 71 21 0 10 1 1
481
+1457563999 439 71 22 0 10 1 1
482
+1457644085 439 71 23 0 10 1 1
483
+1457644884 439 71 24 0 10 1 1
484
+1457646092 439 71 25 0 10 1 1
485
+1457987370 439 72 25 0 10 1 1
486
+1457991490 439 72 26 0 10 1 1
487
+1458004818 439 73 26 0 10 1 1
488
+1458593314 439 74 26 0 10 1 1
489
+1458765547 439 75 26 0 10 1 1
490
+1458914088 439 76 26 0 10 1 1
491
+1458991612 439 77 26 0 10 1 1
492
+1459133960 439 78 26 0 10 1 1
493
+1460047216 439 79 26 0 10 1 1
494
+1460047380 439 81 27 0 10 1 1
495
+1460375644 439 83 28 0 10 1 1
496
+1460380408 439 85 30 0 10 1 1
497
+1460407428 439 86 30 0 10 1 1
498
+1460569764 439 86 31 0 10 1 1
499
+1461074070 439 87 31 0 10 1 1
500
+1461270656 439 88 32 0 10 1 1
501
+1461354039 439 88 33 0 10 1 1
502
+1461551595 439 89 33 0 10 1 1
503
+1461552637 439 90 33 0 10 1 1
504
+1462227334 439 91 33 0 10 1 1
505
+1463152815 439 91 34 0 10 1 1
506
+1463157617 439 91 35 0 10 1 1
507
+1463451445 439 91 36 0 10 1 1
508
+1463451804 439 91 37 0 10 1 1
509
+1463680867 439 92 37 0 10 1 1
510
+1463758054 439 93 37 0 10 1 1
511
+1464031184 439 94 37 0 10 1 1
512
+1464096601 439 95 37 0 10 1 1
513
+1464208709 439 96 37 0 10 1 1
514
+1464237088 439 97 37 0 10 1 1
515
+1464237194 439 98 37 0 10 1 1
516
+1464293889 439 99 37 0 10 1 1
517
+1464793340 439 100 38 0 10 1 1
518
+1464880180 439 101 38 0 10 1 1
519
+1464895801 439 102 38 0 10 1 1
520
+1464902654 439 103 38 0 10 1 1
521
+1465319220 439 104 39 0 10 1 1
522
+1465424498 439 105 39 0 10 1 1
523
+1466648922 439 106 39 0 10 1 1
524
+1466649684 439 107 39 0 10 1 1
525
+1466650102 439 108 39 0 10 1 1
526
+1466701155 439 109 39 0 10 1 1
527
+1466735347 439 110 39 0 10 1 1
528
+1466736165 439 111 40 0 10 1 1
529
+1468180054 439 114 41 0 10 1 1
530
+1468292316 439 114 42 0 10 1 1
531
+1468876762 439 114 43 0 10 1 1
532
+1468876920 439 114 44 0 10 1 1
533
+1469121939 439 114 45 0 10 1 1
534
+1469157573 439 115 45 0 10 1 1
535
+1469552934 439 115 46 0 10 1 1
536
+1469589171 439 116 47 0 10 1 1
537
+1469640240 439 117 47 0 10 1 1
538
+1469723242 439 117 48 0 10 1 1
539
+1469725928 439 118 48 0 10 1 1
540
+1469780404 439 119 48 0 10 1 1
541
+1475506438 439 120 48 0 10 1 1
542
+1475508328 439 122 48 0 10 1 1
543
+1475605531 439 124 48 0 10 1 1
544
+1475759454 439 125 48 0 10 1 1
545
+1476301940 439 125 49 0 10 1 1
546
+1477421556 439 126 50 0 10 1 1
547
+1477427877 439 127 52 0 10 1 1
548
+1477433230 439 127 53 0 10 1 1
549
+1477694191 439 127 54 0 10 1 1
550
+1477939269 439 127 55 0 10 1 1
551
+1477941012 439 127 56 0 10 1 1
552
+1478115005 439 127 57 0 10 1 1
553
+1478288605 439 127 58 0 10 1 1
554
+1478551645 439 127 59 0 10 1 1
555
+1478792001 439 127 60 0 10 1 1
556
+1478814382 439 127 61 0 10 1 1
557
+1478816505 439 127 62 0 10 1 1
558
+1478878515 439 127 63 0 10 1 1
559
+1479419832 439 127 64 0 10 1 1
560
+1479846967 439 127 65 0 10 1 1
561
+1480530088 439 127 66 0 10 1 1
562
+1481670500 439 127 67 0 10 1 1
563
+1482181507 439 127 68 1 10 1 1
564
+1482336682 439 127 69 1 10 1 1
565
+1482340119 439 127 70 1 10 1 1
566
+1482353511 439 127 71 2 10 1 1
567
+1482359008 439 127 71 3 10 1 1
568
+1483455272 439 127 72 4 10 1 1
569
+1484342644 439 127 72 5 10 1 1
570
+1484684949 439 127 72 7 10 1 1
571
+1484772851 439 127 73 8 10 1 1
572
+1485799035 439 127 75 9 10 1 1
573
+1485877540 439 127 76 9 10 1 1
574
+1485877851 439 127 77 9 10 1 1
575
+1485877920 439 127 78 9 10 1 1
576
+1485878121 439 127 79 9 10 1 1
577
+1486146025 439 127 80 9 10 1 1
578
+1486412376 439 127 81 9 10 1 1
579
+1486499625 439 127 82 9 10 1 1
580
+1486755546 439 127 83 9 10 1 1
581
+1487109340 439 127 84 9 10 1 1
582
+1487277883 439 127 84 10 10 1 1
583
+1487783266 439 127 84 13 10 1 1
584
+1487946215 439 127 86 14 10 1 1
585
+1487965862 439 127 86 15 10 1 1
586
+1487966474 439 127 86 16 10 1 1
587
+1487966964 439 127 86 17 10 1 1
588
+1488313813 439 127 86 18 10 1 1
589
+1488406636 439 127 87 18 10 1 1
590
+1488407109 439 127 88 18 10 1 1
591
+1488409193 439 127 89 18 10 1 1
592
+1488560599 439 127 90 18 10 1 1
593
+1488773682 439 127 90 19 10 1 1
594
+1488814084 439 127 91 19 10 1 1
595
+1488815988 439 127 92 19 10 1 1
596
+1489686589 439 127 93 19 10 1 1
597
+1490023066 439 127 95 19 10 1 1
598
+1490035644 439 127 96 19 10 1 1
599
+1490041875 439 127 97 19 10 1 1
600
+1490113585 439 127 98 19 10 1 1
601
+1490120437 439 127 99 19 10 1 1
602
+1490128257 439 127 100 19 10 1 1
603
+1490645760 439 127 101 19 10 1 1
604
+1490725467 439 127 102 19 10 1 1
605
+1491336954 439 127 103 19 10 1 1
606
+1491341896 439 127 104 19 10 1 1
607
+1492026512 439 127 105 19 10 1 1
608
+1492107919 439 127 106 19 10 1 1
609
+1492108901 439 127 107 19 10 1 1
610
+1492435237 439 127 108 19 10 1 1
611
+1492435982 439 127 109 19 10 1 1
612
+1492454272 439 127 110 19 10 1 1
613
+1493052262 439 127 110 20 10 1 1
614
+1493153070 439 127 111 20 10 1 1
615
+1493407521 439 127 112 20 10 1 1
616
+1493412286 439 127 113 20 10 1 1

+ 616
- 0
FlaskTest/static/data/lines_of_code_by_author.dat Целия файл

@@ -0,0 +1,616 @@
1
+1392392111 27013 0 0 0 0 0 0
2
+1392397527 27021 0 0 0 0 0 0
3
+1392830779 27024 0 0 0 0 0 0
4
+1392841719 27573 0 0 0 0 0 0
5
+1392844458 27597 0 0 0 0 0 0
6
+1392852040 27702 0 0 0 0 0 0
7
+1392933375 27702 0 0 0 0 0 0
8
+1393016220 27706 0 0 0 0 0 0
9
+1393018032 27706 0 0 0 0 0 0
10
+1393272179 27723 0 0 0 0 0 0
11
+1393356877 27861 0 0 0 0 0 0
12
+1393357414 27864 0 0 0 0 0 0
13
+1393437911 27903 0 0 0 0 0 0
14
+1393442422 28012 0 0 0 0 0 0
15
+1393515175 28021 0 0 0 0 0 0
16
+1393531146 28032 0 0 0 0 0 0
17
+1393532869 28039 0 0 0 0 0 0
18
+1393534919 28087 0 0 0 0 0 0
19
+1393540722 28089 0 0 0 0 0 0
20
+1393861723 28100 0 0 0 0 0 0
21
+1394035569 28126 0 0 0 0 0 0
22
+1394039661 28188 0 0 0 0 0 0
23
+1394054732 28301 0 0 0 0 0 0
24
+1394055920 28303 0 0 0 0 0 0
25
+1394056588 28307 0 0 0 0 0 0
26
+1394119672 28320 0 0 0 0 0 0
27
+1394125181 28328 0 0 0 0 0 0
28
+1394145913 33119 0 0 0 0 0 0
29
+1394460626 33126 0 0 0 0 0 0
30
+1394465228 33150 0 0 0 0 0 0
31
+1394467610 33176 0 0 0 0 0 0
32
+1394470538 33176 0 0 0 0 0 0
33
+1394478517 33239 0 0 0 0 0 0
34
+1394487470 33251 0 0 0 0 0 0
35
+1394546208 33287 0 0 0 0 0 0
36
+1394549933 33300 0 0 0 0 0 0
37
+1394573638 33509 0 0 0 0 0 0
38
+1394730184 33513 0 0 0 0 0 0
39
+1394732727 33853 0 0 0 0 0 0
40
+1395071699 33905 0 0 0 0 0 0
41
+1395080218 33955 0 0 0 0 0 0
42
+1395084092 37195 0 0 0 0 0 0
43
+1395243935 37258 0 0 0 0 0 0
44
+1395261876 37417 0 0 0 0 0 0
45
+1395324514 37422 0 0 0 0 0 0
46
+1395347240 37423 0 0 0 0 0 0
47
+1395418555 37453 0 0 0 0 0 0
48
+1395675797 37472 0 0 0 0 0 0
49
+1395682889 37517 0 0 0 0 0 0
50
+1395687258 37709 0 0 0 0 0 0
51
+1395687758 37710 0 0 0 0 0 0
52
+1395695371 37779 0 0 0 0 0 0
53
+1395759924 37795 0 0 0 0 0 0
54
+1395781761 37854 0 0 0 0 0 0
55
+1395931149 37969 0 0 0 0 0 0
56
+1395955619 38207 0 0 0 0 0 0
57
+1396017083 38220 0 0 0 0 0 0
58
+1396018452 38220 0 0 0 0 0 0
59
+1396033193 38685 0 0 0 0 0 0
60
+1396034898 38685 0 0 0 0 0 0
61
+1396036712 38779 0 0 0 0 0 0
62
+1396293025 38828 0 0 0 0 0 0
63
+1396379166 38831 0 0 0 0 0 0
64
+1396379379 38852 0 0 0 0 0 0
65
+1396469103 40876 0 0 0 0 0 0
66
+1396469896 40878 0 0 0 0 0 0
67
+1396472129 40897 0 0 0 0 0 0
68
+1396474065 41011 0 0 0 0 0 0
69
+1396541016 41018 0 0 0 0 0 0
70
+1396638448 41021 0 0 0 0 0 0
71
+1396639995 41023 0 0 0 0 0 0
72
+1396885855 41027 0 0 0 0 0 0
73
+1396899853 41086 0 0 0 0 0 0
74
+1396984335 41355 0 0 0 0 0 0
75
+1397247297 41409 0 0 0 0 0 0
76
+1397248748 41410 0 0 0 0 0 0
77
+1397484607 41417 0 0 0 0 0 0
78
+1397491609 41573 0 0 0 0 0 0
79
+1397501654 41592 0 0 0 0 0 0
80
+1397502552 41660 0 0 0 0 0 0
81
+1397508171 41724 0 0 0 0 0 0
82
+1397593977 41896 0 0 0 0 0 0
83
+1397596834 41896 0 0 0 0 0 0
84
+1397681905 41970 0 0 0 0 0 0
85
+1397747221 42529 0 0 0 0 0 0
86
+1397751241 42589 0 0 0 0 0 0
87
+1397754140 42612 0 0 0 0 0 0
88
+1398106573 42712 0 0 0 0 0 0
89
+1398190593 42714 0 0 0 0 0 0
90
+1398265425 42715 0 0 0 0 0 0
91
+1398265460 42922 0 0 0 0 0 0
92
+1398268479 42922 0 0 0 0 0 0
93
+1398271735 42978 0 0 0 0 0 0
94
+1398278671 42981 0 0 0 0 0 0
95
+1398282984 42983 0 0 0 0 0 0
96
+1398373748 42984 0 0 0 0 0 0
97
+1398434861 42998 0 0 0 0 0 0
98
+1398437720 43001 0 0 0 0 0 0
99
+1398697019 43001 0 0 0 0 0 0
100
+1398699790 43005 0 0 0 0 0 0
101
+1398715690 43016 0 0 0 0 0 0
102
+1398716189 43088 0 0 0 0 0 0
103
+1398718090 43089 0 0 0 0 0 0
104
+1398797186 43172 0 0 0 0 0 0
105
+1398799360 43172 0 0 0 0 0 0
106
+1398800327 43395 0 0 0 0 0 0
107
+1399305448 43420 0 0 0 0 0 0
108
+1399315892 43422 0 0 0 0 0 0
109
+1399315910 43422 0 0 0 0 0 0
110
+1399316102 43423 0 0 0 0 0 0
111
+1399387116 43505 0 0 0 0 0 0
112
+1399388283 43506 0 0 0 0 0 0
113
+1399389375 43510 0 0 0 0 0 0
114
+1399564137 43513 0 0 0 0 0 0
115
+1399666061 43517 0 0 0 0 0 0
116
+1399905905 43973 0 0 0 0 0 0
117
+1399991114 43975 0 0 0 0 0 0
118
+1400189261 44019 0 0 0 0 0 0
119
+1400529215 77954 0 0 0 0 0 0
120
+1401293283 77959 0 0 0 0 0 0
121
+1401296335 77960 0 0 0 0 0 0
122
+1401386785 78042 0 0 0 0 0 0
123
+1402341065 78044 0 0 0 0 0 0
124
+1402342238 78045 0 0 0 0 0 0
125
+1402428124 78049 0 0 0 0 0 0
126
+1402431333 78084 0 0 0 0 0 0
127
+1402511788 78089 0 0 0 0 0 0
128
+1402512041 78163 0 0 0 0 0 0
129
+1402595862 78163 0 0 0 0 0 0
130
+1403192924 78164 0 0 0 0 0 0
131
+1403206094 78187 0 0 0 0 0 0
132
+1403280442 78193 0 0 0 0 0 0
133
+1403535284 78194 0 0 0 0 0 0
134
+1403541811 78195 0 0 0 0 0 0
135
+1403636781 78915 0 0 0 0 0 0
136
+1403637528 78942 0 0 0 0 0 0
137
+1403724192 78981 0 0 0 0 0 0
138
+1403732386 78985 0 0 0 0 0 0
139
+1404142813 79122 0 0 0 0 0 0
140
+1404248505 79126 0 0 0 0 0 0
141
+1404915912 79235 0 0 0 0 0 0
142
+1404923241 79236 0 0 0 0 0 0
143
+1404923409 79295 0 0 0 0 0 0
144
+1405002584 79296 0 0 0 0 0 0
145
+1405003854 79310 0 0 0 0 0 0
146
+1405021172 79321 0 0 0 0 0 0
147
+1405023400 79339 0 0 0 0 0 0
148
+1405113290 79348 0 0 0 0 0 0
149
+1405348976 79350 0 0 0 0 0 0
150
+1405361989 79422 0 0 0 0 0 0
151
+1405364123 79437 0 0 0 0 0 0
152
+1405447703 79441 0 0 0 0 0 0
153
+1405540116 79443 0 0 0 0 0 0
154
+1406040988 79533 0 0 0 0 0 0
155
+1406041836 79540 0 0 0 0 0 0
156
+1406052691 79555 0 0 0 0 0 0
157
+1406055617 79580 0 0 0 0 0 0
158
+1406055678 79581 0 0 0 0 0 0
159
+1406225786 79677 0 0 0 0 0 0
160
+1406231654 79678 0 0 0 0 0 0
161
+1406235975 85571 0 0 0 0 0 0
162
+1406297884 85594 0 0 0 0 0 0
163
+1406312943 85746 0 0 0 0 0 0
164
+1406325886 85752 0 0 0 0 0 0
165
+1406563924 85761 0 0 0 0 0 0
166
+1406564839 85762 0 0 0 0 0 0
167
+1406670247 85884 0 0 0 0 0 0
168
+1406734781 85901 0 0 0 0 0 0
169
+1406754307 86541 0 0 0 0 0 0
170
+1406834212 86552 0 0 0 0 0 0
171
+1407185931 86553 0 0 0 0 0 0
172
+1407255136 86619 0 0 0 0 0 0
173
+1407268137 86620 0 0 0 0 0 0
174
+1408040736 86622 0 0 0 0 0 0
175
+1408138667 86622 0 0 0 0 0 0
176
+1408481183 86623 0 0 0 0 0 0
177
+1408976252 86624 0 0 0 0 0 0
178
+1408976366 86669 0 0 0 0 0 0
179
+1408976476 86669 0 0 0 0 0 0
180
+1408983121 86683 0 0 0 0 0 0
181
+1409078217 86713 0 0 0 0 0 0
182
+1409244667 86713 0 0 0 0 0 0
183
+1409253116 86978 0 0 0 0 0 0
184
+1409259944 87041 0 0 0 0 0 0
185
+1409672806 87123 0 0 0 0 0 0
186
+1410206464 87129 0 0 0 0 0 0
187
+1410207080 87130 0 0 0 0 0 0
188
+1410287098 87132 0 0 0 0 0 0
189
+1410551535 87149 0 0 0 0 0 0
190
+1411486906 87153 0 0 0 0 0 0
191
+1411658801 88057 0 0 0 0 0 0
192
+1412085855 88077 0 0 0 0 0 0
193
+1412093603 88189 0 0 0 0 0 0
194
+1412103242 88196 0 0 0 0 0 0
195
+1412103800 88198 0 0 0 0 0 0
196
+1412106592 88200 0 0 0 0 0 0
197
+1412110451 88218 0 0 0 0 0 0
198
+1412110505 88230 0 0 0 0 0 0
199
+1412110521 88331 0 0 0 0 0 0
200
+1412171582 88332 0 0 0 0 0 0
201
+1412178008 88342 0 0 0 0 0 0
202
+1412182838 88428 0 0 0 0 0 0
203
+1412265983 88431 0 0 0 0 0 0
204
+1412275768 88435 0 0 0 0 0 0
205
+1412369691 88439 0 0 0 0 0 0
206
+1412625476 88621 0 0 0 0 0 0
207
+1412715931 88622 0 0 0 0 0 0
208
+1413222749 88885 0 0 0 0 0 0
209
+1414522504 91054 0 0 0 0 0 0
210
+1415144579 91782 0 0 0 0 0 0
211
+1415218924 91785 0 0 0 0 0 0
212
+1415219022 91785 0 0 0 0 0 0
213
+1415220994 91867 0 0 0 0 0 0
214
+1415319788 91879 0 0 0 0 0 0
215
+1415319862 91881 0 0 0 0 0 0
216
+1415321087 91883 0 0 0 0 0 0
217
+1415395231 91883 0 0 0 0 0 0
218
+1415649515 91953 0 0 0 0 0 0
219
+1415652685 91974 0 0 0 0 0 0
220
+1415717488 91977 0 0 0 0 0 0
221
+1415724534 91982 0 0 0 0 0 0
222
+1415726692 92021 0 0 0 0 0 0
223
+1415728253 92036 0 0 0 0 0 0
224
+1415734870 92058 0 0 0 0 0 0
225
+1415736053 92059 0 0 0 0 0 0
226
+1415738826 92066 0 0 0 0 0 0
227
+1415742817 92346 0 0 0 0 0 0
228
+1415745888 92507 0 0 0 0 0 0
229
+1415823605 92559 0 0 0 0 0 0
230
+1415832007 92591 0 0 0 0 0 0
231
+1416239521 92592 0 0 0 0 0 0
232
+1416245778 92594 0 0 0 0 0 0
233
+1416350686 92638 0 0 0 0 0 0
234
+1416612373 92865 0 0 0 0 0 0
235
+1417464052 92875 0 0 0 0 0 0
236
+1417464390 92878 0 0 0 0 0 0
237
+1417534843 92952 0 0 0 0 0 0
238
+1417549520 92979 0 0 0 0 0 0
239
+1417553492 92989 0 0 0 0 0 0
240
+1417564577 93069 0 0 0 0 0 0
241
+1417640760 93089 0 0 0 0 0 0
242
+1418053512 93092 0 0 0 0 0 0
243
+1418318204 93116 0 0 0 0 0 0
244
+1418318222 93134 0 0 0 0 0 0
245
+1418318240 93138 0 0 0 0 0 0
246
+1418318297 93173 0 0 0 0 0 0
247
+1418318361 93174 0 0 0 0 0 0
248
+1418318417 93217 0 0 0 0 0 0
249
+1418318440 93218 0 0 0 0 0 0
250
+1418318684 93221 0 0 0 0 0 0
251
+1418421690 93224 0 0 0 0 0 0
252
+1418421802 93264 0 0 0 0 0 0
253
+1418746234 93299 0 0 0 0 0 0
254
+1418746500 93377 0 0 0 0 0 0
255
+1418752434 93395 0 0 0 0 0 0
256
+1418755439 93488 0 0 0 0 0 0
257
+1418849653 93497 0 0 0 0 0 0
258
+1418918217 93521 0 0 0 0 0 0
259
+1418940694 93661 0 0 0 0 0 0
260
+1418941834 93685 0 0 0 0 0 0
261
+1419356537 99684 0 0 0 0 0 0
262
+1421161999 99703 0 0 0 0 0 0
263
+1421164187 99713 0 0 0 0 0 0
264
+1421170472 99724 0 0 0 0 0 0
265
+1421182429 99726 0 0 0 0 0 0
266
+1421186075 99727 0 0 0 0 0 0
267
+1421333600 99732 0 0 0 0 0 0
268
+1421362394 99757 0 0 0 0 0 0
269
+1421781546 99780 0 0 0 0 0 0
270
+1421794398 99788 0 0 0 0 0 0
271
+1421858140 99792 0 0 0 0 0 0
272
+1421861618 99843 0 0 0 0 0 0
273
+1421865987 99884 0 0 0 0 0 0
274
+1421870518 99887 0 0 0 0 0 0
275
+1421942808 99893 0 0 0 0 0 0
276
+1421965255 99939 0 0 0 0 0 0
277
+1421965286 100061 0 0 0 0 0 0
278
+1421966627 100064 0 0 0 0 0 0
279
+1422288635 100064 0 0 0 0 0 0
280
+1422288804 100071 0 0 0 0 0 0
281
+1422289212 100071 0 0 0 0 0 0
282
+1422289952 100087 0 0 0 0 0 0
283
+1422290748 100091 0 0 0 0 0 0
284
+1422291553 100096 0 0 0 0 0 0
285
+1422310926 100177 0 0 0 0 0 0
286
+1422372141 100178 0 0 0 0 0 0
287
+1422375118 100181 0 0 0 0 0 0
288
+1422384120 100195 0 0 0 0 0 0
289
+1422386950 100246 0 0 0 0 0 0
290
+1422387963 100261 0 0 0 0 0 0
291
+1422463984 100410 0 0 0 0 0 0
292
+1422478361 100413 0 0 0 0 0 0
293
+1422481852 100762 0 0 0 0 0 0
294
+1422544534 100762 0 0 0 0 0 0
295
+1422565449 100764 0 0 0 0 0 0
296
+1422569286 100766 0 0 0 0 0 0
297
+1422569372 100846 0 0 0 0 0 0
298
+1422861179 100846 0 0 0 5 0 0
299
+1422894999 100854 0 0 0 5 0 0
300
+1422989658 100870 0 0 0 5 0 0
301
+1423167301 100920 0 0 0 5 0 0
302
+1423585125 100920 0 0 0 5 0 0
303
+1423590234 100922 0 0 0 5 0 0
304
+1423596075 100928 0 0 0 5 0 0
305
+1423603626 101018 0 0 0 5 0 0
306
+1423672020 101019 0 0 0 5 0 0
307
+1423753966 101019 0 0 0 5 0 0
308
+1424114643 101028 0 0 0 5 0 0
309
+1424188717 101042 0 0 0 5 0 0
310
+1424193093 101101 0 0 0 5 0 0
311
+1424201771 101142 0 0 0 5 0 0
312
+1424273633 101194 0 0 0 5 0 0
313
+1424279063 101242 0 0 0 5 0 0
314
+1424280071 101326 0 0 0 5 0 0
315
+1424281679 101357 0 0 0 5 0 0
316
+1424288424 101438 0 0 0 5 0 0
317
+1424288546 101520 0 0 0 5 0 0
318
+1424295016 101553 0 0 0 5 0 0
319
+1424465722 101565 0 0 0 5 0 0
320
+1424466815 101573 0 0 0 5 0 0
321
+1424467415 101662 0 0 0 5 0 0
322
+1424712442 101669 0 0 0 5 0 0
323
+1424716942 101730 0 0 0 5 0 0
324
+1424721680 101751 0 0 0 5 0 0
325
+1424791974 101756 0 0 0 5 0 0
326
+1424796525 101766 0 0 0 5 0 0
327
+1424798803 101771 0 0 0 5 0 0
328
+1424798945 101795 0 0 0 5 0 0
329
+1424803751 101915 0 0 0 5 0 0
330
+1424810387 101926 0 0 0 5 0 0
331
+1424810657 101941 0 0 0 5 0 0
332
+1424816551 102008 0 0 0 5 0 0
333
+1424818066 102010 0 0 0 5 0 0
334
+1424878655 102148 0 0 0 5 0 0
335
+1424884467 102148 0 0 0 5 0 0
336
+1424884849 102657 0 0 0 5 0 0
337
+1424892815 102721 0 0 0 5 0 0
338
+1424898072 102726 0 0 0 5 0 0
339
+1425051573 102736 0 0 0 5 0 0
340
+1425067581 102789 0 0 0 5 0 0
341
+1425067690 102794 0 0 0 5 0 0
342
+1425077014 102794 0 0 0 5 0 0
343
+1425309326 102794 0 0 0 5 0 0
344
+1425313422 102795 0 0 0 5 0 0
345
+1425329531 102795 0 0 0 5 0 0
346
+1425337212 102869 0 0 0 5 0 0
347
+1425342674 102877 0 0 0 5 0 0
348
+1425914337 102896 0 0 0 5 0 0
349
+1425917461 102914 0 0 0 5 0 0
350
+1425999980 102915 0 0 0 5 0 0
351
+1426267819 102918 0 0 0 5 0 0
352
+1426273408 102938 0 0 0 5 0 0
353
+1426280545 102946 0 0 0 5 0 0
354
+1426628250 102956 0 0 0 5 0 0
355
+1426778183 102958 0 0 0 5 0 0
356
+1426791438 102978 0 0 0 5 0 0
357
+1426803375 102985 0 0 0 5 0 0
358
+1426804222 102989 0 0 0 5 0 0
359
+1426890119 103061 0 0 0 5 0 0
360
+1427129667 103071 0 0 0 5 0 0
361
+1427133177 103105 0 0 0 5 0 0
362
+1427135813 103107 0 0 0 5 0 0
363
+1427137830 103185 0 0 0 5 0 0
364
+1427139221 103186 0 0 0 5 0 0
365
+1427141290 103201 0 0 0 5 0 0
366
+1427141834 103203 0 0 0 5 0 0
367
+1427206811 103207 0 0 0 5 0 0
368
+1427225995 103211 0 0 0 5 0 0
369
+1427226516 103213 0 0 0 5 0 0
370
+1427228366 131222 0 0 0 5 0 0
371
+1427267795 131222 0 0 0 74 0 0
372
+1427267837 131222 0 0 0 77 0 0
373
+1427294459 131228 0 0 0 77 0 0
374
+1427317966 131235 0 0 0 77 0 0
375
+1427335799 131235 0 0 0 77 0 0
376
+1427349002 131235 0 0 0 77 0 0
377
+1427349639 131235 0 0 0 79 0 0
378
+1427362185 131235 0 0 0 157 0 0
379
+1427362305 131235 0 0 0 161 0 0
380
+1427426891 131327 0 0 0 167 0 0
381
+1427488267 131335 0 0 0 167 0 0
382
+1427789357 131335 0 0 0 169 0 0
383
+1427789410 131337 0 0 0 195 0 0
384
+1427833035 131337 0 0 0 195 0 0
385
+1427917146 131337 0 0 0 195 0 0
386
+1427997681 131342 0 0 0 195 0 0
387
+1428443057 131359 0 0 0 195 0 0
388
+1428513842 131365 0 0 0 195 0 0
389
+1428521668 131382 0 0 0 195 0 0
390
+1428934715 131387 0 0 0 195 0 0
391
+1429112004 131391 0 0 0 195 0 0
392
+1429284766 235670 0 0 0 195 0 0
393
+1429304949 235700 0 0 0 195 0 0
394
+1429310422 235708 0 0 0 195 0 0
395
+1430403827 235710 0 0 0 195 0 0
396
+1431109116 235710 0 0 0 195 0 0
397
+1431110062 235718 0 0 0 195 0 0
398
+1431398393 235718 0 0 0 195 0 0
399
+1432310614 235718 23 0 0 195 0 0
400
+1432310966 235718 23 0 0 195 0 0
401
+1432311075 235718 23 0 0 195 0 0
402
+1432311088 235718 53 0 0 195 0 0
403
+1432311100 235718 53 0 0 195 0 0
404
+1432312050 235718 67 0 0 195 0 0
405
+1432588799 235718 67 1 0 195 0 0
406
+1432588896 235718 67 15 0 195 0 0
407
+1432588954 235718 67 17 0 195 0 0
408
+1432831392 235718 75 17 0 195 0 0
409
+1432831427 235718 75 17 0 195 0 0
410
+1432907636 235718 75 17 0 195 0 0
411
+1432929105 235718 122 17 0 195 0 0
412
+1433175771 235718 151 17 0 195 0 0
413
+1433464018 235718 151 75 0 195 0 0
414
+1433464310 235718 151 90 0 195 0 0
415
+1434132785 235718 151 90 0 195 0 0
416
+1434134581 235718 151 90 0 195 0 0
417
+1434138920 235718 157 90 0 195 0 0
418
+1434138982 235718 157 90 0 195 0 0
419
+1434139189 235718 158 90 0 195 0 0
420
+1434376340 235718 180 90 0 195 0 0
421
+1435836687 235718 180 90 0 195 0 0
422
+1435843824 235718 194 90 0 195 0 0
423
+1436849943 235718 194 90 0 195 8 0
424
+1438269784 235718 1029 90 0 195 8 0
425
+1438269918 235718 1029 90 0 195 8 0
426
+1438271263 235718 1035 90 0 195 8 0
427
+1438273348 235718 2894 90 0 195 8 0
428
+1438282294 235718 2894 90 0 195 8 0
429
+1438303550 235718 2935 90 0 195 8 0
430
+1439397263 235718 2965 90 0 195 8 0
431
+1439399146 235718 2982 90 0 195 8 0
432
+1441737290 235718 5196 90 0 195 8 0
433
+1442267605 235718 5300 90 0 195 8 0
434
+1442597513 235718 5308 90 0 195 8 0
435
+1442954764 235718 5308 90 0 195 8 0
436
+1443018794 235718 5331 90 0 195 8 0
437
+1443031570 235718 5370 90 0 195 8 0
438
+1443032056 235718 5370 90 0 195 8 0
439
+1443119585 235718 5390 90 0 195 8 0
440
+1443711951 235718 5465 90 0 195 8 0
441
+1443798856 235718 5478 90 0 195 8 0
442
+1449685453 235718 10423 90 0 195 8 0
443
+1450283272 235718 11105 90 0 195 8 0
444
+1450664183 235718 11363 90 0 195 8 0
445
+1450666600 235718 11389 90 0 195 8 0
446
+1452027169 235718 11389 93 0 195 8 0
447
+1452028386 235718 11389 1094 0 195 8 0
448
+1452200566 235718 11389 1143 0 195 8 0
449
+1452207430 235718 11389 1319 0 195 8 0
450
+1452207602 235718 11395 1324 0 195 8 0
451
+1452212539 235718 11420 1324 0 195 8 0
452
+1452280833 235718 11420 1325 0 195 8 0
453
+1452793688 235718 11439 1325 0 195 8 0
454
+1453224499 235718 11448 1325 0 195 8 0
455
+1453225168 235718 11457 1325 0 195 8 0
456
+1453230613 235718 11457 1325 0 195 8 0
457
+1453323055 235718 11525 1325 0 195 8 0
458
+1453493571 235718 11562 1325 0 195 8 0
459
+1453704637 235718 11562 1325 0 195 8 24
460
+1453825100 235718 11590 1325 0 195 8 24
461
+1453837575 235718 11715 1325 0 195 8 24
462
+1453848840 235718 11715 1325 0 195 8 24
463
+1453953447 235718 13364 1380 0 195 8 24
464
+1454689867 235718 13364 1431 0 195 8 24
465
+1455057770 235718 13364 1447 0 195 8 24
466
+1455660753 235718 13423 1447 0 195 8 24
467
+1455830872 235718 13459 1447 0 195 8 24
468
+1455831309 235718 13475 1447 0 195 8 24
469
+1455831437 235718 13502 1447 0 195 8 24
470
+1455894228 235718 13502 1708 0 195 8 24
471
+1455897059 235718 13502 1713 0 195 8 24
472
+1456110772 235718 13525 1713 0 195 8 24
473
+1456266997 235718 13525 1718 0 195 8 24
474
+1456267705 235718 13525 1724 0 195 8 24
475
+1456269047 235718 14252 1724 0 195 8 24
476
+1456506668 235718 14252 2037 0 195 8 24
477
+1456806554 235718 14268 2037 0 195 8 24
478
+1457141043 235718 14273 2037 0 195 8 24
479
+1457452520 235718 14273 2045 0 195 8 24
480
+1457488422 235718 14273 2051 0 195 8 24
481
+1457563999 235718 14273 2249 0 195 8 24
482
+1457644085 235718 14273 2539 0 195 8 24
483
+1457644884 235718 14273 2683 0 195 8 24
484
+1457646092 235718 14273 2684 0 195 8 24
485
+1457987370 235718 14281 2684 0 195 8 24
486
+1457991490 235718 14281 2692 0 195 8 24
487
+1458004818 235718 14303 2692 0 195 8 24
488
+1458593314 235718 14310 2692 0 195 8 24
489
+1458765547 235718 14382 2692 0 195 8 24
490
+1458914088 235718 14390 2692 0 195 8 24
491
+1458991612 235718 14405 2692 0 195 8 24
492
+1459133960 235718 14421 2692 0 195 8 24
493
+1460047216 235718 14429 2692 0 195 8 24
494
+1460047380 235718 14451 2730 0 195 8 24
495
+1460375644 235718 14636 2730 0 195 8 24
496
+1460380408 235718 14685 2764 0 195 8 24
497
+1460407428 235718 14736 2764 0 195 8 24
498
+1460569764 235718 14736 2764 0 195 8 24
499
+1461074070 235718 14755 2764 0 195 8 24
500
+1461270656 235718 14768 2835 0 195 8 24
501
+1461354039 235718 14768 2841 0 195 8 24
502
+1461551595 235718 14768 2841 0 195 8 24
503
+1461552637 235718 14791 2841 0 195 8 24
504
+1462227334 235718 14959 2841 0 195 8 24
505
+1463152815 235718 14959 2906 0 195 8 24
506
+1463157617 235718 14959 2945 0 195 8 24
507
+1463451445 235718 14959 2980 0 195 8 24
508
+1463451804 235718 14959 3031 0 195 8 24
509
+1463680867 235718 15014 3031 0 195 8 24
510
+1463758054 235718 15029 3031 0 195 8 24
511
+1464031184 235718 15037 3031 0 195 8 24
512
+1464096601 235718 15043 3031 0 195 8 24
513
+1464208709 235718 15043 3031 0 195 8 24
514
+1464237088 235718 15084 3031 0 195 8 24
515
+1464237194 235718 15115 3031 0 195 8 24
516
+1464293889 235718 15115 3031 0 195 8 24
517
+1464793340 235718 15120 3102 0 195 8 24
518
+1464880180 235718 15120 3102 0 195 8 24
519
+1464895801 235718 15139 3102 0 195 8 24
520
+1464902654 235718 15155 3102 0 195 8 24
521
+1465319220 235718 15195 3168 0 195 8 24
522
+1465424498 235718 15216 3168 0 195 8 24
523
+1466648922 235718 15263 3168 0 195 8 24
524
+1466649684 235718 15270 3168 0 195 8 24
525
+1466650102 235718 15335 3168 0 195 8 24
526
+1466701155 235718 15352 3168 0 195 8 24
527
+1466735347 235718 15361 3168 0 195 8 24
528
+1466736165 235718 15387 3174 0 195 8 24
529
+1468180054 235718 15459 3240 0 195 8 24
530
+1468292316 235718 15459 3262 0 195 8 24
531
+1468876762 235718 15459 3270 0 195 8 24
532
+1468876920 235718 15459 3277 0 195 8 24
533
+1469121939 235718 15459 3315 0 195 8 24
534
+1469157573 235718 15487 3315 0 195 8 24
535
+1469552934 235718 15487 3346 0 195 8 24
536
+1469589171 235718 15552 3360 0 195 8 24
537
+1469640240 235718 15577 3360 0 195 8 24
538
+1469723242 235718 15577 3362 0 195 8 24
539
+1469725928 235718 15602 3362 0 195 8 24
540
+1469780404 235718 15617 3362 0 195 8 24
541
+1475506438 235718 15618 3362 0 195 8 24
542
+1475508328 235718 15765 3362 0 195 8 24
543
+1475605531 235718 15898 3362 0 195 8 24
544
+1475759454 235718 15918 3362 0 195 8 24
545
+1476301940 235718 15918 3362 0 195 8 24
546
+1477421556 235718 15918 3376 0 195 8 24
547
+1477427877 235718 15970 3439 0 195 8 24
548
+1477433230 235718 15970 3447 0 195 8 24
549
+1477694191 235718 15970 3450 0 195 8 24
550
+1477939269 235718 15970 3497 0 195 8 24
551
+1477941012 235718 15970 3575 0 195 8 24
552
+1478115005 235718 15970 3575 0 195 8 24
553
+1478288605 235718 15970 3627 0 195 8 24
554
+1478551645 235718 15970 3628 0 195 8 24
555
+1478792001 235718 15970 3651 0 195 8 24
556
+1478814382 235718 15970 3651 0 195 8 24
557
+1478816505 235718 15970 3665 0 195 8 24
558
+1478878515 235718 15970 3682 0 195 8 24
559
+1479419832 235718 15970 3685 0 195 8 24
560
+1479846967 235718 15970 3687 0 195 8 24
561
+1480530088 235718 15970 3687 0 195 8 24
562
+1481670500 235718 15970 3740 0 195 8 24
563
+1482181507 235718 15970 3740 1 195 8 24
564
+1482336682 235718 15970 3746 1 195 8 24
565
+1482340119 235718 15970 3753 1 195 8 24
566
+1482353511 235718 15970 3771 11 195 8 24
567
+1482359008 235718 15970 3771 32 195 8 24
568
+1483455272 235718 15970 3796 76 195 8 24
569
+1484342644 235718 15970 3796 77 195 8 24
570
+1484684949 235718 15970 3796 99 195 8 24
571
+1484772851 235718 15970 3800 100 195 8 24
572
+1485799035 235718 15970 120285 116 195 8 24
573
+1485877540 235718 15970 120293 116 195 8 24
574
+1485877851 235718 15970 120317 116 195 8 24
575
+1485877920 235718 15970 120318 116 195 8 24
576
+1485878121 235718 15970 120340 116 195 8 24
577
+1486146025 235718 15970 120354 116 195 8 24
578
+1486412376 235718 15970 120363 116 195 8 24
579
+1486499625 235718 15970 120404 116 195 8 24
580
+1486755546 235718 15970 120405 116 195 8 24
581
+1487109340 235718 15970 120444 116 195 8 24
582
+1487277883 235718 15970 120444 1305 195 8 24
583
+1487783266 235718 15970 120444 1313 195 8 24
584
+1487946215 235718 15970 120503 1347 195 8 24
585
+1487965862 235718 15970 120503 1369 195 8 24
586
+1487966474 235718 15970 120503 1429 195 8 24
587
+1487966964 235718 15970 120503 1429 195 8 24
588
+1488313813 235718 15970 120503 1461 195 8 24
589
+1488406636 235718 15970 120525 1461 195 8 24
590
+1488407109 235718 15970 120587 1461 195 8 24
591
+1488409193 235718 15970 120620 1461 195 8 24
592
+1488560599 235718 15970 120718 1461 195 8 24
593
+1488773682 235718 15970 120718 1545 195 8 24
594
+1488814084 235718 15970 120718 1545 195 8 24
595
+1488815988 235718 15970 120747 1545 195 8 24
596
+1489686589 235718 15970 120753 1545 195 8 24
597
+1490023066 235718 15970 120755 1545 195 8 24
598
+1490035644 235718 15970 120761 1545 195 8 24
599
+1490041875 235718 15970 120763 1545 195 8 24
600
+1490113585 235718 15970 120763 1545 195 8 24
601
+1490120437 235718 15970 120764 1545 195 8 24
602
+1490128257 235718 15970 120783 1545 195 8 24
603
+1490645760 235718 15970 120783 1545 195 8 24
604
+1490725467 235718 15970 120858 1545 195 8 24
605
+1491336954 235718 15970 120858 1545 195 8 24
606
+1491341896 235718 15970 120875 1545 195 8 24
607
+1492026512 235718 15970 120893 1545 195 8 24
608
+1492107919 235718 15970 120898 1545 195 8 24
609
+1492108901 235718 15970 120998 1545 195 8 24
610
+1492435237 235718 15970 120998 1545 195 8 24
611
+1492435982 235718 15970 120998 1545 195 8 24
612
+1492454272 235718 15970 121021 1545 195 8 24
613
+1493052262 235718 15970 121021 1646 195 8 24
614
+1493153070 235718 15970 121070 1646 195 8 24
615
+1493407521 235718 15970 121078 1646 195 8 24
616
+1493412286 235718 15970 121093 1646 195 8 24

+ 617
- 0
FlaskTest/static/data/test_repo/commits_by_author_copy.tsv Целия файл

@@ -0,0 +1,617 @@
1
+date	author1	author2	author3	author4	author5	author6	author7
2
+1392392111	16	0	0	0	0	0	0
3
+1392397527	17	0	0	0	0	0	0
4
+1392830779	18	0	0	0	0	0	0
5
+1392841719	19	0	0	0	0	0	0
6
+1392844458	20	0	0	0	0	0	0
7
+1392852040	21	0	0	0	0	0	0
8
+1392933375	22	0	0	0	0	0	0
9
+1393016220	23	0	0	0	0	0	0
10
+1393018032	24	0	0	0	0	0	0
11
+1393272179	25	0	0	0	0	0	0
12
+1393356877	26	0	0	0	0	0	0
13
+1393357414	27	0	0	0	0	0	0
14
+1393437911	28	0	0	0	0	0	0
15
+1393442422	29	0	0	0	0	0	0
16
+1393515175	30	0	0	0	0	0	0
17
+1393531146	31	0	0	0	0	0	0
18
+1393532869	32	0	0	0	0	0	0
19
+1393534919	33	0	0	0	0	0	0
20
+1393540722	34	0	0	0	0	0	0
21
+1393861723	35	0	0	0	0	0	0
22
+1394035569	36	0	0	0	0	0	0
23
+1394039661	37	0	0	0	0	0	0
24
+1394054732	38	0	0	0	0	0	0
25
+1394055920	39	0	0	0	0	0	0
26
+1394056588	40	0	0	0	0	0	0
27
+1394119672	41	0	0	0	0	0	0
28
+1394125181	42	0	0	0	0	0	0
29
+1394145913	43	0	0	0	0	0	0
30
+1394460626	44	0	0	0	0	0	0
31
+1394465228	45	0	0	0	0	0	0
32
+1394467610	46	0	0	0	0	0	0
33
+1394470538	47	0	0	0	0	0	0
34
+1394478517	48	0	0	0	0	0	0
35
+1394487470	49	0	0	0	0	0	0
36
+1394546208	50	0	0	0	0	0	0
37
+1394549933	51	0	0	0	0	0	0
38
+1394573638	52	0	0	0	0	0	0
39
+1394730184	53	0	0	0	0	0	0
40
+1394732727	55	0	0	0	0	0	0
41
+1395071699	56	0	0	0	0	0	0
42
+1395080218	57	0	0	0	0	0	0
43
+1395084092	58	0	0	0	0	0	0
44
+1395243935	59	0	0	0	0	0	0
45
+1395261876	60	0	0	0	0	0	0
46
+1395324514	61	0	0	0	0	0	0
47
+1395347240	62	0	0	0	0	0	0
48
+1395418555	63	0	0	0	0	0	0
49
+1395675797	64	0	0	0	0	0	0
50
+1395682889	65	0	0	0	0	0	0
51
+1395687258	66	0	0	0	0	0	0
52
+1395687758	67	0	0	0	0	0	0
53
+1395695371	68	0	0	0	0	0	0
54
+1395759924	69	0	0	0	0	0	0
55
+1395781761	70	0	0	0	0	0	0
56
+1395931149	71	0	0	0	0	0	0
57
+1395955619	72	0	0	0	0	0	0
58
+1396017083	73	0	0	0	0	0	0
59
+1396018452	74	0	0	0	0	0	0
60
+1396033193	78	0	0	0	0	0	0
61
+1396034898	79	0	0	0	0	0	0
62
+1396036712	80	0	0	0	0	0	0
63
+1396293025	81	0	0	0	0	0	0
64
+1396379166	82	0	0	0	0	0	0
65
+1396379379	83	0	0	0	0	0	0
66
+1396469103	84	0	0	0	0	0	0
67
+1396469896	85	0	0	0	0	0	0
68
+1396472129	86	0	0	0	0	0	0
69
+1396474065	87	0	0	0	0	0	0
70
+1396541016	88	0	0	0	0	0	0
71
+1396638448	89	0	0	0	0	0	0
72
+1396639995	90	0	0	0	0	0	0
73
+1396885855	91	0	0	0	0	0	0
74
+1396899853	92	0	0	0	0	0	0
75
+1396984335	94	0	0	0	0	0	0
76
+1397247297	95	0	0	0	0	0	0
77
+1397248748	96	0	0	0	0	0	0
78
+1397484607	97	0	0	0	0	0	0
79
+1397491609	98	0	0	0	0	0	0
80
+1397501654	99	0	0	0	0	0	0
81
+1397502552	100	0	0	0	0	0	0
82
+1397508171	101	0	0	0	0	0	0
83
+1397593977	102	0	0	0	0	0	0
84
+1397596834	103	0	0	0	0	0	0
85
+1397681905	104	0	0	0	0	0	0
86
+1397747221	105	0	0	0	0	0	0
87
+1397751241	106	0	0	0	0	0	0
88
+1397754140	107	0	0	0	0	0	0
89
+1398106573	108	0	0	0	0	0	0
90
+1398190593	109	0	0	0	0	0	0
91
+1398265425	110	0	0	0	0	0	0
92
+1398265460	111	0	0	0	0	0	0
93
+1398268479	112	0	0	0	0	0	0
94
+1398271735	113	0	0	0	0	0	0
95
+1398278671	114	0	0	0	0	0	0
96
+1398282984	115	0	0	0	0	0	0
97
+1398373748	116	0	0	0	0	0	0
98
+1398434861	117	0	0	0	0	0	0
99
+1398437720	118	0	0	0	0	0	0
100
+1398697019	119	0	0	0	0	0	0
101
+1398699790	120	0	0	0	0	0	0
102
+1398715690	121	0	0	0	0	0	0
103
+1398716189	122	0	0	0	0	0	0
104
+1398718090	123	0	0	0	0	0	0
105
+1398797186	124	0	0	0	0	0	0
106
+1398799360	125	0	0	0	0	0	0
107
+1398800327	129	0	0	0	0	0	0
108
+1399305448	130	0	0	0	0	0	0
109
+1399315892	131	0	0	0	0	0	0
110
+1399315910	132	0	0	0	0	0	0
111
+1399316102	133	0	0	0	0	0	0
112
+1399387116	134	0	0	0	0	0	0
113
+1399388283	135	0	0	0	0	0	0
114
+1399389375	136	0	0	0	0	0	0
115
+1399564137	137	0	0	0	0	0	0
116
+1399666061	138	0	0	0	0	0	0
117
+1399905905	140	0	0	0	0	0	0
118
+1399991114	141	0	0	0	0	0	0
119
+1400189261	142	0	0	0	0	0	0
120
+1400529215	144	0	0	0	0	0	0
121
+1401293283	145	0	0	0	0	0	0
122
+1401296335	146	0	0	0	0	0	0
123
+1401386785	147	0	0	0	0	0	0
124
+1402341065	148	0	0	0	0	0	0
125
+1402342238	149	0	0	0	0	0	0
126
+1402428124	150	0	0	0	0	0	0
127
+1402431333	151	0	0	0	0	0	0
128
+1402511788	152	0	0	0	0	0	0
129
+1402512041	153	0	0	0	0	0	0
130
+1402595862	154	0	0	0	0	0	0
131
+1403192924	155	0	0	0	0	0	0
132
+1403206094	157	0	0	0	0	0	0
133
+1403280442	158	0	0	0	0	0	0
134
+1403535284	159	0	0	0	0	0	0
135
+1403541811	160	0	0	0	0	0	0
136
+1403636781	161	0	0	0	0	0	0
137
+1403637528	162	0	0	0	0	0	0
138
+1403724192	163	0	0	0	0	0	0
139
+1403732386	164	0	0	0	0	0	0
140
+1404142813	165	0	0	0	0	0	0
141
+1404248505	166	0	0	0	0	0	0
142
+1404915912	167	0	0	0	0	0	0
143
+1404923241	168	0	0	0	0	0	0
144
+1404923409	169	0	0	0	0	0	0
145
+1405002584	170	0	0	0	0	0	0
146
+1405003854	171	0	0	0	0	0	0
147
+1405021172	172	0	0	0	0	0	0
148
+1405023400	173	0	0	0	0	0	0
149
+1405113290	174	0	0	0	0	0	0
150
+1405348976	175	0	0	0	0	0	0
151
+1405361989	176	0	0	0	0	0	0
152
+1405364123	177	0	0	0	0	0	0
153
+1405447703	178	0	0	0	0	0	0
154
+1405540116	179	0	0	0	0	0	0
155
+1406040988	180	0	0	0	0	0	0
156
+1406041836	181	0	0	0	0	0	0
157
+1406052691	182	0	0	0	0	0	0
158
+1406055617	183	0	0	0	0	0	0
159
+1406055678	184	0	0	0	0	0	0
160
+1406225786	185	0	0	0	0	0	0
161
+1406231654	186	0	0	0	0	0	0
162
+1406235975	191	0	0	0	0	0	0
163
+1406297884	192	0	0	0	0	0	0
164
+1406312943	194	0	0	0	0	0	0
165
+1406325886	195	0	0	0	0	0	0
166
+1406563924	196	0	0	0	0	0	0
167
+1406564839	197	0	0	0	0	0	0
168
+1406670247	198	0	0	0	0	0	0
169
+1406734781	199	0	0	0	0	0	0
170
+1406754307	200	0	0	0	0	0	0
171
+1406834212	201	0	0	0	0	0	0
172
+1407185931	202	0	0	0	0	0	0
173
+1407255136	203	0	0	0	0	0	0
174
+1407268137	204	0	0	0	0	0	0
175
+1408040736	205	0	0	0	0	0	0
176
+1408138667	206	0	0	0	0	0	0
177
+1408481183	207	0	0	0	0	0	0
178
+1408976252	208	0	0	0	0	0	0
179
+1408976366	209	0	0	0	0	0	0
180
+1408976476	210	0	0	0	0	0	0
181
+1408983121	211	0	0	0	0	0	0
182
+1409078217	212	0	0	0	0	0	0
183
+1409244667	213	0	0	0	0	0	0
184
+1409253116	215	0	0	0	0	0	0
185
+1409259944	216	0	0	0	0	0	0
186
+1409672806	218	0	0	0	0	0	0
187
+1410206464	219	0	0	0	0	0	0
188
+1410207080	220	0	0	0	0	0	0
189
+1410287098	221	0	0	0	0	0	0
190
+1410551535	222	0	0	0	0	0	0
191
+1411486906	223	0	0	0	0	0	0
192
+1411658801	226	0	0	0	0	0	0
193
+1412085855	227	0	0	0	0	0	0
194
+1412093603	229	0	0	0	0	0	0
195
+1412103242	230	0	0	0	0	0	0
196
+1412103800	231	0	0	0	0	0	0
197
+1412106592	232	0	0	0	0	0	0
198
+1412110451	233	0	0	0	0	0	0
199
+1412110505	234	0	0	0	0	0	0
200
+1412110521	235	0	0	0	0	0	0
201
+1412171582	236	0	0	0	0	0	0
202
+1412178008	237	0	0	0	0	0	0
203
+1412182838	238	0	0	0	0	0	0
204
+1412265983	239	0	0	0	0	0	0
205
+1412275768	240	0	0	0	0	0	0
206
+1412369691	241	0	0	0	0	0	0
207
+1412625476	242	0	0	0	0	0	0
208
+1412715931	243	0	0	0	0	0	0
209
+1413222749	245	0	0	0	0	0	0
210
+1414522504	247	0	0	0	0	0	0
211
+1415144579	248	0	0	0	0	0	0
212
+1415218924	249	0	0	0	0	0	0
213
+1415219022	250	0	0	0	0	0	0
214
+1415220994	252	0	0	0	0	0	0
215
+1415319788	253	0	0	0	0	0	0
216
+1415319862	254	0	0	0	0	0	0
217
+1415321087	255	0	0	0	0	0	0
218
+1415395231	256	0	0	0	0	0	0
219
+1415649515	257	0	0	0	0	0	0
220
+1415652685	258	0	0	0	0	0	0
221
+1415717488	259	0	0	0	0	0	0
222
+1415724534	260	0	0	0	0	0	0
223
+1415726692	261	0	0	0	0	0	0
224
+1415728253	262	0	0	0	0	0	0
225
+1415734870	263	0	0	0	0	0	0
226
+1415736053	264	0	0	0	0	0	0
227
+1415738826	265	0	0	0	0	0	0
228
+1415742817	266	0	0	0	0	0	0
229
+1415745888	267	0	0	0	0	0	0
230
+1415823605	268	0	0	0	0	0	0
231
+1415832007	269	0	0	0	0	0	0
232
+1416239521	270	0	0	0	0	0	0
233
+1416245778	271	0	0	0	0	0	0
234
+1416350686	272	0	0	0	0	0	0
235
+1416612373	273	0	0	0	0	0	0
236
+1417464052	274	0	0	0	0	0	0
237
+1417464390	275	0	0	0	0	0	0
238
+1417534843	276	0	0	0	0	0	0
239
+1417549520	277	0	0	0	0	0	0
240
+1417553492	278	0	0	0	0	0	0
241
+1417564577	279	0	0	0	0	0	0
242
+1417640760	280	0	0	0	0	0	0
243
+1418053512	281	0	0	0	0	0	0
244
+1418318204	282	0	0	0	0	0	0
245
+1418318222	283	0	0	0	0	0	0
246
+1418318240	284	0	0	0	0	0	0
247
+1418318297	285	0	0	0	0	0	0
248
+1418318361	286	0	0	0	0	0	0
249
+1418318417	287	0	0	0	0	0	0
250
+1418318440	288	0	0	0	0	0	0
251
+1418318684	290	0	0	0	0	0	0
252
+1418421690	291	0	0	0	0	0	0
253
+1418421802	294	0	0	0	0	0	0
254
+1418746234	295	0	0	0	0	0	0
255
+1418746500	296	0	0	0	0	0	0
256
+1418752434	297	0	0	0	0	0	0
257
+1418755439	298	0	0	0	0	0	0
258
+1418849653	299	0	0	0	0	0	0
259
+1418918217	300	0	0	0	0	0	0
260
+1418940694	301	0	0	0	0	0	0
261
+1418941834	302	0	0	0	0	0	0
262
+1419356537	308	0	0	0	0	0	0
263
+1421161999	309	0	0	0	0	0	0
264
+1421164187	310	0	0	0	0	0	0
265
+1421170472	311	0	0	0	0	0	0
266
+1421182429	312	0	0	0	0	0	0
267
+1421186075	313	0	0	0	0	0	0
268
+1421333600	314	0	0	0	0	0	0
269
+1421362394	315	0	0	0	0	0	0
270
+1421781546	316	0	0	0	0	0	0
271
+1421794398	317	0	0	0	0	0	0
272
+1421858140	318	0	0	0	0	0	0
273
+1421861618	319	0	0	0	0	0	0
274
+1421865987	320	0	0	0	0	0	0
275
+1421870518	321	0	0	0	0	0	0
276
+1421942808	322	0	0	0	0	0	0
277
+1421965255	323	0	0	0	0	0	0
278
+1421965286	324	0	0	0	0	0	0
279
+1421966627	325	0	0	0	0	0	0
280
+1422288635	326	0	0	0	0	0	0
281
+1422288804	327	0	0	0	0	0	0
282
+1422289212	328	0	0	0	0	0	0
283
+1422289952	329	0	0	0	0	0	0
284
+1422290748	330	0	0	0	0	0	0
285
+1422291553	331	0	0	0	0	0	0
286
+1422310926	332	0	0	0	0	0	0
287
+1422372141	333	0	0	0	0	0	0
288
+1422375118	334	0	0	0	0	0	0
289
+1422384120	335	0	0	0	0	0	0
290
+1422386950	336	0	0	0	0	0	0
291
+1422387963	337	0	0	0	0	0	0
292
+1422463984	338	0	0	0	0	0	0
293
+1422478361	339	0	0	0	0	0	0
294
+1422481852	340	0	0	0	0	0	0
295
+1422544534	341	0	0	0	0	0	0
296
+1422565449	342	0	0	0	0	0	0
297
+1422569286	343	0	0	0	0	0	0
298
+1422569372	344	0	0	0	0	0	0
299
+1422861179	344	0	0	0	1	0	0
300
+1422894999	346	0	0	0	1	0	0
301
+1422989658	347	0	0	0	1	0	0
302
+1423167301	348	0	0	0	1	0	0
303
+1423585125	349	0	0	0	1	0	0
304
+1423590234	350	0	0	0	1	0	0
305
+1423596075	351	0	0	0	1	0	0
306
+1423603626	352	0	0	0	1	0	0
307
+1423672020	353	0	0	0	1	0	0
308
+1423753966	354	0	0	0	1	0	0
309
+1424114643	355	0	0	0	1	0	0
310
+1424188717	356	0	0	0	1	0	0
311
+1424193093	357	0	0	0	1	0	0
312
+1424201771	358	0	0	0	1	0	0
313
+1424273633	359	0	0	0	1	0	0
314
+1424279063	360	0	0	0	1	0	0
315
+1424280071	361	0	0	0	1	0	0
316
+1424281679	362	0	0	0	1	0	0
317
+1424288424	363	0	0	0	1	0	0
318
+1424288546	364	0	0	0	1	0	0
319
+1424295016	365	0	0	0	1	0	0
320
+1424465722	366	0	0	0	1	0	0
321
+1424466815	367	0	0	0	1	0	0
322
+1424467415	368	0	0	0	1	0	0
323
+1424712442	369	0	0	0	1	0	0
324
+1424716942	370	0	0	0	1	0	0
325
+1424721680	371	0	0	0	1	0	0
326
+1424791974	372	0	0	0	1	0	0
327
+1424796525	373	0	0	0	1	0	0
328
+1424798803	374	0	0	0	1	0	0
329
+1424798945	375	0	0	0	1	0	0
330
+1424803751	376	0	0	0	1	0	0
331
+1424810387	377	0	0	0	1	0	0
332
+1424810657	378	0	0	0	1	0	0
333
+1424816551	379	0	0	0	1	0	0
334
+1424818066	380	0	0	0	1	0	0
335
+1424878655	381	0	0	0	1	0	0
336
+1424884467	382	0	0	0	1	0	0
337
+1424884849	383	0	0	0	1	0	0
338
+1424892815	384	0	0	0	1	0	0
339
+1424898072	385	0	0	0	1	0	0
340
+1425051573	386	0	0	0	1	0	0
341
+1425067581	387	0	0	0	1	0	0
342
+1425067690	388	0	0	0	1	0	0
343
+1425077014	389	0	0	0	1	0	0
344
+1425309326	390	0	0	0	1	0	0
345
+1425313422	391	0	0	0	1	0	0
346
+1425329531	392	0	0	0	1	0	0
347
+1425337212	393	0	0	0	1	0	0
348
+1425342674	394	0	0	0	1	0	0
349
+1425914337	395	0	0	0	1	0	0
350
+1425917461	396	0	0	0	1	0	0
351
+1425999980	397	0	0	0	1	0	0
352
+1426267819	398	0	0	0	1	0	0
353
+1426273408	399	0	0	0	1	0	0
354
+1426280545	400	0	0	0	1	0	0
355
+1426628250	402	0	0	0	1	0	0
356
+1426778183	403	0	0	0	1	0	0
357
+1426791438	404	0	0	0	1	0	0
358
+1426803375	405	0	0	0	1	0	0
359
+1426804222	406	0	0	0	1	0	0
360
+1426890119	407	0	0	0	1	0	0
361
+1427129667	408	0	0	0	1	0	0
362
+1427133177	409	0	0	0	1	0	0
363
+1427135813	410	0	0	0	1	0	0
364
+1427137830	411	0	0	0	1	0	0
365
+1427139221	412	0	0	0	1	0	0
366
+1427141290	413	0	0	0	1	0	0
367
+1427141834	414	0	0	0	1	0	0
368
+1427206811	415	0	0	0	1	0	0
369
+1427225995	416	0	0	0	1	0	0
370
+1427226516	417	0	0	0	1	0	0
371
+1427228366	419	0	0	0	1	0	0
372
+1427267795	419	0	0	0	2	0	0
373
+1427267837	419	0	0	0	3	0	0
374
+1427294459	420	0	0	0	3	0	0
375
+1427317966	421	0	0	0	3	0	0
376
+1427335799	422	0	0	0	3	0	0
377
+1427349002	422	0	0	0	4	0	0
378
+1427349639	422	0	0	0	5	0	0
379
+1427362185	422	0	0	0	6	0	0
380
+1427362305	422	0	0	0	7	0	0
381
+1427426891	424	0	0	0	8	0	0
382
+1427488267	425	0	0	0	8	0	0
383
+1427789357	425	0	0	0	9	0	0
384
+1427789410	426	0	0	0	10	0	0
385
+1427833035	427	0	0	0	10	0	0
386
+1427917146	428	0	0	0	10	0	0
387
+1427997681	429	0	0	0	10	0	0
388
+1428443057	430	0	0	0	10	0	0
389
+1428513842	431	0	0	0	10	0	0
390
+1428521668	432	0	0	0	10	0	0
391
+1428934715	433	0	0	0	10	0	0
392
+1429112004	434	0	0	0	10	0	0
393
+1429284766	435	0	0	0	10	0	0
394
+1429304949	436	0	0	0	10	0	0
395
+1429310422	437	0	0	0	10	0	0
396
+1430403827	438	0	0	0	10	0	0
397
+1431109116	438	1	0	0	10	0	0
398
+1431110062	439	2	0	0	10	0	0
399
+1431398393	439	3	0	0	10	0	0
400
+1432310614	439	4	0	0	10	0	0
401
+1432310966	439	5	0	0	10	0	0
402
+1432311075	439	6	0	0	10	0	0
403
+1432311088	439	7	0	0	10	0	0
404
+1432311100	439	8	0	0	10	0	0
405
+1432312050	439	9	0	0	10	0	0
406
+1432588799	439	9	1	0	10	0	0
407
+1432588896	439	9	2	0	10	0	0
408
+1432588954	439	9	3	0	10	0	0
409
+1432831392	439	10	3	0	10	0	0
410
+1432831427	439	11	3	0	10	0	0
411
+1432907636	439	12	3	0	10	0	0
412
+1432929105	439	13	3	0	10	0	0
413
+1433175771	439	14	3	0	10	0	0
414
+1433464018	439	14	4	0	10	0	0
415
+1433464310	439	14	5	0	10	0	0
416
+1434132785	439	15	5	0	10	0	0
417
+1434134581	439	16	5	0	10	0	0
418
+1434138920	439	17	5	0	10	0	0
419
+1434138982	439	18	5	0	10	0	0
420
+1434139189	439	19	5	0	10	0	0
421
+1434376340	439	20	5	0	10	0	0
422
+1435836687	439	21	5	0	10	0	0
423
+1435843824	439	22	5	0	10	0	0
424
+1436849943	439	22	5	0	10	1	0
425
+1438269784	439	23	5	0	10	1	0
426
+1438269918	439	24	5	0	10	1	0
427
+1438271263	439	25	5	0	10	1	0
428
+1438273348	439	26	5	0	10	1	0
429
+1438282294	439	27	5	0	10	1	0
430
+1438303550	439	28	5	0	10	1	0
431
+1439397263	439	29	5	0	10	1	0
432
+1439399146	439	30	5	0	10	1	0
433
+1441737290	439	31	5	0	10	1	0
434
+1442267605	439	32	5	0	10	1	0
435
+1442597513	439	34	5	0	10	1	0
436
+1442954764	439	35	5	0	10	1	0
437
+1443018794	439	36	5	0	10	1	0
438
+1443031570	439	37	5	0	10	1	0
439
+1443032056	439	38	5	0	10	1	0
440
+1443119585	439	39	5	0	10	1	0
441
+1443711951	439	40	5	0	10	1	0
442
+1443798856	439	41	5	0	10	1	0
443
+1449685453	439	42	5	0	10	1	0
444
+1450283272	439	43	5	0	10	1	0
445
+1450664183	439	44	5	0	10	1	0
446
+1450666600	439	45	5	0	10	1	0
447
+1452027169	439	45	6	0	10	1	0
448
+1452028386	439	45	7	0	10	1	0
449
+1452200566	439	45	8	0	10	1	0
450
+1452207430	439	45	9	0	10	1	0
451
+1452207602	439	47	10	0	10	1	0
452
+1452212539	439	48	10	0	10	1	0
453
+1452280833	439	48	11	0	10	1	0
454
+1452793688	439	49	11	0	10	1	0
455
+1453224499	439	50	11	0	10	1	0
456
+1453225168	439	52	11	0	10	1	0
457
+1453230613	439	53	11	0	10	1	0
458
+1453323055	439	54	11	0	10	1	0
459
+1453493571	439	55	11	0	10	1	0
460
+1453704637	439	55	11	0	10	1	1
461
+1453825100	439	56	11	0	10	1	1
462
+1453837575	439	60	11	0	10	1	1
463
+1453848840	439	61	11	0	10	1	1
464
+1453953447	439	63	12	0	10	1	1
465
+1454689867	439	63	13	0	10	1	1
466
+1455057770	439	63	14	0	10	1	1
467
+1455660753	439	64	14	0	10	1	1
468
+1455830872	439	65	14	0	10	1	1
469
+1455831309	439	66	14	0	10	1	1
470
+1455831437	439	67	14	0	10	1	1
471
+1455894228	439	67	15	0	10	1	1
472
+1455897059	439	67	16	0	10	1	1
473
+1456110772	439	68	16	0	10	1	1
474
+1456266997	439	68	17	0	10	1	1
475
+1456267705	439	68	18	0	10	1	1
476
+1456269047	439	69	18	0	10	1	1
477
+1456506668	439	69	19	0	10	1	1
478
+1456806554	439	70	19	0	10	1	1
479
+1457141043	439	71	19	0	10	1	1
480
+1457452520	439	71	20	0	10	1	1
481
+1457488422	439	71	21	0	10	1	1
482
+1457563999	439	71	22	0	10	1	1
483
+1457644085	439	71	23	0	10	1	1
484
+1457644884	439	71	24	0	10	1	1
485
+1457646092	439	71	25	0	10	1	1
486
+1457987370	439	72	25	0	10	1	1
487
+1457991490	439	72	26	0	10	1	1
488
+1458004818	439	73	26	0	10	1	1
489
+1458593314	439	74	26	0	10	1	1
490
+1458765547	439	75	26	0	10	1	1
491
+1458914088	439	76	26	0	10	1	1
492
+1458991612	439	77	26	0	10	1	1
493
+1459133960	439	78	26	0	10	1	1
494
+1460047216	439	79	26	0	10	1	1
495
+1460047380	439	81	27	0	10	1	1
496
+1460375644	439	83	28	0	10	1	1
497
+1460380408	439	85	30	0	10	1	1
498
+1460407428	439	86	30	0	10	1	1
499
+1460569764	439	86	31	0	10	1	1
500
+1461074070	439	87	31	0	10	1	1
501
+1461270656	439	88	32	0	10	1	1
502
+1461354039	439	88	33	0	10	1	1
503
+1461551595	439	89	33	0	10	1	1
504
+1461552637	439	90	33	0	10	1	1
505
+1462227334	439	91	33	0	10	1	1
506
+1463152815	439	91	34	0	10	1	1
507
+1463157617	439	91	35	0	10	1	1
508
+1463451445	439	91	36	0	10	1	1
509
+1463451804	439	91	37	0	10	1	1
510
+1463680867	439	92	37	0	10	1	1
511
+1463758054	439	93	37	0	10	1	1
512
+1464031184	439	94	37	0	10	1	1
513
+1464096601	439	95	37	0	10	1	1
514
+1464208709	439	96	37	0	10	1	1
515
+1464237088	439	97	37	0	10	1	1
516
+1464237194	439	98	37	0	10	1	1
517
+1464293889	439	99	37	0	10	1	1
518
+1464793340	439	100	38	0	10	1	1
519
+1464880180	439	101	38	0	10	1	1
520
+1464895801	439	102	38	0	10	1	1
521
+1464902654	439	103	38	0	10	1	1
522
+1465319220	439	104	39	0	10	1	1
523
+1465424498	439	105	39	0	10	1	1
524
+1466648922	439	106	39	0	10	1	1
525
+1466649684	439	107	39	0	10	1	1
526
+1466650102	439	108	39	0	10	1	1
527
+1466701155	439	109	39	0	10	1	1
528
+1466735347	439	110	39	0	10	1	1
529
+1466736165	439	111	40	0	10	1	1
530
+1468180054	439	114	41	0	10	1	1
531
+1468292316	439	114	42	0	10	1	1
532
+1468876762	439	114	43	0	10	1	1
533
+1468876920	439	114	44	0	10	1	1
534
+1469121939	439	114	45	0	10	1	1
535
+1469157573	439	115	45	0	10	1	1
536
+1469552934	439	115	46	0	10	1	1
537
+1469589171	439	116	47	0	10	1	1
538
+1469640240	439	117	47	0	10	1	1
539
+1469723242	439	117	48	0	10	1	1
540
+1469725928	439	118	48	0	10	1	1
541
+1469780404	439	119	48	0	10	1	1
542
+1475506438	439	120	48	0	10	1	1
543
+1475508328	439	122	48	0	10	1	1
544
+1475605531	439	124	48	0	10	1	1
545
+1475759454	439	125	48	0	10	1	1
546
+1476301940	439	125	49	0	10	1	1
547
+1477421556	439	126	50	0	10	1	1
548
+1477427877	439	127	52	0	10	1	1
549
+1477433230	439	127	53	0	10	1	1
550
+1477694191	439	127	54	0	10	1	1
551
+1477939269	439	127	55	0	10	1	1
552
+1477941012	439	127	56	0	10	1	1
553
+1478115005	439	127	57	0	10	1	1
554
+1478288605	439	127	58	0	10	1	1
555
+1478551645	439	127	59	0	10	1	1
556
+1478792001	439	127	60	0	10	1	1
557
+1478814382	439	127	61	0	10	1	1
558
+1478816505	439	127	62	0	10	1	1
559
+1478878515	439	127	63	0	10	1	1
560
+1479419832	439	127	64	0	10	1	1
561
+1479846967	439	127	65	0	10	1	1
562
+1480530088	439	127	66	0	10	1	1
563
+1481670500	439	127	67	0	10	1	1
564
+1482181507	439	127	68	1	10	1	1
565
+1482336682	439	127	69	1	10	1	1
566
+1482340119	439	127	70	1	10	1	1
567
+1482353511	439	127	71	2	10	1	1
568
+1482359008	439	127	71	3	10	1	1
569
+1483455272	439	127	72	4	10	1	1
570
+1484342644	439	127	72	5	10	1	1
571
+1484684949	439	127	72	7	10	1	1
572
+1484772851	439	127	73	8	10	1	1
573
+1485799035	439	127	75	9	10	1	1
574
+1485877540	439	127	76	9	10	1	1
575
+1485877851	439	127	77	9	10	1	1
576
+1485877920	439	127	78	9	10	1	1
577
+1485878121	439	127	79	9	10	1	1
578
+1486146025	439	127	80	9	10	1	1
579
+1486412376	439	127	81	9	10	1	1
580
+1486499625	439	127	82	9	10	1	1
581
+1486755546	439	127	83	9	10	1	1
582
+1487109340	439	127	84	9	10	1	1
583
+1487277883	439	127	84	10	10	1	1
584
+1487783266	439	127	84	13	10	1	1
585
+1487946215	439	127	86	14	10	1	1
586
+1487965862	439	127	86	15	10	1	1
587
+1487966474	439	127	86	16	10	1	1
588
+1487966964	439	127	86	17	10	1	1
589
+1488313813	439	127	86	18	10	1	1
590
+1488406636	439	127	87	18	10	1	1
591
+1488407109	439	127	88	18	10	1	1
592
+1488409193	439	127	89	18	10	1	1
593
+1488560599	439	127	90	18	10	1	1
594
+1488773682	439	127	90	19	10	1	1
595
+1488814084	439	127	91	19	10	1	1
596
+1488815988	439	127	92	19	10	1	1
597
+1489686589	439	127	93	19	10	1	1
598
+1490023066	439	127	95	19	10	1	1
599
+1490035644	439	127	96	19	10	1	1
600
+1490041875	439	127	97	19	10	1	1
601
+1490113585	439	127	98	19	10	1	1
602
+1490120437	439	127	99	19	10	1	1
603
+1490128257	439	127	100	19	10	1	1
604
+1490645760	439	127	101	19	10	1	1
605
+1490725467	439	127	102	19	10	1	1
606
+1491336954	439	127	103	19	10	1	1
607
+1491341896	439	127	104	19	10	1	1
608
+1492026512	439	127	105	19	10	1	1
609
+1492107919	439	127	106	19	10	1	1
610
+1492108901	439	127	107	19	10	1	1
611
+1492435237	439	127	108	19	10	1	1
612
+1492435982	439	127	109	19	10	1	1
613
+1492454272	439	127	110	19	10	1	1
614
+1493052262	439	127	110	20	10	1	1
615
+1493153070	439	127	111	20	10	1	1
616
+1493407521	439	127	112	20	10	1	1
617
+1493412286	439	127	113	20	10	1	1

+ 169
- 0
FlaskTest/static/data/test_repo/data2_dummy.tsv Целия файл

@@ -0,0 +1,169 @@
1
+day	hour	value
2
+1	1	88
3
+1	2	20
4
+1	3	44
5
+1	4	0
6
+1	5	33
7
+1	6	2
8
+1	7	0
9
+1	8	9
10
+1	9	25
11
+1	10	1
12
+1	11	57
13
+1	12	61
14
+1	13	22
15
+1	14	25
16
+1	15	7
17
+1	16	55
18
+1	17	51
19
+1	18	4
20
+1	19	17
21
+1	20	20
22
+1	21	4
23
+1	22	4
24
+1	23	6
25
+1	24	12
26
+2	1	6
27
+2	2	22
28
+2	3	0
29
+2	4	0
30
+2	5	33
31
+2	6	5
32
+2	7	4
33
+2	8	8
34
+2	9	28
35
+2	10	99
36
+2	11	51
37
+2	12	66
38
+2	13	38
39
+2	14	39
40
+2	15	60
41
+2	16	22
42
+2	17	65
43
+2	18	50
44
+2	19	22
45
+2	20	11
46
+2	21	12
47
+2	22	9
48
+2	23	33
49
+2	24	13
50
+3	1	5
51
+3	2	8
52
+3	3	44
53
+3	4	0
54
+3	5	12
55
+3	6	2
56
+3	7	5
57
+3	8	12
58
+3	9	34
59
+3	10	43
60
+3	11	54
61
+3	12	44
62
+3	13	40
63
+3	14	48
64
+3	15	54
65
+3	16	59
66
+3	17	60
67
+3	18	51
68
+3	19	21
69
+3	20	16
70
+3	21	9
71
+3	22	5
72
+3	23	24
73
+3	24	7
74
+4	1	22
75
+4	2	33
76
+4	3	102
77
+4	4	33
78
+4	5	0
79
+4	6	2
80
+4	7	4
81
+4	8	13
82
+4	9	26
83
+4	10	58
84
+4	11	61
85
+4	12	59
86
+4	13	53
87
+4	14	54
88
+4	15	64
89
+4	16	55
90
+4	17	52
91
+4	18	53
92
+4	19	18
93
+4	20	3
94
+4	21	9
95
+4	22	12
96
+4	23	2
97
+4	24	8
98
+5	1	2
99
+5	2	0
100
+5	3	8
101
+5	4	2
102
+5	5	0
103
+5	6	2
104
+5	7	4
105
+5	8	14
106
+5	9	31
107
+5	10	48
108
+5	11	46
109
+5	12	50
110
+5	13	66
111
+5	14	54
112
+5	15	56
113
+5	16	67
114
+5	17	54
115
+5	18	23
116
+5	19	14
117
+5	20	6
118
+5	21	8
119
+5	22	7
120
+5	23	0
121
+5	24	84
122
+6	1	11
123
+6	2	22
124
+6	3	33
125
+6	4	44
126
+6	5	4
127
+6	6	0
128
+6	7	4
129
+6	8	8
130
+6	9	8
131
+6	10	6
132
+6	11	14
133
+6	12	12
134
+6	13	9
135
+6	14	14
136
+6	15	0
137
+6	16	4
138
+6	17	7
139
+6	18	6
140
+6	19	42
141
+6	20	5
142
+6	21	77
143
+6	22	32
144
+6	23	41
145
+6	24	0
146
+7	1	7
147
+7	2	6
148
+7	3	11
149
+7	4	22
150
+7	5	33
151
+7	6	44
152
+7	7	66
153
+7	8	5
154
+7	9	33
155
+7	10	0
156
+7	11	2
157
+7	12	2
158
+7	13	44
159
+7	14	6
160
+7	15	0
161
+7	16	4
162
+7	17	33
163
+7	18	2
164
+7	19	10
165
+7	20	7
166
+7	21	0
167
+7	22	19
168
+7	23	9
169
+7	24	4

+ 169
- 0
FlaskTest/static/data/test_repo/data_dummy.tsv Целия файл

@@ -0,0 +1,169 @@
1
+day	hour	value
2
+1	1	16
3
+1	2	20
4
+1	3	0
5
+1	4	0
6
+1	5	0
7
+1	6	2
8
+1	7	0
9
+1	8	9
10
+1	9	25
11
+1	10	49
12
+1	11	57
13
+1	12	61
14
+1	13	37
15
+1	14	66
16
+1	15	70
17
+1	16	55
18
+1	17	51
19
+1	18	55
20
+1	19	17
21
+1	20	20
22
+1	21	9
23
+1	22	4
24
+1	23	0
25
+1	24	12
26
+2	1	6
27
+2	2	2
28
+2	3	0
29
+2	4	0
30
+2	5	0
31
+2	6	2
32
+2	7	4
33
+2	8	11
34
+2	9	28
35
+2	10	49
36
+2	11	51
37
+2	12	47
38
+2	13	38
39
+2	14	65
40
+2	15	60
41
+2	16	50
42
+2	17	65
43
+2	18	50
44
+2	19	22
45
+2	20	11
46
+2	21	12
47
+2	22	9
48
+2	23	0
49
+2	24	13
50
+3	1	5
51
+3	2	8
52
+3	3	8
53
+3	4	0
54
+3	5	0
55
+3	6	2
56
+3	7	5
57
+3	8	12
58
+3	9	34
59
+3	10	43
60
+3	11	54
61
+3	12	44
62
+3	13	40
63
+3	14	48
64
+3	15	54
65
+3	16	59
66
+3	17	60
67
+3	18	51
68
+3	19	21
69
+3	20	16
70
+3	21	9
71
+3	22	5
72
+3	23	4
73
+3	24	7
74
+4	1	0
75
+4	2	0
76
+4	3	0
77
+4	4	0
78
+4	5	0
79
+4	6	2
80
+4	7	4
81
+4	8	13
82
+4	9	26
83
+4	10	58
84
+4	11	61
85
+4	12	59
86
+4	13	53
87
+4	14	54
88
+4	15	64
89
+4	16	55
90
+4	17	52
91
+4	18	53
92
+4	19	18
93
+4	20	3
94
+4	21	9
95
+4	22	12
96
+4	23	2
97
+4	24	8
98
+5	1	2
99
+5	2	0
100
+5	3	8
101
+5	4	2
102
+5	5	0
103
+5	6	2
104
+5	7	4
105
+5	8	14
106
+5	9	31
107
+5	10	48
108
+5	11	46
109
+5	12	50
110
+5	13	66
111
+5	14	54
112
+5	15	56
113
+5	16	67
114
+5	17	54
115
+5	18	23
116
+5	19	14
117
+5	20	6
118
+5	21	8
119
+5	22	7
120
+5	23	0
121
+5	24	8
122
+6	1	2
123
+6	2	0
124
+6	3	2
125
+6	4	0
126
+6	5	0
127
+6	6	0
128
+6	7	4
129
+6	8	8
130
+6	9	8
131
+6	10	6
132
+6	11	14
133
+6	12	12
134
+6	13	9
135
+6	14	14
136
+6	15	0
137
+6	16	4
138
+6	17	7
139
+6	18	6
140
+6	19	0
141
+6	20	0
142
+6	21	0
143
+6	22	0
144
+6	23	0
145
+6	24	0
146
+7	1	7
147
+7	2	6
148
+7	3	0
149
+7	4	0
150
+7	5	0
151
+7	6	0
152
+7	7	0
153
+7	8	0
154
+7	9	0
155
+7	10	0
156
+7	11	2
157
+7	12	2
158
+7	13	5
159
+7	14	6
160
+7	15	0
161
+7	16	4
162
+7	17	0
163
+7	18	2
164
+7	19	10
165
+7	20	7
166
+7	21	0
167
+7	22	19
168
+7	23	9
169
+7	24	4

+ 8
- 0
FlaskTest/static/data/test_repo/day_of_week_copy.tsv Целия файл

@@ -0,0 +1,8 @@
1
+day_number	day_name	commits
2
+1	Mon	148
3
+2	Tue	176
4
+3	Wed	120
5
+4	Thu	153
6
+5	Fri	101
7
+6	Sat	1
8
+7	Sun	12

+ 617
- 0
FlaskTest/static/data/test_repo/lines_of_code_by_author_copy.tsv Целия файл

@@ -0,0 +1,617 @@
1
+date	author1	author2	author3	author4	author5	author6	author7
2
+1392392111	27013	0	0	0	0	0	0
3
+1392397527	27021	0	0	0	0	0	0
4
+1392830779	27024	0	0	0	0	0	0
5
+1392841719	27573	0	0	0	0	0	0
6
+1392844458	27597	0	0	0	0	0	0
7
+1392852040	27702	0	0	0	0	0	0
8
+1392933375	27702	0	0	0	0	0	0
9
+1393016220	27706	0	0	0	0	0	0
10
+1393018032	27706	0	0	0	0	0	0
11
+1393272179	27723	0	0	0	0	0	0
12
+1393356877	27861	0	0	0	0	0	0
13
+1393357414	27864	0	0	0	0	0	0
14
+1393437911	27903	0	0	0	0	0	0
15
+1393442422	28012	0	0	0	0	0	0
16
+1393515175	28021	0	0	0	0	0	0
17
+1393531146	28032	0	0	0	0	0	0
18
+1393532869	28039	0	0	0	0	0	0
19
+1393534919	28087	0	0	0	0	0	0
20
+1393540722	28089	0	0	0	0	0	0
21
+1393861723	28100	0	0	0	0	0	0
22
+1394035569	28126	0	0	0	0	0	0
23
+1394039661	28188	0	0	0	0	0	0
24
+1394054732	28301	0	0	0	0	0	0
25
+1394055920	28303	0	0	0	0	0	0
26
+1394056588	28307	0	0	0	0	0	0
27
+1394119672	28320	0	0	0	0	0	0
28
+1394125181	28328	0	0	0	0	0	0
29
+1394145913	33119	0	0	0	0	0	0
30
+1394460626	33126	0	0	0	0	0	0
31
+1394465228	33150	0	0	0	0	0	0
32
+1394467610	33176	0	0	0	0	0	0
33
+1394470538	33176	0	0	0	0	0	0
34
+1394478517	33239	0	0	0	0	0	0
35
+1394487470	33251	0	0	0	0	0	0
36
+1394546208	33287	0	0	0	0	0	0
37
+1394549933	33300	0	0	0	0	0	0
38
+1394573638	33509	0	0	0	0	0	0
39
+1394730184	33513	0	0	0	0	0	0
40
+1394732727	33853	0	0	0	0	0	0
41
+1395071699	33905	0	0	0	0	0	0
42
+1395080218	33955	0	0	0	0	0	0
43
+1395084092	37195	0	0	0	0	0	0
44
+1395243935	37258	0	0	0	0	0	0
45
+1395261876	37417	0	0	0	0	0	0
46
+1395324514	37422	0	0	0	0	0	0
47
+1395347240	37423	0	0	0	0	0	0
48
+1395418555	37453	0	0	0	0	0	0
49
+1395675797	37472	0	0	0	0	0	0
50
+1395682889	37517	0	0	0	0	0	0
51
+1395687258	37709	0	0	0	0	0	0
52
+1395687758	37710	0	0	0	0	0	0
53
+1395695371	37779	0	0	0	0	0	0
54
+1395759924	37795	0	0	0	0	0	0
55
+1395781761	37854	0	0	0	0	0	0
56
+1395931149	37969	0	0	0	0	0	0
57
+1395955619	38207	0	0	0	0	0	0
58
+1396017083	38220	0	0	0	0	0	0
59
+1396018452	38220	0	0	0	0	0	0
60
+1396033193	38685	0	0	0	0	0	0
61
+1396034898	38685	0	0	0	0	0	0
62
+1396036712	38779	0	0	0	0	0	0
63
+1396293025	38828	0	0	0	0	0	0
64
+1396379166	38831	0	0	0	0	0	0
65
+1396379379	38852	0	0	0	0	0	0
66
+1396469103	40876	0	0	0	0	0	0
67
+1396469896	40878	0	0	0	0	0	0
68
+1396472129	40897	0	0	0	0	0	0
69
+1396474065	41011	0	0	0	0	0	0
70
+1396541016	41018	0	0	0	0	0	0
71
+1396638448	41021	0	0	0	0	0	0
72
+1396639995	41023	0	0	0	0	0	0
73
+1396885855	41027	0	0	0	0	0	0
74
+1396899853	41086	0	0	0	0	0	0
75
+1396984335	41355	0	0	0	0	0	0
76
+1397247297	41409	0	0	0	0	0	0
77
+1397248748	41410	0	0	0	0	0	0
78
+1397484607	41417	0	0	0	0	0	0
79
+1397491609	41573	0	0	0	0	0	0
80
+1397501654	41592	0	0	0	0	0	0
81
+1397502552	41660	0	0	0	0	0	0
82
+1397508171	41724	0	0	0	0	0	0
83
+1397593977	41896	0	0	0	0	0	0
84
+1397596834	41896	0	0	0	0	0	0
85
+1397681905	41970	0	0	0	0	0	0
86
+1397747221	42529	0	0	0	0	0	0
87
+1397751241	42589	0	0	0	0	0	0
88
+1397754140	42612	0	0	0	0	0	0
89
+1398106573	42712	0	0	0	0	0	0
90
+1398190593	42714	0	0	0	0	0	0
91
+1398265425	42715	0	0	0	0	0	0
92
+1398265460	42922	0	0	0	0	0	0
93
+1398268479	42922	0	0	0	0	0	0
94
+1398271735	42978	0	0	0	0	0	0
95
+1398278671	42981	0	0	0	0	0	0
96
+1398282984	42983	0	0	0	0	0	0
97
+1398373748	42984	0	0	0	0	0	0
98
+1398434861	42998	0	0	0	0	0	0
99
+1398437720	43001	0	0	0	0	0	0
100
+1398697019	43001	0	0	0	0	0	0
101
+1398699790	43005	0	0	0	0	0	0
102
+1398715690	43016	0	0	0	0	0	0
103
+1398716189	43088	0	0	0	0	0	0
104
+1398718090	43089	0	0	0	0	0	0
105
+1398797186	43172	0	0	0	0	0	0
106
+1398799360	43172	0	0	0	0	0	0
107
+1398800327	43395	0	0	0	0	0	0
108
+1399305448	43420	0	0	0	0	0	0
109
+1399315892	43422	0	0	0	0	0	0
110
+1399315910	43422	0	0	0	0	0	0
111
+1399316102	43423	0	0	0	0	0	0
112
+1399387116	43505	0	0	0	0	0	0
113
+1399388283	43506	0	0	0	0	0	0
114
+1399389375	43510	0	0	0	0	0	0
115
+1399564137	43513	0	0	0	0	0	0
116
+1399666061	43517	0	0	0	0	0	0
117
+1399905905	43973	0	0	0	0	0	0
118
+1399991114	43975	0	0	0	0	0	0
119
+1400189261	44019	0	0	0	0	0	0
120
+1400529215	77954	0	0	0	0	0	0
121
+1401293283	77959	0	0	0	0	0	0
122
+1401296335	77960	0	0	0	0	0	0
123
+1401386785	78042	0	0	0	0	0	0
124
+1402341065	78044	0	0	0	0	0	0
125
+1402342238	78045	0	0	0	0	0	0
126
+1402428124	78049	0	0	0	0	0	0
127
+1402431333	78084	0	0	0	0	0	0
128
+1402511788	78089	0	0	0	0	0	0
129
+1402512041	78163	0	0	0	0	0	0
130
+1402595862	78163	0	0	0	0	0	0
131
+1403192924	78164	0	0	0	0	0	0
132
+1403206094	78187	0	0	0	0	0	0
133
+1403280442	78193	0	0	0	0	0	0
134
+1403535284	78194	0	0	0	0	0	0
135
+1403541811	78195	0	0	0	0	0	0
136
+1403636781	78915	0	0	0	0	0	0
137
+1403637528	78942	0	0	0	0	0	0
138
+1403724192	78981	0	0	0	0	0	0
139
+1403732386	78985	0	0	0	0	0	0
140
+1404142813	79122	0	0	0	0	0	0
141
+1404248505	79126	0	0	0	0	0	0
142
+1404915912	79235	0	0	0	0	0	0
143
+1404923241	79236	0	0	0	0	0	0
144
+1404923409	79295	0	0	0	0	0	0
145
+1405002584	79296	0	0	0	0	0	0
146
+1405003854	79310	0	0	0	0	0	0
147
+1405021172	79321	0	0	0	0	0	0
148
+1405023400	79339	0	0	0	0	0	0
149
+1405113290	79348	0	0	0	0	0	0
150
+1405348976	79350	0	0	0	0	0	0
151
+1405361989	79422	0	0	0	0	0	0
152
+1405364123	79437	0	0	0	0	0	0
153
+1405447703	79441	0	0	0	0	0	0
154
+1405540116	79443	0	0	0	0	0	0
155
+1406040988	79533	0	0	0	0	0	0
156
+1406041836	79540	0	0	0	0	0	0
157
+1406052691	79555	0	0	0	0	0	0
158
+1406055617	79580	0	0	0	0	0	0
159
+1406055678	79581	0	0	0	0	0	0
160
+1406225786	79677	0	0	0	0	0	0
161
+1406231654	79678	0	0	0	0	0	0
162
+1406235975	85571	0	0	0	0	0	0
163
+1406297884	85594	0	0	0	0	0	0
164
+1406312943	85746	0	0	0	0	0	0
165
+1406325886	85752	0	0	0	0	0	0
166
+1406563924	85761	0	0	0	0	0	0
167
+1406564839	85762	0	0	0	0	0	0
168
+1406670247	85884	0	0	0	0	0	0
169
+1406734781	85901	0	0	0	0	0	0
170
+1406754307	86541	0	0	0	0	0	0
171
+1406834212	86552	0	0	0	0	0	0
172
+1407185931	86553	0	0	0	0	0	0
173
+1407255136	86619	0	0	0	0	0	0
174
+1407268137	86620	0	0	0	0	0	0
175
+1408040736	86622	0	0	0	0	0	0
176
+1408138667	86622	0	0	0	0	0	0
177
+1408481183	86623	0	0	0	0	0	0
178
+1408976252	86624	0	0	0	0	0	0
179
+1408976366	86669	0	0	0	0	0	0
180
+1408976476	86669	0	0	0	0	0	0
181
+1408983121	86683	0	0	0	0	0	0
182
+1409078217	86713	0	0	0	0	0	0
183
+1409244667	86713	0	0	0	0	0	0
184
+1409253116	86978	0	0	0	0	0	0
185
+1409259944	87041	0	0	0	0	0	0
186
+1409672806	87123	0	0	0	0	0	0
187
+1410206464	87129	0	0	0	0	0	0
188
+1410207080	87130	0	0	0	0	0	0
189
+1410287098	87132	0	0	0	0	0	0
190
+1410551535	87149	0	0	0	0	0	0
191
+1411486906	87153	0	0	0	0	0	0
192
+1411658801	88057	0	0	0	0	0	0
193
+1412085855	88077	0	0	0	0	0	0
194
+1412093603	88189	0	0	0	0	0	0
195
+1412103242	88196	0	0	0	0	0	0
196
+1412103800	88198	0	0	0	0	0	0
197
+1412106592	88200	0	0	0	0	0	0
198
+1412110451	88218	0	0	0	0	0	0
199
+1412110505	88230	0	0	0	0	0	0
200
+1412110521	88331	0	0	0	0	0	0
201
+1412171582	88332	0	0	0	0	0	0
202
+1412178008	88342	0	0	0	0	0	0
203
+1412182838	88428	0	0	0	0	0	0
204
+1412265983	88431	0	0	0	0	0	0
205
+1412275768	88435	0	0	0	0	0	0
206
+1412369691	88439	0	0	0	0	0	0
207
+1412625476	88621	0	0	0	0	0	0
208
+1412715931	88622	0	0	0	0	0	0
209
+1413222749	88885	0	0	0	0	0	0
210
+1414522504	91054	0	0	0	0	0	0
211
+1415144579	91782	0	0	0	0	0	0
212
+1415218924	91785	0	0	0	0	0	0
213
+1415219022	91785	0	0	0	0	0	0
214
+1415220994	91867	0	0	0	0	0	0
215
+1415319788	91879	0	0	0	0	0	0
216
+1415319862	91881	0	0	0	0	0	0
217
+1415321087	91883	0	0	0	0	0	0
218
+1415395231	91883	0	0	0	0	0	0
219
+1415649515	91953	0	0	0	0	0	0
220
+1415652685	91974	0	0	0	0	0	0
221
+1415717488	91977	0	0	0	0	0	0
222
+1415724534	91982	0	0	0	0	0	0
223
+1415726692	92021	0	0	0	0	0	0
224
+1415728253	92036	0	0	0	0	0	0
225
+1415734870	92058	0	0	0	0	0	0
226
+1415736053	92059	0	0	0	0	0	0
227
+1415738826	92066	0	0	0	0	0	0
228
+1415742817	92346	0	0	0	0	0	0
229
+1415745888	92507	0	0	0	0	0	0
230
+1415823605	92559	0	0	0	0	0	0
231
+1415832007	92591	0	0	0	0	0	0
232
+1416239521	92592	0	0	0	0	0	0
233
+1416245778	92594	0	0	0	0	0	0
234
+1416350686	92638	0	0	0	0	0	0
235
+1416612373	92865	0	0	0	0	0	0
236
+1417464052	92875	0	0	0	0	0	0
237
+1417464390	92878	0	0	0	0	0	0
238
+1417534843	92952	0	0	0	0	0	0
239
+1417549520	92979	0	0	0	0	0	0
240
+1417553492	92989	0	0	0	0	0	0
241
+1417564577	93069	0	0	0	0	0	0
242
+1417640760	93089	0	0	0	0	0	0
243
+1418053512	93092	0	0	0	0	0	0
244
+1418318204	93116	0	0	0	0	0	0
245
+1418318222	93134	0	0	0	0	0	0
246
+1418318240	93138	0	0	0	0	0	0
247
+1418318297	93173	0	0	0	0	0	0
248
+1418318361	93174	0	0	0	0	0	0
249
+1418318417	93217	0	0	0	0	0	0
250
+1418318440	93218	0	0	0	0	0	0
251
+1418318684	93221	0	0	0	0	0	0
252
+1418421690	93224	0	0	0	0	0	0
253
+1418421802	93264	0	0	0	0	0	0
254
+1418746234	93299	0	0	0	0	0	0
255
+1418746500	93377	0	0	0	0	0	0
256
+1418752434	93395	0	0	0	0	0	0
257
+1418755439	93488	0	0	0	0	0	0
258
+1418849653	93497	0	0	0	0	0	0
259
+1418918217	93521	0	0	0	0	0	0
260
+1418940694	93661	0	0	0	0	0	0
261
+1418941834	93685	0	0	0	0	0	0
262
+1419356537	99684	0	0	0	0	0	0
263
+1421161999	99703	0	0	0	0	0	0
264
+1421164187	99713	0	0	0	0	0	0
265
+1421170472	99724	0	0	0	0	0	0
266
+1421182429	99726	0	0	0	0	0	0
267
+1421186075	99727	0	0	0	0	0	0
268
+1421333600	99732	0	0	0	0	0	0
269
+1421362394	99757	0	0	0	0	0	0
270
+1421781546	99780	0	0	0	0	0	0
271
+1421794398	99788	0	0	0	0	0	0
272
+1421858140	99792	0	0	0	0	0	0
273
+1421861618	99843	0	0	0	0	0	0
274
+1421865987	99884	0	0	0	0	0	0
275
+1421870518	99887	0	0	0	0	0	0
276
+1421942808	99893	0	0	0	0	0	0
277
+1421965255	99939	0	0	0	0	0	0
278
+1421965286	100061	0	0	0	0	0	0
279
+1421966627	100064	0	0	0	0	0	0
280
+1422288635	100064	0	0	0	0	0	0
281
+1422288804	100071	0	0	0	0	0	0
282
+1422289212	100071	0	0	0	0	0	0
283
+1422289952	100087	0	0	0	0	0	0
284
+1422290748	100091	0	0	0	0	0	0
285
+1422291553	100096	0	0	0	0	0	0
286
+1422310926	100177	0	0	0	0	0	0
287
+1422372141	100178	0	0	0	0	0	0
288
+1422375118	100181	0	0	0	0	0	0
289
+1422384120	100195	0	0	0	0	0	0
290
+1422386950	100246	0	0	0	0	0	0
291
+1422387963	100261	0	0	0	0	0	0
292
+1422463984	100410	0	0	0	0	0	0
293
+1422478361	100413	0	0	0	0	0	0
294
+1422481852	100762	0	0	0	0	0	0
295
+1422544534	100762	0	0	0	0	0	0
296
+1422565449	100764	0	0	0	0	0	0
297
+1422569286	100766	0	0	0	0	0	0
298
+1422569372	100846	0	0	0	0	0	0
299
+1422861179	100846	0	0	0	5	0	0
300
+1422894999	100854	0	0	0	5	0	0
301
+1422989658	100870	0	0	0	5	0	0
302
+1423167301	100920	0	0	0	5	0	0
303
+1423585125	100920	0	0	0	5	0	0
304
+1423590234	100922	0	0	0	5	0	0
305
+1423596075	100928	0	0	0	5	0	0
306
+1423603626	101018	0	0	0	5	0	0
307
+1423672020	101019	0	0	0	5	0	0
308
+1423753966	101019	0	0	0	5	0	0
309
+1424114643	101028	0	0	0	5	0	0
310
+1424188717	101042	0	0	0	5	0	0
311
+1424193093	101101	0	0	0	5	0	0
312
+1424201771	101142	0	0	0	5	0	0
313
+1424273633	101194	0	0	0	5	0	0
314
+1424279063	101242	0	0	0	5	0	0
315
+1424280071	101326	0	0	0	5	0	0
316
+1424281679	101357	0	0	0	5	0	0
317
+1424288424	101438	0	0	0	5	0	0
318
+1424288546	101520	0	0	0	5	0	0
319
+1424295016	101553	0	0	0	5	0	0
320
+1424465722	101565	0	0	0	5	0	0
321
+1424466815	101573	0	0	0	5	0	0
322
+1424467415	101662	0	0	0	5	0	0
323
+1424712442	101669	0	0	0	5	0	0
324
+1424716942	101730	0	0	0	5	0	0
325
+1424721680	101751	0	0	0	5	0	0
326
+1424791974	101756	0	0	0	5	0	0
327
+1424796525	101766	0	0	0	5	0	0
328
+1424798803	101771	0	0	0	5	0	0
329
+1424798945	101795	0	0	0	5	0	0
330
+1424803751	101915	0	0	0	5	0	0
331
+1424810387	101926	0	0	0	5	0	0
332
+1424810657	101941	0	0	0	5	0	0
333
+1424816551	102008	0	0	0	5	0	0
334
+1424818066	102010	0	0	0	5	0	0
335
+1424878655	102148	0	0	0	5	0	0
336
+1424884467	102148	0	0	0	5	0	0
337
+1424884849	102657	0	0	0	5	0	0
338
+1424892815	102721	0	0	0	5	0	0
339
+1424898072	102726	0	0	0	5	0	0
340
+1425051573	102736	0	0	0	5	0	0
341
+1425067581	102789	0	0	0	5	0	0
342
+1425067690	102794	0	0	0	5	0	0
343
+1425077014	102794	0	0	0	5	0	0
344
+1425309326	102794	0	0	0	5	0	0
345
+1425313422	102795	0	0	0	5	0	0
346
+1425329531	102795	0	0	0	5	0	0
347
+1425337212	102869	0	0	0	5	0	0
348
+1425342674	102877	0	0	0	5	0	0
349
+1425914337	102896	0	0	0	5	0	0
350
+1425917461	102914	0	0	0	5	0	0
351
+1425999980	102915	0	0	0	5	0	0
352
+1426267819	102918	0	0	0	5	0	0
353
+1426273408	102938	0	0	0	5	0	0
354
+1426280545	102946	0	0	0	5	0	0
355
+1426628250	102956	0	0	0	5	0	0
356
+1426778183	102958	0	0	0	5	0	0
357
+1426791438	102978	0	0	0	5	0	0
358
+1426803375	102985	0	0	0	5	0	0
359
+1426804222	102989	0	0	0	5	0	0
360
+1426890119	103061	0	0	0	5	0	0
361
+1427129667	103071	0	0	0	5	0	0
362
+1427133177	103105	0	0	0	5	0	0
363
+1427135813	103107	0	0	0	5	0	0
364
+1427137830	103185	0	0	0	5	0	0
365
+1427139221	103186	0	0	0	5	0	0
366
+1427141290	103201	0	0	0	5	0	0
367
+1427141834	103203	0	0	0	5	0	0
368
+1427206811	103207	0	0	0	5	0	0
369
+1427225995	103211	0	0	0	5	0	0
370
+1427226516	103213	0	0	0	5	0	0
371
+1427228366	131222	0	0	0	5	0	0
372
+1427267795	131222	0	0	0	74	0	0
373
+1427267837	131222	0	0	0	77	0	0
374
+1427294459	131228	0	0	0	77	0	0
375
+1427317966	131235	0	0	0	77	0	0
376
+1427335799	131235	0	0	0	77	0	0
377
+1427349002	131235	0	0	0	77	0	0
378
+1427349639	131235	0	0	0	79	0	0
379
+1427362185	131235	0	0	0	157	0	0
380
+1427362305	131235	0	0	0	161	0	0
381
+1427426891	131327	0	0	0	167	0	0
382
+1427488267	131335	0	0	0	167	0	0
383
+1427789357	131335	0	0	0	169	0	0
384
+1427789410	131337	0	0	0	195	0	0
385
+1427833035	131337	0	0	0	195	0	0
386
+1427917146	131337	0	0	0	195	0	0
387
+1427997681	131342	0	0	0	195	0	0
388
+1428443057	131359	0	0	0	195	0	0
389
+1428513842	131365	0	0	0	195	0	0
390
+1428521668	131382	0	0	0	195	0	0
391
+1428934715	131387	0	0	0	195	0	0
392
+1429112004	131391	0	0	0	195	0	0
393
+1429284766	235670	0	0	0	195	0	0
394
+1429304949	235700	0	0	0	195	0	0
395
+1429310422	235708	0	0	0	195	0	0
396
+1430403827	235710	0	0	0	195	0	0
397
+1431109116	235710	0	0	0	195	0	0
398
+1431110062	235718	0	0	0	195	0	0
399
+1431398393	235718	0	0	0	195	0	0
400
+1432310614	235718	23	0	0	195	0	0
401
+1432310966	235718	23	0	0	195	0	0
402
+1432311075	235718	23	0	0	195	0	0
403
+1432311088	235718	53	0	0	195	0	0
404
+1432311100	235718	53	0	0	195	0	0
405
+1432312050	235718	67	0	0	195	0	0
406
+1432588799	235718	67	1	0	195	0	0
407
+1432588896	235718	67	15	0	195	0	0
408
+1432588954	235718	67	17	0	195	0	0
409
+1432831392	235718	75	17	0	195	0	0
410
+1432831427	235718	75	17	0	195	0	0
411
+1432907636	235718	75	17	0	195	0	0
412
+1432929105	235718	122	17	0	195	0	0
413
+1433175771	235718	151	17	0	195	0	0
414
+1433464018	235718	151	75	0	195	0	0
415
+1433464310	235718	151	90	0	195	0	0
416
+1434132785	235718	151	90	0	195	0	0
417
+1434134581	235718	151	90	0	195	0	0
418
+1434138920	235718	157	90	0	195	0	0
419
+1434138982	235718	157	90	0	195	0	0
420
+1434139189	235718	158	90	0	195	0	0
421
+1434376340	235718	180	90	0	195	0	0
422
+1435836687	235718	180	90	0	195	0	0
423
+1435843824	235718	194	90	0	195	0	0
424
+1436849943	235718	194	90	0	195	8	0
425
+1438269784	235718	1029	90	0	195	8	0
426
+1438269918	235718	1029	90	0	195	8	0
427
+1438271263	235718	1035	90	0	195	8	0
428
+1438273348	235718	2894	90	0	195	8	0
429
+1438282294	235718	2894	90	0	195	8	0
430
+1438303550	235718	2935	90	0	195	8	0
431
+1439397263	235718	2965	90	0	195	8	0
432
+1439399146	235718	2982	90	0	195	8	0
433
+1441737290	235718	5196	90	0	195	8	0
434
+1442267605	235718	5300	90	0	195	8	0
435
+1442597513	235718	5308	90	0	195	8	0
436
+1442954764	235718	5308	90	0	195	8	0
437
+1443018794	235718	5331	90	0	195	8	0
438
+1443031570	235718	5370	90	0	195	8	0
439
+1443032056	235718	5370	90	0	195	8	0
440
+1443119585	235718	5390	90	0	195	8	0
441
+1443711951	235718	5465	90	0	195	8	0
442
+1443798856	235718	5478	90	0	195	8	0
443
+1449685453	235718	10423	90	0	195	8	0
444
+1450283272	235718	11105	90	0	195	8	0
445
+1450664183	235718	11363	90	0	195	8	0
446
+1450666600	235718	11389	90	0	195	8	0
447
+1452027169	235718	11389	93	0	195	8	0
448
+1452028386	235718	11389	1094	0	195	8	0
449
+1452200566	235718	11389	1143	0	195	8	0
450
+1452207430	235718	11389	1319	0	195	8	0
451
+1452207602	235718	11395	1324	0	195	8	0
452
+1452212539	235718	11420	1324	0	195	8	0
453
+1452280833	235718	11420	1325	0	195	8	0
454
+1452793688	235718	11439	1325	0	195	8	0
455
+1453224499	235718	11448	1325	0	195	8	0
456
+1453225168	235718	11457	1325	0	195	8	0
457
+1453230613	235718	11457	1325	0	195	8	0
458
+1453323055	235718	11525	1325	0	195	8	0
459
+1453493571	235718	11562	1325	0	195	8	0
460
+1453704637	235718	11562	1325	0	195	8	24
461
+1453825100	235718	11590	1325	0	195	8	24
462
+1453837575	235718	11715	1325	0	195	8	24
463
+1453848840	235718	11715	1325	0	195	8	24
464
+1453953447	235718	13364	1380	0	195	8	24
465
+1454689867	235718	13364	1431	0	195	8	24
466
+1455057770	235718	13364	1447	0	195	8	24
467
+1455660753	235718	13423	1447	0	195	8	24
468
+1455830872	235718	13459	1447	0	195	8	24
469
+1455831309	235718	13475	1447	0	195	8	24
470
+1455831437	235718	13502	1447	0	195	8	24
471
+1455894228	235718	13502	1708	0	195	8	24
472
+1455897059	235718	13502	1713	0	195	8	24
473
+1456110772	235718	13525	1713	0	195	8	24
474
+1456266997	235718	13525	1718	0	195	8	24
475
+1456267705	235718	13525	1724	0	195	8	24
476
+1456269047	235718	14252	1724	0	195	8	24
477
+1456506668	235718	14252	2037	0	195	8	24
478
+1456806554	235718	14268	2037	0	195	8	24
479
+1457141043	235718	14273	2037	0	195	8	24
480
+1457452520	235718	14273	2045	0	195	8	24
481
+1457488422	235718	14273	2051	0	195	8	24
482
+1457563999	235718	14273	2249	0	195	8	24
483
+1457644085	235718	14273	2539	0	195	8	24
484
+1457644884	235718	14273	2683	0	195	8	24
485
+1457646092	235718	14273	2684	0	195	8	24
486
+1457987370	235718	14281	2684	0	195	8	24
487
+1457991490	235718	14281	2692	0	195	8	24
488
+1458004818	235718	14303	2692	0	195	8	24
489
+1458593314	235718	14310	2692	0	195	8	24
490
+1458765547	235718	14382	2692	0	195	8	24
491
+1458914088	235718	14390	2692	0	195	8	24
492
+1458991612	235718	14405	2692	0	195	8	24
493
+1459133960	235718	14421	2692	0	195	8	24
494
+1460047216	235718	14429	2692	0	195	8	24
495
+1460047380	235718	14451	2730	0	195	8	24
496
+1460375644	235718	14636	2730	0	195	8	24
497
+1460380408	235718	14685	2764	0	195	8	24
498
+1460407428	235718	14736	2764	0	195	8	24
499
+1460569764	235718	14736	2764	0	195	8	24
500
+1461074070	235718	14755	2764	0	195	8	24
501
+1461270656	235718	14768	2835	0	195	8	24
502
+1461354039	235718	14768	2841	0	195	8	24
503
+1461551595	235718	14768	2841	0	195	8	24
504
+1461552637	235718	14791	2841	0	195	8	24
505
+1462227334	235718	14959	2841	0	195	8	24
506
+1463152815	235718	14959	2906	0	195	8	24
507
+1463157617	235718	14959	2945	0	195	8	24
508
+1463451445	235718	14959	2980	0	195	8	24
509
+1463451804	235718	14959	3031	0	195	8	24
510
+1463680867	235718	15014	3031	0	195	8	24
511
+1463758054	235718	15029	3031	0	195	8	24
512
+1464031184	235718	15037	3031	0	195	8	24
513
+1464096601	235718	15043	3031	0	195	8	24
514
+1464208709	235718	15043	3031	0	195	8	24
515
+1464237088	235718	15084	3031	0	195	8	24
516
+1464237194	235718	15115	3031	0	195	8	24
517
+1464293889	235718	15115	3031	0	195	8	24
518
+1464793340	235718	15120	3102	0	195	8	24
519
+1464880180	235718	15120	3102	0	195	8	24
520
+1464895801	235718	15139	3102	0	195	8	24
521
+1464902654	235718	15155	3102	0	195	8	24
522
+1465319220	235718	15195	3168	0	195	8	24
523
+1465424498	235718	15216	3168	0	195	8	24
524
+1466648922	235718	15263	3168	0	195	8	24
525
+1466649684	235718	15270	3168	0	195	8	24
526
+1466650102	235718	15335	3168	0	195	8	24
527
+1466701155	235718	15352	3168	0	195	8	24
528
+1466735347	235718	15361	3168	0	195	8	24
529
+1466736165	235718	15387	3174	0	195	8	24
530
+1468180054	235718	15459	3240	0	195	8	24
531
+1468292316	235718	15459	3262	0	195	8	24
532
+1468876762	235718	15459	3270	0	195	8	24
533
+1468876920	235718	15459	3277	0	195	8	24
534
+1469121939	235718	15459	3315	0	195	8	24
535
+1469157573	235718	15487	3315	0	195	8	24
536
+1469552934	235718	15487	3346	0	195	8	24
537
+1469589171	235718	15552	3360	0	195	8	24
538
+1469640240	235718	15577	3360	0	195	8	24
539
+1469723242	235718	15577	3362	0	195	8	24
540
+1469725928	235718	15602	3362	0	195	8	24
541
+1469780404	235718	15617	3362	0	195	8	24
542
+1475506438	235718	15618	3362	0	195	8	24
543
+1475508328	235718	15765	3362	0	195	8	24
544
+1475605531	235718	15898	3362	0	195	8	24
545
+1475759454	235718	15918	3362	0	195	8	24
546
+1476301940	235718	15918	3362	0	195	8	24
547
+1477421556	235718	15918	3376	0	195	8	24
548
+1477427877	235718	15970	3439	0	195	8	24
549
+1477433230	235718	15970	3447	0	195	8	24
550
+1477694191	235718	15970	3450	0	195	8	24
551
+1477939269	235718	15970	3497	0	195	8	24
552
+1477941012	235718	15970	3575	0	195	8	24
553
+1478115005	235718	15970	3575	0	195	8	24
554
+1478288605	235718	15970	3627	0	195	8	24
555
+1478551645	235718	15970	3628	0	195	8	24
556
+1478792001	235718	15970	3651	0	195	8	24
557
+1478814382	235718	15970	3651	0	195	8	24
558
+1478816505	235718	15970	3665	0	195	8	24
559
+1478878515	235718	15970	3682	0	195	8	24
560
+1479419832	235718	15970	3685	0	195	8	24
561
+1479846967	235718	15970	3687	0	195	8	24
562
+1480530088	235718	15970	3687	0	195	8	24
563
+1481670500	235718	15970	3740	0	195	8	24
564
+1482181507	235718	15970	3740	1	195	8	24
565
+1482336682	235718	15970	3746	1	195	8	24
566
+1482340119	235718	15970	3753	1	195	8	24
567
+1482353511	235718	15970	3771	11	195	8	24
568
+1482359008	235718	15970	3771	32	195	8	24
569
+1483455272	235718	15970	3796	76	195	8	24
570
+1484342644	235718	15970	3796	77	195	8	24
571
+1484684949	235718	15970	3796	99	195	8	24
572
+1484772851	235718	15970	3800	100	195	8	24
573
+1485799035	235718	15970	120285	116	195	8	24
574
+1485877540	235718	15970	120293	116	195	8	24
575
+1485877851	235718	15970	120317	116	195	8	24
576
+1485877920	235718	15970	120318	116	195	8	24
577
+1485878121	235718	15970	120340	116	195	8	24
578
+1486146025	235718	15970	120354	116	195	8	24
579
+1486412376	235718	15970	120363	116	195	8	24
580
+1486499625	235718	15970	120404	116	195	8	24
581
+1486755546	235718	15970	120405	116	195	8	24
582
+1487109340	235718	15970	120444	116	195	8	24
583
+1487277883	235718	15970	120444	1305	195	8	24
584
+1487783266	235718	15970	120444	1313	195	8	24
585
+1487946215	235718	15970	120503	1347	195	8	24
586
+1487965862	235718	15970	120503	1369	195	8	24
587
+1487966474	235718	15970	120503	1429	195	8	24
588
+1487966964	235718	15970	120503	1429	195	8	24
589
+1488313813	235718	15970	120503	1461	195	8	24
590
+1488406636	235718	15970	120525	1461	195	8	24
591
+1488407109	235718	15970	120587	1461	195	8	24
592
+1488409193	235718	15970	120620	1461	195	8	24
593
+1488560599	235718	15970	120718	1461	195	8	24
594
+1488773682	235718	15970	120718	1545	195	8	24
595
+1488814084	235718	15970	120718	1545	195	8	24
596
+1488815988	235718	15970	120747	1545	195	8	24
597
+1489686589	235718	15970	120753	1545	195	8	24
598
+1490023066	235718	15970	120755	1545	195	8	24
599
+1490035644	235718	15970	120761	1545	195	8	24
600
+1490041875	235718	15970	120763	1545	195	8	24
601
+1490113585	235718	15970	120763	1545	195	8	24
602
+1490120437	235718	15970	120764	1545	195	8	24
603
+1490128257	235718	15970	120783	1545	195	8	24
604
+1490645760	235718	15970	120783	1545	195	8	24
605
+1490725467	235718	15970	120858	1545	195	8	24
606
+1491336954	235718	15970	120858	1545	195	8	24
607
+1491341896	235718	15970	120875	1545	195	8	24
608
+1492026512	235718	15970	120893	1545	195	8	24
609
+1492107919	235718	15970	120898	1545	195	8	24
610
+1492108901	235718	15970	120998	1545	195	8	24
611
+1492435237	235718	15970	120998	1545	195	8	24
612
+1492435982	235718	15970	120998	1545	195	8	24
613
+1492454272	235718	15970	121021	1545	195	8	24
614
+1493052262	235718	15970	121021	1646	195	8	24
615
+1493153070	235718	15970	121070	1646	195	8	24
616
+1493407521	235718	15970	121078	1646	195	8	24
617
+1493412286	235718	15970	121093	1646	195	8	24

BIN
FlaskTest/static/img/zebra.png Целия файл


BIN
FlaskTest/static/img/zebra_white.png Целия файл


+ 475
- 0
FlaskTest/static/js/script.js Целия файл

@@ -0,0 +1,475 @@
1
+// menu transition
2
+$("#leftside-navigation .sub-menu > a").click(function(e) {
3
+  $("#leftside-navigation ul ul").slideUp(), $(this).next().is(":visible") || $(this).next().slideDown(),
4
+  e.stopPropagation()
5
+});
6
+
7
+// Cool load in the beginning
8
+$("#leftside-navigation .sub-menu > a").trigger("click");
9
+
10
+// generateHeatMap(["../static/data/data_dummy.tsv", "../static/data/data2_dummy.tsv"], "#chart");
11
+
12
+function generateHeatMap(datasets, divID){
13
+  // heatmap SVG
14
+  const margin = { top: 50, right: 0, bottom: 100, left: 30 },
15
+    width =520 - margin.left - margin.right,
16
+    height = 320 - margin.top - margin.bottom,
17
+    gridSize = Math.floor(width / 24),
18
+    legendElementWidth = gridSize*2,
19
+    buckets = 9,
20
+
21
+    // blue colors
22
+    // colors = ["#ffffd9","#edf8b1","#c7e9b4","#7fcdbb","#41b6c4","#1d91c0","#225ea8","#253494","#081d58"], // alternatively colorbrewer.YlGnBu[9]
23
+    
24
+    // orange colors
25
+    // colors = ['#fff5eb','#fee6ce','#fdd0a2','#fdae6b','#fd8d3c','#f16913','#d94801','#a63603','#7f2704'],
26
+
27
+    // yellow colors
28
+    colors =["#ECE622","#D5CF22","#BEB922","#A7A222","#908C22","#797522","#625F22","#625F22","#4B4822","#343222"],
29
+
30
+    days = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
31
+    times = ["1a", "2a", "3a", "4a", "5a", "6a", "7a", "8a", "9a", "10a", "11a", "12a", "1p", "2p", "3p", "4p", "5p", "6p", "7p", "8p", "9p", "10p", "11p", "12p"];
32
+    
33
+    // datasets = ["../static/data/data_dummy.tsv", "../static/data/data2_dummy.tsv"];
34
+
35
+  const svg = d3.select(divID).append("svg")
36
+    .attr("width", width + margin.left + margin.right)
37
+    .attr("height", height + margin.top + margin.bottom)
38
+    .append("g")
39
+    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
40
+
41
+
42
+  const dayLabels = svg.selectAll(".dayLabel")
43
+    .data(days)
44
+    .enter().append("text")
45
+      .text(function (d) { return d; })
46
+      .attr("x", 0)
47
+      .attr("y", (d, i) => i * gridSize)
48
+      .style("text-anchor", "end")
49
+      .attr("transform", "translate(-6," + gridSize / 1.5 + ")")
50
+      .attr("class", (d, i) => ((i >= 0 && i <= 4) ? "dayLabel mono axis axis-workweek" : "dayLabel mono axis"));
51
+
52
+  const timeLabels = svg.selectAll(".timeLabel")
53
+    .data(times)
54
+    .enter().append("text") 
55
+      .text((d) => d)
56
+      .attr("x", (d, i) => i * gridSize)
57
+      .attr("y", 0)
58
+      .style("text-anchor", "middle")
59
+      .attr("transform", "translate(" + gridSize / 2 + ", -6)")
60
+      .attr("class", (d, i) => ((i >= 7 && i <= 16) ? "timeLabel mono axis axis-worktime" : "timeLabel mono axis"));
61
+
62
+  const type = (d) => {
63
+    return {
64
+      day: +d.day,
65
+      hour: +d.hour,
66
+      value: +d.value
67
+    };
68
+  };
69
+
70
+  const heatmapChart = function(tsvFile) {
71
+  d3.tsv(tsvFile, type, (error, data) => {
72
+
73
+    var div = d3.select("body").append("div")
74
+      .attr("class", "tooltip")
75
+      .style("opacity", 0);
76
+
77
+    const colorScale = d3.scaleQuantile()
78
+      .domain([0, buckets - 1, d3.max(data, (d) => d.value)])
79
+      .range(colors);
80
+
81
+    const cards = svg.selectAll(".hour")
82
+      .data(data, (d) => d.day+':'+d.hour);
83
+
84
+    cards.append("title");
85
+
86
+    cards.enter().append("rect")
87
+    .attr("x", (d) => (d.hour - 1) * gridSize)
88
+    .attr("y", (d) => (d.day - 1) * gridSize)
89
+    .attr("rx", 4)
90
+    .attr("ry", 4)  
91
+    .attr("class", "hour bordered")
92
+    .attr("width", gridSize)
93
+    .attr("height", gridSize)
94
+    .style("fill", colors[0])
95
+    .on("mouseover", function(d) {
96
+      div.transition()
97
+        .duration(200)
98
+        .style("opacity", .9);
99
+      div.html("Day: " +days[d.day-1]+ "<br>Hour: " +times[d.hour-1]+ "<br>Commits: " +d.value+ "")
100
+        .style("left", (d3.event.pageX + 20) + "px")
101
+        .style("top", (d3.event.pageY - 20) + "px");
102
+      })
103
+    .on("mouseout", function(d) {
104
+      div.transition()
105
+        .duration(500)
106
+        .style("opacity", 0);
107
+      })
108
+    .merge(cards)
109
+      .transition()
110
+      .duration(1000)
111
+      .style("fill", (d) => colorScale(d.value));
112
+
113
+    // cards.select("title").text((d) => d.value);  
114
+
115
+    cards.exit().remove();
116
+
117
+    const legend = svg.selectAll(".legend")
118
+      .data([0].concat(colorScale.quantiles()), (d) => d);
119
+
120
+    const legend_g = legend.enter().append("g")
121
+      .attr("class", "legend");
122
+
123
+    legend_g.append("rect")
124
+      .attr("x", (d, i) => legendElementWidth * i)
125
+      .attr("y", height)
126
+      .attr("width", legendElementWidth)
127
+      .attr("height", gridSize / 2)
128
+      .style("fill", (d, i) => colors[i]);
129
+
130
+    legend_g.append("text")
131
+      .attr("class", "mono")
132
+      .text((d) => "≥ " + Math.round(d))
133
+      .attr("x", (d, i) => legendElementWidth * i)
134
+      .attr("y", height + gridSize);
135
+
136
+    legend.exit().remove();
137
+  });
138
+  };
139
+
140
+  // use first dataset by default
141
+  heatmapChart(datasets[0]);
142
+
143
+  // dataset buttons
144
+  const datasetpicker = d3.select("#dataset-picker")
145
+    .selectAll(".btn btn-primary")
146
+    .data(datasets);
147
+
148
+  // dataset picking button
149
+  datasetpicker.enter()
150
+    .append("input")
151
+    .attr("value", (d) => "Dataset " + d)
152
+    .attr("type", "button")
153
+    .attr("class", "btn-sm btn-primary")
154
+    .on("click", (d) => heatmapChart(d));
155
+
156
+
157
+}
158
+
159
+
160
+
161
+
162
+
163
+
164
+// generateBarChart("../static/data/day_of_week_copy.tsv", "#day_of_week");
165
+
166
+function generateBarChart (pathToTSV,divID){
167
+  // Bar chart, see function 'main' for main execution wheel
168
+
169
+   function toNum(d){
170
+    //cleaner function
171
+        d.commits= +d.commits
172
+        return d
173
+  };
174
+
175
+  d3.tsv(pathToTSV, toNum, function (error,data){
176
+      "use strict"
177
+      // declare outside for reference later
178
+      var width=520
179
+      var height=380
180
+      var chartWidth, chartHeight
181
+      var svg = d3.select(divID).append("svg")
182
+      var axisLayer = svg.append("g").classed("axisLayer", true)
183
+      var chartLayer = svg.append("g").classed("chartLayer", true)
184
+      var margin = {top:50, right:0, bottom:100,  left:30}
185
+      
186
+      var xScale = d3.scaleBand()
187
+      var yScale = d3.scaleLinear()
188
+
189
+      var div = d3.select("body").append("div")
190
+      .attr("class", "rect_tooltip")
191
+      .style("opacity", 0);
192
+      
193
+      function main(data) {
194
+          setSize();
195
+          drawAxisBarChart();
196
+          drawChartBarChart();    
197
+      }
198
+      
199
+      function setSize() {
200
+
201
+          chartWidth = width - margin.left - margin.right,
202
+          chartHeight = height - margin.top - margin.bottom,        
203
+          
204
+          svg
205
+            .attr("width", 520)
206
+            .attr("height", 320)
207
+          
208
+          axisLayer
209
+            .attr("width", chartWidth)
210
+            .attr("height", chartHeight)
211
+          
212
+          chartLayer
213
+              .attr("width", chartWidth)
214
+              .attr("height", chartHeight)
215
+              .attr("transform", "translate("+[margin.left, margin.top]+")")
216
+              
217
+          xScale.domain(data.map(function(d){ return d.day_name })).range([0, chartWidth])
218
+              .paddingInner(0.1) 
219
+              .paddingOuter(0.5)
220
+
221
+          yScale.domain([0, d3.max(data, function(d){ return d.commits})]).range([chartHeight, 0])
222
+              
223
+      }
224
+      
225
+      function drawChartBarChart() {
226
+         // monitor the transition
227
+          var t = d3.transition()
228
+              .duration(1100)
229
+              .ease(d3.easeLinear)
230
+              .on("start", function(d){ console.log("Bar Chart Transiton start") })
231
+              .on("end", function(d){ console.log("Bar Chart Transiton end") })
232
+          
233
+          var bar = chartLayer
234
+            .selectAll(".bar")
235
+            .data(data)
236
+          
237
+          bar.exit().remove() 
238
+
239
+          bar
240
+            .enter()
241
+            .append("rect")
242
+            .classed("bar", true)
243
+            .merge(bar) 
244
+            .attr("fill", "rgb(236, 230, 34)")
245
+            .attr("width", xScale.bandwidth())
246
+            .attr("stroke", "#323232")
247
+            //setup for cool transition
248
+            .attr("height", 0)
249
+            .attr("transform", function(d){ return "translate("+[xScale(d.day_name), chartHeight]+")"})
250
+
251
+          var labels = chartLayer
252
+            .selectAll("labels")
253
+            .data(data)
254
+              
255
+          //setup for percentage display
256
+          var totalCommits=0
257
+          data.forEach(function(d){
258
+            totalCommits+=d.commits
259
+          })   
260
+
261
+          labels
262
+            .enter()
263
+            .append("text")
264
+            .text(function(d){
265
+              var percentage= (d.commits/totalCommits *100).toFixed(2)
266
+              return ""+percentage+"%";
267
+            })
268
+            .attr("transform", function(d){
269
+               return "translate("+[xScale(d.day_name)+5, chartHeight-5]+")"
270
+            })
271
+
272
+          chartLayer.selectAll(".bar").transition(t)
273
+              // grows to appropriate amount
274
+              .attr("height", function(d){ return chartHeight - yScale(d.commits) })
275
+              .attr("transform", function(d){ return "translate("+[xScale(d.day_name), yScale(d.commits)]+")"})
276
+      }
277
+      
278
+      function drawAxisBarChart(){
279
+          var yAxis = d3.axisLeft(yScale)
280
+              .tickSizeInner(-chartWidth)
281
+          
282
+          axisLayer.append("g")
283
+              .attr("transform", "translate("+[margin.left, margin.top]+")")
284
+              .attr("class", "axis y")
285
+              .call(yAxis);
286
+              
287
+          var xAxis = d3.axisBottom(xScale)
288
+      
289
+          axisLayer.append("g")
290
+              .attr("class", "axis x")
291
+              .attr("transform", "translate("+[margin.left, (height-margin.bottom)]+")")
292
+              .call(xAxis);
293
+      }  
294
+      
295
+      //kicks of execution of the bar chart
296
+      main();
297
+
298
+  }); //end of tsv read in
299
+
300
+} //end of generateBarChart
301
+
302
+// generateLineChart("../static/data/commits_by_author_copy.tsv", "#lineChart");
303
+// generateLineChart("../static/data/lines_of_code_by_author_copy.tsv", "#lineChart2");
304
+
305
+
306
+
307
+
308
+
309
+function generateLineChart(pathToTSV, divID){
310
+
311
+  function allToNumber(d){
312
+    //converts everything to Number type
313
+    for (var key in d){
314
+      d[key] = +d[key]
315
+    }
316
+    //fix for Unix timestamps
317
+    d.date = new Date(d.date * 1000)
318
+    return d;
319
+  }
320
+
321
+  d3.tsv(pathToTSV, allToNumber, function (error,data){
322
+    if (error) throw error;
323
+
324
+    var authors = data.columns.slice(1).map(function(id) {
325
+      return {
326
+        id: id,
327
+        values: data.map(function(d) {
328
+          return {date: d.date, commits: d[id]};
329
+        })
330
+      };
331
+    });
332
+
333
+    var svgWidth=1200,
334
+    svgHeight=400,
335
+    chartWidth= 1000;
336
+
337
+    var svg = d3.select(divID).append("svg").attr("height", svgHeight).attr("width", svgWidth),
338
+      margin = {top: 20, right: 80, bottom: 30, left: 50},
339
+      //this is smaller than svgWidth to accomodate Legend
340
+      width = chartWidth - margin.left - margin.right,
341
+      height = svgHeight - margin.top - margin.bottom,
342
+      //dynamically builds grid
343
+      numberGridLines=30
344
+      //main chart container
345
+      g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
346
+
347
+    var xScale = d3.scaleTime().range([0, width]),
348
+        yScale = d3.scaleLinear().range([height, 0]),
349
+        // 10 nice colors
350
+        colors = d3.scaleOrdinal(d3.schemeCategory10);
351
+
352
+    xScale.domain(d3.extent(data, function(d) { return d.date; }));
353
+
354
+    yScale.domain([
355
+      d3.min(authors, function(c) { return d3.min(c.values, function(d) { return d.commits; }); }),
356
+      d3.max(authors, function(c) { return d3.max(c.values, function(d) { return d.commits; }); })
357
+    ]);
358
+
359
+    var line = d3.line()
360
+    // different types of interpolations here
361
+    // https://bl.ocks.org/d3noob/ced1b9b18bd8192d2c898884033b5529
362
+      .curve(d3.curveBasis)
363
+      .x(function(d) { return xScale(d.date); })
364
+      .y(function(d) { return yScale(d.commits);});
365
+
366
+    colors.domain(authors.map(function(c) { return c.id; }));
367
+
368
+    function main(){
369
+      drawAxisLineChart();
370
+      drawChartLineChart();
371
+      drawLegend();
372
+    }
373
+
374
+    // gridlines in x axis 
375
+    function make_x_gridlines() {   
376
+      return d3.axisBottom(xScale)
377
+          .ticks(numberGridLines)
378
+    }
379
+
380
+    // gridlines in y axis
381
+    function make_y_gridlines() {   
382
+      return d3.axisLeft(yScale)
383
+          .ticks(numberGridLines)
384
+    }
385
+
386
+    function drawAxisLineChart(){
387
+
388
+      g.append("g")
389
+        .attr("class", "axis axis--x")
390
+        .attr("transform", "translate(0," + height + ")")
391
+        .call(d3.axisBottom(xScale));
392
+
393
+      g.append("g")
394
+          .attr("class", "axis axis--y")
395
+          .call(d3.axisLeft(yScale))
396
+        // .append("text")
397
+        //   .attr("transform", "rotate(-90)")
398
+        //   .attr("y", 6)
399
+        //   .attr("dy", "0.71em")
400
+        //   .attr("fill", "#000")
401
+
402
+      // add the X gridlines
403
+      g.append("g")     
404
+          .attr("class", "grid")
405
+          .attr("transform", "translate(0," + height + ")")
406
+          .call(make_x_gridlines()
407
+              .tickSize(-height)
408
+              .tickFormat("")
409
+          );
410
+
411
+      // add the Y gridlines
412
+      g.append("g")     
413
+          .attr("class", "grid")
414
+          .call(make_y_gridlines()
415
+              .tickSize(-width)
416
+              .tickFormat("")
417
+          );
418
+
419
+    }
420
+
421
+    function drawChartLineChart() {
422
+      var author = g.selectAll(".author")
423
+        .data(authors)
424
+        .enter().append("g")
425
+          .attr("class", "author");
426
+
427
+      author.append("path")
428
+          .attr("class", "line")
429
+          .attr("d", function(d) { return line(d.values); })
430
+          .style("stroke", function(d) { return colors(d.id); });
431
+
432
+      // Enable to show author at the end of the line
433
+      // author.append("text")
434
+      //     .datum(function(d) { return {id: d.id, value: d.values[d.values.length - 1]}; })
435
+      //     .attr("transform", function(d) { return "translate(" + xScale(d.value.date) + "," + yScale(d.value.commits) + ")"; })
436
+      //     .attr("x", 3)
437
+      //     .attr("dy", "0.35em")
438
+      //     .style("font", "10px sans-serif")
439
+      //     .text(function(d) { return d.id; });
440
+
441
+      }
442
+
443
+      function drawLegend(){
444
+        var legend= svg.selectAll(".legend-item")
445
+        .data(authors)
446
+        .enter().append("g")
447
+          .attr("class","legend");
448
+
449
+        legend.append("rect")
450
+            .attr('x', chartWidth - 20)
451
+            .attr('y', function(d, i) {
452
+              return (i * 35) + 20;
453
+            })
454
+            .attr('width', 10)
455
+            .attr('height', 10)
456
+            .style('fill', function(d) {
457
+              return colors(d.id);
458
+            });
459
+        legend.append('text')
460
+            .attr('x', chartWidth - 8)
461
+            .attr('y', function(d, i) {
462
+              return (i * 35) + 29;
463
+            })
464
+            .text(function(d) {
465
+              return d.id;
466
+            });
467
+      }
468
+
469
+      main(); //kicks off main execution wheel
470
+
471
+  }); //end tsv read in
472
+
473
+}; //end generate line chart
474
+
475
+

+ 61
- 0
FlaskTest/templates/base.html Целия файл

@@ -0,0 +1,61 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+     <meta name="viewport" content="width=device-width, initial-scale=1">
5
+     <link rel="stylesheet" href="../static/css/style.css">
6
+     
7
+     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
8
+     <title> {{sub.name}} - Dashboard </title>
9
+
10
+  </head>
11
+  <body class="bg-faded">
12
+
13
+
14
+	<!-- top fixed bar -->
15
+	<nav class="navbar black sticky-top">
16
+	  <a class="navbar-brand nav_text" href="#" style="color: white">
17
+	    <img src="../static/img/zebra_white.png" alt="Zebra Logo" class="zebra_img" class="d-inline-block align-top">
18
+	    EMC Engineering Code Statistics
19
+	  </a>
20
+	</nav>
21
+
22
+	<!-- sidebar menu -->
23
+	<aside class="sidebar">
24
+	  <div id="leftside-navigation" class="nano">
25
+	    <ul class="nano-content">
26
+	      <li class="sub-menu">
27
+	        <a href="{{url_for('dashboard')}}"><i class="fa fa-home"></i><span>{{nav.main_repo[0].name}}</span><i class="arrow fa"></i></a>
28
+	      </li>
29
+
30
+	      <li class="sub-menu">
31
+	        <a href="javascript:void(0);"><i class="fa fa-cogs"></i><span>Sub-Repos</span><i class="arrow fa fa-angle-right pull-right"></i></a>
32
+	        <ul>
33
+	        <!-- drop drown items are name of sub directories -->
34
+	        {% for sub_repo in nav.sub_repos %}
35
+		        <li> <a href ="{{url_for('dashboard')}}/{{sub_repo.name}}"> {{sub_repo.name}} </a></li>
36
+	        {% endfor %}
37
+
38
+	        </ul>
39
+	      </li>
40
+
41
+	    <!--   <li class="sub-menu">
42
+	        <a href="#"><i class="fa fa-terminal"></i><span>Lines</span><i class="arrow fa"></i></a>
43
+	      </li>
44
+
45
+	      <li class="sub-menu">
46
+	        <a href="#"><i class="fa fa-user"></i><span>Authors</span><i class="arrow fa "></i></a>
47
+	      </li> -->
48
+
49
+	    </ul>
50
+
51
+	  </div>
52
+	</aside>
53
+
54
+
55
+  	{% block content %}{% endblock %}
56
+
57
+
58
+    
59
+
60
+  </body>
61
+</html>

+ 160
- 0
FlaskTest/templates/combined_dashboard.html Целия файл

@@ -0,0 +1,160 @@
1
+<!DOCTYPE html>
2
+<html>
3
+  <head>
4
+     <meta name="viewport" content="width=device-width, initial-scale=1">
5
+     <!-- <script src='https://cdnjs.cloudflare.com/ajax/libs/prefixfree/1.0.7/prefixfree.min.js'></script> -->
6
+     <!--  <link rel='stylesheet prefetch' href='//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/themes/smoothness/jquery-ui.css'> -->
7
+
8
+     <link rel="stylesheet" href="../static/css/style.css">
9
+     
10
+     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
11
+  </head>
12
+
13
+  <body class="bg-faded">
14
+
15
+    <!-- top fixed bar -->
16
+    <nav class="navbar black sticky-top">
17
+      <a class="navbar-brand nav_text" href="#" style="color: white">
18
+        <img src="../static/img/zebra_white.png" alt="Zebra Logo" class="zebra_img" class="d-inline-block align-top">
19
+        EMC Engineering Tools &amp; Process News Letter
20
+      </a>
21
+    </nav>
22
+
23
+    <!-- sidebar menu -->
24
+    <aside class="sidebar">
25
+      <div id="leftside-navigation" class="nano">
26
+        <ul class="nano-content">
27
+          <li class="sub-menu">
28
+            <a href="home.html"><i class="fa fa-home"></i><span>Home</span><i class="arrow fa"></i></a>
29
+          </li>
30
+
31
+          <li class="sub-menu">
32
+            <a href="javascript:void(0);"><i class="fa fa-cogs"></i><span>Projects</span><i class="arrow fa fa-angle-right pull-right"></i></a>
33
+            <ul>
34
+              <li><a href="#">Shim's cool project</a>
35
+              </li>
36
+              <li><a href="#">Tony's cool project</a>  
37
+              </li>
38
+              <li><a href="#">Greg's intern project</a>
39
+              </li>
40
+            </ul>
41
+          </li>
42
+
43
+          <li class="sub-menu">
44
+            <a href="lines.html"><i class="fa fa-terminal"></i><span>Lines</span><i class="arrow fa"></i></a>
45
+          </li>
46
+
47
+          <li class="sub-menu">
48
+            <a href="authors.html"><i class="fa fa-user"></i><span>Authors</span><i class="arrow fa "></i></a>
49
+          </li>
50
+
51
+        </ul>
52
+
53
+      </div>
54
+    </aside>
55
+
56
+    <!-- Main Dashboard -->
57
+
58
+    <!-- Summary -->
59
+    <div class="main_dash">
60
+      <div class="container-fluid">
61
+        <div class="card">
62
+          <div class="card-block">
63
+            <h1> Cool Project Name Here </h1>
64
+            <br>
65
+            <h6> Summary: This project is about making Zebra really fast and efficient </h6>
66
+            <br>
67
+            Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum
68
+            </p>
69
+          </div>
70
+        </div>
71
+
72
+        <div class="row top_spacer">
73
+
74
+          <!-- Metric 1, half screen-->
75
+          <div class="col-md-6">
76
+            <div class="card">
77
+              <div class="card-block">
78
+                <h4 class="card-title"># Commits by day-dummy </h4>
79
+                <div class="metric">
80
+                  <div id="chart"></div>
81
+                  <div id="dataset-picker"></div>
82
+                </div>
83
+                <div class="details_spacer">
84
+                  <div class="text_content">
85
+                    <h5 class="card-text"> This data shows cool stuff </h5>
86
+                    <p class="card-text"> As you can see, this data focuses on business hours </p>
87
+                    <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
88
+                  </div>
89
+                </div>
90
+              </div>
91
+            </div>
92
+          </div>
93
+
94
+          <!-- Metric 2, half screen-->
95
+          <div class="col-md-6">
96
+            <div class="card">
97
+              <div class="card-block">
98
+                <h4 class="card-title">Metric 2 </h4>
99
+                <div class="metric">
100
+                  <img class="img-fluid" style="width:100%; height: 350px;" src="https://mdbootstrap.com/img/Photos/Horizontal/Nature/4-col/img%20%282%29.jpg" alt="Card image cap">
101
+                </div>
102
+                <div class="details_spacer">
103
+                  <div class="text_content">
104
+                    <h5 class="card-text"> This is a cool image that will be a better metric</h5>
105
+                    <p class="card-text"> Summary: this is a mountain </p>
106
+                    <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
107
+                  </div>
108
+                </div>
109
+              </div>
110
+            </div>
111
+          </div>
112
+
113
+        </div> 
114
+
115
+        <!-- Metric 3, full width -->
116
+        <div class="row top_spacer">
117
+
118
+          <div class="col-md-12">
119
+            <div class="card">
120
+              <div class="card-block">
121
+                <h4 class="card-title">Metric 3 </h4>
122
+                <div class="metric">
123
+                  <img class="img-fluid" src="http://cdn.inquisitr.com/wp-content/uploads/2016/10/One-of-Queen-Elizabeths-last-Corgis-has-died.jpg" style="width:100%; height: 400px;" alt="Card image cap">
124
+                </div>
125
+                <div class="details_spacer">
126
+                  <div class="text_content">
127
+                    <h5 class="card-text"> Greg Likes Corgis</h5>
128
+                    <p class="card-text"> As you can see, corgis are little fluff balls </p>
129
+                    <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
130
+                  </div>
131
+                </div>
132
+              </div>
133
+            </div>
134
+          </div>
135
+
136
+        </div>
137
+
138
+        <!-- bottom for aesthetic spacing -->
139
+        <div class="top_spacer"></div>
140
+
141
+      </div>
142
+    </div>
143
+
144
+
145
+
146
+    <!-- bootstrap -->
147
+    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
148
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
149
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
150
+
151
+    <!-- for nav bar accordion -->
152
+    <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
153
+    <script src='//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js'></script>
154
+
155
+    <!-- for visualizations -->
156
+    <script src="http://d3js.org/d3.v4.js"></script>
157
+    <script src="../static/js/script.js" type="text/javascript"></script>
158
+
159
+  </body>
160
+</html>

+ 156
- 0
FlaskTest/templates/dashboard.html Целия файл

@@ -0,0 +1,156 @@
1
+{% extends "base.html" %}
2
+
3
+{% block content %}
4
+
5
+
6
+<!-- Main Dashboard -->
7
+<!-- Summary -->
8
+<div class="main_dash">
9
+  <div class="container-fluid">
10
+    <div class="card drop-shadow">
11
+      <div class="card-block">
12
+        <h1> {{sub.name}} </h1>
13
+        <br>
14
+        <h6> {{sub.summary}} </h6>
15
+        <br>
16
+        <p> {{sub.description}}</p>
17
+      </div>
18
+    </div>
19
+
20
+    <div class="row top_spacer">
21
+
22
+      <!-- Metric 1, half screen, heatmap-->
23
+      <div class="col-md-6">
24
+        <div class="card drop-shadow">
25
+          <div class="card-block">
26
+            <h4 class="card-title">Commits by Hour of Week-dummy data </h4>
27
+            <div class="metric">
28
+              <div id="{{sub.visualizations[0].heatmap.divID}}"></div>
29
+              <div id="dataset-picker"></div>
30
+            </div>
31
+            <div class="details_spacer">
32
+              <div class="text_content">
33
+                <h5 class="card-text"> This data shows cool stuff </h5>
34
+                <p class="card-text"> As you can see, this data focuses on business hours </p>
35
+                <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
36
+              </div>
37
+            </div>
38
+          </div>
39
+        </div>
40
+      </div>
41
+
42
+      <!-- Metric 2, half screen, bar chart-->
43
+      <div class="col-md-6">
44
+        <div class="card drop-shadow">
45
+          <div class="card-block">
46
+            <h4 class="card-title"># Commits by day - real data </h4>
47
+            <div class="metric">
48
+
49
+              <div id="{{sub.visualizations[0].barchart.divID}}"></div>
50
+
51
+            </div>
52
+            <div class="details_spacer">
53
+              <div class="text_content">
54
+                <h5 class="card-text"> Bar Chart</h5>
55
+                <p class="card-text"> What week is this from?  <br>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
56
+                tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
57
+                quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
58
+                consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
59
+                cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
60
+                proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
61
+                <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
62
+              </div>
63
+            </div>
64
+          </div>
65
+        </div>
66
+      </div>
67
+
68
+    </div> 
69
+
70
+    <!-- Metric 3, full width, line chart -->
71
+    <div class="row top_spacer">
72
+
73
+      <div class="col-md-12">
74
+        <div class="card drop-shadow">
75
+          <div class="card-block">
76
+            <h4 class="card-title">Commits per Author - real data</h4>
77
+            <div class="metric">
78
+
79
+              <div id ="{{sub.visualizations[0].linegraph1.divID}}"></div>
80
+
81
+            </div>
82
+            <div class="details_spacer">
83
+              <div class="text_content">
84
+                <h5 class="card-text"> Multi Series Line Chart</h5>
85
+                <p class="card-text"> Theres a few authors on this project but 1 stands out </p>
86
+                <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
87
+              </div>
88
+            </div>
89
+          </div>
90
+        </div>
91
+      </div>
92
+
93
+    </div>
94
+
95
+    <!-- Another test metric, line chart -->
96
+    <div class="row top_spacer">
97
+
98
+      <div class="col-md-12">
99
+        <div class="card drop-shadow">
100
+          <div class="card-block">
101
+            <h4 class="card-title">Cumulative Added Lines by Author - real data</h4>
102
+            <div class="metric">
103
+
104
+              <div id ="{{sub.visualizations[0].linegraph2.divID}}"></div>
105
+
106
+            </div>
107
+            <div class="details_spacer">
108
+              <div class="text_content">
109
+                <h5 class="card-text"> Multi Series Line Chart</h5>
110
+                <p class="card-text"> Theres a few authors on this project but 1 stands out </p>
111
+                <p class="card-text"> <small class="text-muted"> System last updated at 11:34am </small></p>
112
+              </div>
113
+            </div>
114
+          </div>
115
+        </div>
116
+      </div>
117
+
118
+    </div>
119
+
120
+    <!-- bottom for aesthetic spacing -->
121
+    <div class="top_spacer"></div>
122
+
123
+  </div>
124
+</div>
125
+
126
+
127
+
128
+    <!-- bootstrap -->
129
+    <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
130
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
131
+    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js" integrity="sha384-vBWWzlZJ8ea9aCX4pEW3rVHjgjt7zpkNpZk+02D9phzyeVkE+jo0ieGizqPLForn" crossorigin="anonymous"></script>
132
+
133
+    <!-- for nav bar accordion -->
134
+    <script src='//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js'></script>
135
+    <script src='//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js'></script>
136
+
137
+    <!-- for visualizations -->
138
+    <script src="http://d3js.org/d3.v4.js"></script>
139
+    <script src="../static/js/script.js" type="text/javascript"></script>
140
+
141
+    <script type="text/javascript">
142
+
143
+    generateHeatMap([
144
+       '{{url_for("static", filename=sub.visualizations[0].heatmap.data_path1)}}',
145
+       '{{url_for("static", filename=sub.visualizations[0].heatmap.data_path2)}}' ],
146
+       "#{{sub.visualizations[0].heatmap.divID}}");
147
+
148
+      generateBarChart('{{url_for("static", filename=sub.visualizations[0].barchart.data_path)}}', "#{{sub.visualizations[0].barchart.divID}}");
149
+
150
+      generateLineChart('{{url_for("static", filename=sub.visualizations[0].linegraph1.data_path)}}', "#{{sub.visualizations[0].linegraph1.divID}}");
151
+    generateLineChart('{{url_for("static", filename=sub.visualizations[0].linegraph2.data_path)}}', "#{{sub.visualizations[0].linegraph2.divID}}");
152
+
153
+    </script>
154
+
155
+{% endblock %}
156
+

+ 48
- 24
gitstats Целия файл

@@ -732,6 +732,7 @@ def html_header(level, text):
732 732
     return '\n<h%d id="%s"><a href="#%s">%s</a></h%d>\n\n' % (level, name, name, text, level)
733 733
 
734 734
 class HTMLReportCreator(ReportCreator):
735
+
735 736
     def create(self, data, path):
736 737
         ReportCreator.create(self, data, path)
737 738
         self.title = data.projectname
@@ -755,6 +756,29 @@ class HTMLReportCreator(ReportCreator):
755 756
 
756 757
         f.write('<h1>GitStats - %s</h1>' % data.projectname)
757 758
 
759
+         # New function for TSV write ins, put this in a more organized place later
760
+
761
+        def writeHeaderstoNewTSV(fileName,headers):
762
+            """
763
+            Writes the headers to the first line of the .tsv file
764
+
765
+            Args:
766
+                fileName (String): Name of the destination file, ex: "data.tsv"
767
+                headers (List(String)): Headers to be written, ex: ["header1","header2"....]
768
+
769
+            """
770
+            assert fileName[-4:] ==".tsv", "fileName must be '.tsv' file not '%s'" %(fileName)
771
+
772
+            f = open (fileName,"w")
773
+            for headerIndex in range(len(headers)):
774
+                if headerIndex!=len(headers)-1:
775
+                    # write header along with\t 
776
+                    f.write(headers[headerIndex]+"\t")
777
+                else:
778
+                    # write last word along with\n
779
+                    f.write(headers[len(headers)-1]+"\n")
780
+            f.close()
781
+
758 782
         self.printNav(f)
759 783
 
760 784
         f.write('<dl>')
@@ -851,7 +875,12 @@ class HTMLReportCreator(ReportCreator):
851 875
                 fg.write('%d 0\n' % (i + 1))
852 876
         fg.close()
853 877
 
878
+
854 879
         # Day of Week
880
+
881
+        writeHeaderstoNewTSV("FlaskTest/static/data/day_of_week_TEST.tsv", ['day_number','day_name','commits'])
882
+        day_of_week_tsv=open("FlaskTest/static/data/day_of_week_TEST.tsv", "a+")
883
+
855 884
         f.write(html_header(2, 'Day of Week'))
856 885
         day_of_week = data.getActivityByDayOfWeek()
857 886
         f.write('<div class="vtable"><table>')
@@ -862,6 +891,8 @@ class HTMLReportCreator(ReportCreator):
862 891
             if d in day_of_week:
863 892
                 commits = day_of_week[d]
864 893
             fp.write('%d %s %d\n' % (d + 1, WEEKDAYS[d], commits))
894
+            # WRITE TO TSV, add +1, may cause off by one err
895
+            day_of_week_tsv.write("%d\t%s\t%d\n" %(d+1, WEEKDAYS[d],commits))
865 896
             f.write('<tr>')
866 897
             f.write('<th>%s</th>' % (WEEKDAYS[d]))
867 898
             if d in day_of_week:
@@ -873,7 +904,11 @@ class HTMLReportCreator(ReportCreator):
873 904
         f.write('<img src="day_of_week.png" alt="Day of Week">')
874 905
         fp.close()
875 906
 
907
+       
876 908
         # Hour of Week
909
+        writeHeaderstoNewTSV("FlaskTest/static/data/hour_of_week_TEST.tsv", ['day','hour','value'])
910
+        hour_of_week_TEST=open("FlaskTest/static/data/hour_of_week_TEST.tsv", "a+")
911
+
877 912
         f.write(html_header(2, 'Hour of Week'))
878 913
         f.write('<table>')
879 914
 
@@ -881,7 +916,6 @@ class HTMLReportCreator(ReportCreator):
881 916
         for hour in range(0, 24):
882 917
             f.write('<th>%d</th>' % (hour))
883 918
         f.write('</tr>')
884
-
885 919
         for weekday in range(0, 7):
886 920
             f.write('<tr><th>%s</th>' % (WEEKDAYS[weekday]))
887 921
             for hour in range(0, 24):
@@ -894,8 +928,10 @@ class HTMLReportCreator(ReportCreator):
894 928
                     r = 127 + int((float(commits) / data.activity_by_hour_of_week_busiest) * 128)
895 929
                     f.write(' style="background-color: rgb(%d, 0, 0)"' % r)
896 930
                     f.write('>%d</td>' % commits)
931
+                    hour_of_week_TEST.write("%d\t%d\t%d\n" %(weekday+1,hour,commits))
897 932
                 else:
898 933
                     f.write('<td></td>')
934
+                    hour_of_week_TEST.write("%d\t%d\t%d\n" %(weekday+1,hour,0))
899 935
             f.write('</tr>')
900 936
 
901 937
         f.write('</table>')
@@ -991,29 +1027,6 @@ class HTMLReportCreator(ReportCreator):
991 1027
             f.write('<p class="moreauthors">Only top %d authors shown</p>' % conf['max_authors'])
992 1028
 
993 1029
 
994
-
995
-       def writeHeaders(fileName,headers):
996
-            """
997
-            Writes the headers to the first line of the .tsv file
998
-
999
-            Args:
1000
-                fileName (String): Name of the destination file, ex: "data.tsv"
1001
-                headers (List(String)): Headers to be written, ex: ["header1","header2"....]
1002
-
1003
-            """
1004
-            assert fileName[-4:] ==".tsv", "fileName must be '.tsv' file not '%s'" %(fileName)
1005
-
1006
-            f = open (fileName,"w")
1007
-            for headerIndex in range(len(headers)):
1008
-                if headerIndex!=len(headers)-1:
1009
-                    # write header along with\t 
1010
-                    f.write(headers[headerIndex]+"\t")
1011
-                else:
1012
-                    # write last word along with\n
1013
-                    f.write(headers[len(headers)-1]+"\n")
1014
-            f.close()
1015
-
1016
-        writeHeaders("lines_of_code_by_author.tsv",[''])
1017 1030
         fgl = open(path + '/lines_of_code_by_author.dat', 'w')
1018 1031
         fgc = open(path + '/commits_by_author.dat', 'w')
1019 1032
         
@@ -1036,12 +1049,23 @@ class HTMLReportCreator(ReportCreator):
1036 1049
         for stamp in sorted(data.changes_by_date_by_author.keys()):
1037 1050
             fgl.write('%d' % stamp)
1038 1051
             fgc.write('%d' % stamp)
1052
+
1053
+            hour_of_week_TEST.write("%d\t" %(stamp))
1054
+            commits_by_author_TEST.write("%d\t" %(stamp))
1055
+
1039 1056
             for author in self.authors_to_plot:
1040 1057
                 if author in data.changes_by_date_by_author[stamp].keys():
1041 1058
                     lines_by_authors[author] = data.changes_by_date_by_author[stamp][author]['lines_added']
1042 1059
                     commits_by_authors[author] = data.changes_by_date_by_author[stamp][author]['commits']
1043 1060
                 fgl.write(' %d' % lines_by_authors[author])
1044 1061
                 fgc.write(' %d' % commits_by_authors[author])
1062
+
1063
+                hour_of_week_TEST.write("%d\t" % lines_by_authors[author] )
1064
+                commits_by_author_TEST.write("%d\t" % commits_by_authors[author] )
1065
+
1066
+            hour_of_week_TEST.write("\n")
1067
+            commits_by_author_TEST.write("\n")
1068
+
1045 1069
             fgl.write('\n')
1046 1070
             fgc.write('\n')
1047 1071
         fgl.close()

+ 33
- 0
gitstats-wrapper.py Целия файл

@@ -0,0 +1,33 @@
1
+import libraries
2
+
3
+path = './8956n'
4
+
5
+# Global DataCollector:
6
+class GlobalDataCollector:
7
+    """Manages data collection from the parent revision control repository."""
8
+    def __init__(self):
9
+        self.global_stamp_created = time.time()
10
+        self.global_cache = {}
11
+        self.global_total_authors = 0
12
+        self.global_activity_by_hour_of_day = {} # hour -> commits
13
+        # All other collector definitions
14
+
15
+# Go through each sub directory
16
+directory_content = list_files(path)
17
+cd(path)
18
+for file in directory_content:
19
+    current_path = pwd()
20
+    if type(file) == "directory"
21
+        cd(file)
22
+        if ".git" in current_directory
23
+            data = gitstats()
24
+            get_total_authors(data, global_total_author)
25
+            get_total_line_of_code(data, global_line_of_code)
26
+            ...
27
+    cd(current_path)
28
+    
29
+# Now we should have all the data for 8956n project
30
+cd(current_path)
31
+print 'Generating global report
32
+report = HTMLReportCreator()
33
+report.create(data, outputpath)

+ 20
- 0
test_repository/.gitignore Целия файл

@@ -0,0 +1,20 @@
1
+### NodeJS
2
+# Logs
3
+logs
4
+*.log
5
+npm-debug.log*
6
+
7
+# Dependency directories
8
+node_modules
9
+
10
+### Backups
11
+_BACK
12
+
13
+### Dist (files generated by gulp for production)
14
+dist
15
+
16
+### Intellij specific files/folders
17
+
18
+.idea
19
+*.iml
20
+*nohup.out

+ 6
- 0
test_repository/app/.gitignore Целия файл

@@ -0,0 +1,6 @@
1
+### Bower
2
+bower_components
3
+
4
+### Notes / Scratchpads
5
+notes
6
+scratchpad

+ 167
- 0
test_repository/app/admin/admin-shell.html Целия файл

@@ -0,0 +1,167 @@
1
+<dom-module id="admin-shell">
2
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
3
+    <style>
4
+        app-header {
5
+            background-color: var(--zebra-dark-gray);
6
+        }
7
+
8
+        app-toolbar {
9
+            color: white;
10
+        }
11
+
12
+        .header {
13
+            height: 64px;
14
+            background-color: var(--zebra-dark-gray);
15
+        }
16
+
17
+        .separator {
18
+            font-size: 14px;
19
+            font-weight: 500;
20
+            color: #aaa;
21
+            border-top: 1px solid #e0e0e0;
22
+        }
23
+
24
+        .admin-header {
25
+            height: 64px;
26
+            background-color: #ebebeb;
27
+        }
28
+
29
+        iron-pages {
30
+            height: 100%;
31
+        }
32
+
33
+        .default-content {
34
+            margin: 8px;
35
+            padding: 8px;
36
+
37
+            background-color: white;
38
+        }
39
+
40
+        .login-container {
41
+            margin: 0;
42
+            width: 100%;
43
+            height: 100%;
44
+        }
45
+    </style>
46
+
47
+    <template>
48
+        <template is="dom-if" if="{{adminExists()}}" restamp="true">
49
+            <app-drawer-layout fullbleed>
50
+                <app-drawer>
51
+                    <div class="header"></div>
52
+
53
+                    <div class="admin-header"></div>
54
+
55
+                    <paper-menu selected="{{selected}}" attr-for-selected="item">
56
+                        <paper-item class="first-item" item="home">Home</paper-item>
57
+
58
+                        <paper-item class="separator" disabled="true">Markdown</paper-item>
59
+                        <paper-item item="markdown-editor-add">Add Item</paper-item>
60
+                        <paper-item item="markdown-editor-update">Update Item</paper-item>
61
+                        <paper-item item="markdown-editor-delete">Delete Item</paper-item>
62
+
63
+                        <paper-item class="separator" disabled="true">Tool Operations</paper-item>
64
+                        <paper-item item="add-tool">Add Tool</paper-item>
65
+                        <paper-item item="update-tool">Update Tool</paper-item>
66
+                        <paper-item item="remove-tool">Remove Tool</paper-item>
67
+                    </paper-menu>
68
+                </app-drawer>
69
+                <app-header-layout fullbleed>
70
+                    <app-header>
71
+                        <app-toolbar>
72
+                            <paper-icon-button icon="menu" drawer-toggle></paper-icon-button>
73
+                            <div main-title>Admin Console</div>
74
+                        </app-toolbar>
75
+                    </app-header>
76
+
77
+                    <iron-pages selected="{{selected}}" attr-for-selected="item">
78
+                        <paper-material class="default-content" item="home">
79
+                            TEST
80
+                        </paper-material>
81
+
82
+                        <!-- Markdown Subsection -->
83
+                        <admin-markdown-add item="markdown-editor-add"></admin-markdown-add>
84
+
85
+                        <paper-material class="default-content" item="markdown-editor-update">
86
+                            Update Item
87
+                        </paper-material>
88
+
89
+                        <admin-markdown-delete item="markdown-editor-delete"></admin-markdown-delete>
90
+
91
+                        <!-- Tool Subsection -->
92
+                        <admin-add-tool-form item="add-tool"></admin-add-tool-form>
93
+
94
+                        <paper-material class="default-content" item="update-tool">
95
+                            Update Tool
96
+                        </paper-material>
97
+
98
+                        <admin-remove-tool-form item="remove-tool"></admin-remove-tool-form>
99
+                    </iron-pages>
100
+
101
+                </app-header-layout>
102
+            </app-drawer-layout>
103
+        </template>
104
+
105
+        <div class="login-container">
106
+            <template is="dom-if" if="{{!adminExists()}}">
107
+                <admin-login id="login" on-admin-login-success="_handleLogin" opened></admin-login>
108
+            </template>
109
+        </div>
110
+    </template>
111
+    <script>
112
+        Polymer({
113
+            is: 'admin-shell',
114
+
115
+            properties: {
116
+                /**
117
+                 * The logged in state of the user
118
+                 */
119
+                loggedIn: {
120
+                    type: Boolean,
121
+                    value: false
122
+                },
123
+
124
+                /**
125
+                 * The currently selected item
126
+                 */
127
+                selected: {
128
+                    type: Number,
129
+                    value: 0
130
+                },
131
+            },
132
+
133
+            attached: function () {
134
+
135
+            },
136
+
137
+            /**
138
+             * Helper function for checking if an admin exists (i.e. is a token in sessionStorage)
139
+             * @returns {boolean} Whether or not an admin is logged in
140
+             */
141
+            adminExists: function () {
142
+                var adminExists = false;
143
+
144
+                //Check token
145
+                if (sessionStorage.token) {
146
+                    adminExists = true;
147
+                }
148
+
149
+                return adminExists;
150
+            },
151
+
152
+            /**
153
+             * Helper function for handling the login from admin-login
154
+             * @param e The event that admin-login fires
155
+             * @param detail The details from the event
156
+             * @private
157
+             */
158
+            _handleLogin: function (e, detail) {
159
+                token = detail.token;
160
+
161
+                sessionStorage.setItem('token', token);
162
+
163
+                location.reload(true);
164
+            }
165
+        });
166
+    </script>
167
+</dom-module>

+ 28
- 0
test_repository/app/admin/index.html Целия файл

@@ -0,0 +1,28 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <title>Zebra EMC Engineering Tools Portal : Admin Console</title>
5
+
6
+    <link rel="import" href="../elements/elements.html">
7
+    <link rel="import" href="admin-shell.html">
8
+
9
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
10
+    <style is="custom-style">
11
+        * {
12
+            font-family: Arial, Helvetica, sans-serif;
13
+        }
14
+
15
+        body {
16
+            margin: 0;
17
+
18
+            background-color: lightgray;
19
+        }
20
+    </style>
21
+
22
+    <script src="../scripts/js-cookie.js" type="text/javascript"></script>
23
+</head>
24
+
25
+<body class="fullbleed">
26
+<admin-shell id="t"></admin-shell>
27
+</body>
28
+</html>

+ 294
- 0
test_repository/app/app-shell.html Целия файл

@@ -0,0 +1,294 @@
1
+<link rel="import" href="elements/elements.html">
2
+
3
+<dom-module id="app-shell">
4
+    <style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning"></style>
5
+    <style>
6
+        .yellow {
7
+            background-color: var(--zebra-yellow);
8
+        }
9
+
10
+        .title {
11
+            text-align: center;
12
+        }
13
+
14
+        .black {
15
+            background-color: var(--zebra-black);
16
+        }
17
+
18
+        .tabs-container {
19
+            /*width: 15%;*/
20
+        }
21
+
22
+        .page-tabs {
23
+            height: 32px;
24
+
25
+            color: var(--zebra-yellow);
26
+            background-color: var(--zebra-black);
27
+
28
+            --paper-tabs-selection-bar-color: var(--zebra-yellow);
29
+        }
30
+
31
+        .container {
32
+            background-color: white;
33
+        }
34
+
35
+        .pages {
36
+            margin: 8px 16px;
37
+        }
38
+
39
+        .name {
40
+            font-size: 14px;
41
+        }
42
+
43
+        .avatar {
44
+            vertical-align: middle;
45
+        }
46
+    </style>
47
+
48
+    <template>
49
+        <app-header-layout has-scrolling-region fullbleed>
50
+            <app-header class="yellow" fixed>
51
+                <!-- First Row -->
52
+                <app-toolbar>
53
+                    <img class="" src="images/zebra_logo_64.png">
54
+
55
+                    <div class="title" main-title>{{title}}</div>
56
+
57
+                    <div hidden="{{loggedIn}}">
58
+                        <paper-icon-button icon="account-box" on-tap="_openLoginDialog"></paper-icon-button>
59
+                    </div>
60
+
61
+                    <div hidden="{{!loggedIn}}">
62
+                        <!--<span class="name">{{displayName}}</span>-->
63
+                        <iron-image id="avatar" class="avatar" src="{{avatarLink}}"
64
+                                    on-tap="_openNameDialog"></iron-image>
65
+                        <paper-icon-button id="logout" icon="close" on-tap="_logout"></paper-icon-button>
66
+                    </div>
67
+                </app-toolbar>
68
+
69
+                <!-- Second Row -->
70
+                <div class="black">
71
+                    <div class="tabs-container">
72
+                        <paper-tabs class="page-tabs" selected="{{selected}}" sticky>
73
+                            <paper-tab>Home</paper-tab>
74
+                            <paper-tab>News</paper-tab>
75
+                            <paper-tab>Forms</paper-tab>
76
+                            <paper-tab>Tools</paper-tab>
77
+                        </paper-tabs>
78
+                    </div>
79
+                </div>
80
+            </app-header>
81
+
82
+            <div class="main-content">
83
+                <iron-pages class="pages" selected="{{selected}}">
84
+                    <!-- Home -->
85
+                    <portal-home></portal-home>
86
+
87
+                    <!-- News -->
88
+                    <portal-news-list></portal-news-list>
89
+
90
+                    <!-- Forms -->
91
+                    <portal-forms logged-in="{{loggedIn}}"></portal-forms>
92
+
93
+                    <!-- Tools -->
94
+                    <portal-tools></portal-tools>
95
+                </iron-pages>
96
+            </div>
97
+        </app-header-layout>
98
+
99
+        <jira-login-dialog id="loginDialog"></jira-login-dialog>
100
+
101
+        <paper-dialog id="nameDialog" no-overlap horizontal-align="right" vertical-align="auto">
102
+            <h4>Currently signed in as:</h4>
103
+            <p>{{user.displayName}} ({{user.username}}@zebra.com)</p>
104
+        </paper-dialog>
105
+    </template>
106
+    <script>
107
+        Polymer({
108
+            is: 'app-shell',
109
+
110
+            properties: {
111
+                /**
112
+                 * The URL to the avatar of the user.
113
+                 */
114
+                avatarLink: {
115
+                    type: String
116
+                },
117
+
118
+                /**
119
+                 * The display name (i.e. full name) of the user.
120
+                 */
121
+                displayName: {
122
+                    type: String
123
+                },
124
+
125
+                /**
126
+                 * Logged in state of the user.
127
+                 * Has an observer to watch for changes.
128
+                 * Will not show user details if the user is not logged in.
129
+                 */
130
+                loggedIn: {
131
+                    type: Boolean,
132
+                    value: false,
133
+                    observer: '_loggedIn'
134
+                },
135
+
136
+                /**
137
+                 * The currently selected 'page'.
138
+                 * Defaults to the 'Home' page (0).
139
+                 */
140
+                selected: {
141
+                    type: Number,
142
+                    value: 0
143
+                },
144
+
145
+                /**
146
+                 * The title that appears in the toolbar at the top of the page.
147
+                 */
148
+                title: {
149
+                    type: String,
150
+                    value: "EMC Engineering Tools Portal"
151
+                },
152
+
153
+                user: {
154
+                    type: Object,
155
+                    value: {}
156
+                }
157
+            },
158
+
159
+            attached: function () {
160
+                var me = this;
161
+
162
+                //Check if the user is logged in by checking the cookie.
163
+                me.loggedIn = me._checkCookie();
164
+
165
+                //If the cookie is invalid, delete it
166
+                if (me.loggedIn == false)
167
+                    me._deleteCookie();
168
+
169
+                //Setup a listener for login events
170
+                //<jira-login-dialog> will fire a 'loggedIn' event when the user logs in
171
+                me.$.loginDialog.addEventListener('loggedIn', function (event) {
172
+                    me.loggedIn = event.detail.loggedIn;
173
+                });
174
+            },
175
+
176
+            /**
177
+             * Function for retrieving the details of the currently signed in user, such as avatar and display name.
178
+             *
179
+             * @param username The username of the user to retrieve details for.
180
+             */
181
+            getUserDetails: function (username) {
182
+                var me = this;
183
+
184
+                var ajax = document.createElement('iron-ajax');
185
+                ajax.auto = true;
186
+                ajax.method = "GET";
187
+                ajax.url = "/api/jira/user/";
188
+                ajax.params = {cookie: Cookies.get("JSESSIONID"), username: username};
189
+
190
+                ajax.addEventListener("response", function (e) {
191
+                    var response = e.detail.response;
192
+                    me.avatarLink = response.avatar;
193
+                    me.displayName = response.displayName;
194
+                    me.set('user.displayName', response.displayName);
195
+                    me.set('user.username', username);
196
+                });
197
+            },
198
+
199
+            /**
200
+             * Function for retrieving the username of the user.
201
+             * Uses the current session cookie to get the current user's username.
202
+             * Then, calls helper method to get user details.
203
+             *
204
+             * @see {@link getUserDetails} for the actual details retrieval.
205
+             */
206
+            getUser: function () {
207
+                var me = this;
208
+
209
+                var ajax = document.createElement('iron-ajax');
210
+                ajax.auto = true;
211
+                ajax.method = "GET";
212
+                ajax.url = "/api/jira/login/";
213
+                ajax.params = {cookie: Cookies.get("JSESSIONID")};
214
+
215
+                ajax.addEventListener("response", function (e) {
216
+                    //Call helper method now that we know the username
217
+                    me.getUserDetails(e.detail.response.name);
218
+                });
219
+            },
220
+
221
+            /**
222
+             * Helper method for checking the validity of the cookie.
223
+             * @returns {boolean} The validity of the cookie.
224
+             * @private
225
+             */
226
+            _checkCookie: function () {
227
+                if (typeof Cookies.get("JSESSIONID") === typeof undefined)
228
+                    return false;
229
+                else
230
+                    return true;
231
+            },
232
+
233
+            /**
234
+             * Helper method for deleting the JIRA session cookie.
235
+             * Uses js-cookie.
236
+             * @private
237
+             */
238
+            _deleteCookie: function () {
239
+                Cookies.remove('JSESSIONID');
240
+            },
241
+
242
+            /**
243
+             * Helper method for opening the JIRA login dialog.
244
+             * @private
245
+             */
246
+            _openLoginDialog: function () {
247
+                this.$.loginDialog.open();
248
+            },
249
+
250
+            /**
251
+             * Helper method for opening the name dialog.
252
+             * @private
253
+             */
254
+            _openNameDialog: function () {
255
+                this.$.nameDialog.positionTarget = this.$.avatar;
256
+                this.$.nameDialog.open();
257
+            },
258
+
259
+            /**
260
+             * Observer function tied to loggedIn prop.
261
+             * When the user logs in, the prop is updated to 'true' and the user detail retrieval process begins.
262
+             * @private
263
+             */
264
+            _loggedIn: function () {
265
+                var me = this;
266
+
267
+                if (me.loggedIn == true)
268
+                    me.getUser();
269
+            },
270
+
271
+            /**
272
+             * Helper method for logging out the user.
273
+             * Simply sends a request to the server to relay a logout to JIRA.
274
+             * Once complete, deletes the session cookie and sets the loggedIn state to false.
275
+             * @private
276
+             */
277
+            _logout: function () {
278
+                var me = this;
279
+
280
+                var ajax = document.createElement('iron-ajax');
281
+                ajax.auto = true;
282
+                ajax.method = "DELETE";
283
+                ajax.url = "/api/jira/login";
284
+                ajax.params = {cookie: Cookies.get('JSESSIONID')};
285
+
286
+                //If there were no HTTP errors encountered, the logout was successful
287
+                ajax.addEventListener("response", function (e) {
288
+                    me.loggedIn = false;
289
+                    me._deleteCookie();
290
+                });
291
+            }
292
+        });
293
+    </script>
294
+</dom-module>

+ 46
- 0
test_repository/app/bower.json Целия файл

@@ -0,0 +1,46 @@
1
+{
2
+  "name": "zebra_emc_tools_portal",
3
+  "authors": [
4
+    "Kyle Crowley <nfg84@zebra.com>"
5
+  ],
6
+  "description": "Zebra EMC Engineering Tools Portal",
7
+  "main": "index.html",
8
+  "license": "UNLICENSED",
9
+  "homepage": "",
10
+  "private": true,
11
+  "ignore": [
12
+    "**/.*",
13
+    "node_modules",
14
+    "bower_components",
15
+    "test",
16
+    "tests"
17
+  ],
18
+  "dependencies": {
19
+    "app-layout": "PolymerElements/app-layout#^0.10.1",
20
+    "paper-icon-button": "PolymerElements/paper-icon-button#^1.1.2",
21
+    "paper-item": "PolymerElements/paper-item#^1.2.1",
22
+    "paper-material": "PolymerElements/paper-material#^1.0.6",
23
+    "paper-menu": "PolymerElements/paper-menu#^1.2.2",
24
+    "paper-styles": "PolymerElements/paper-styles#^1.1.4",
25
+    "paper-tabs": "PolymerElements/paper-tabs#^1.6.2",
26
+    "paper-toast": "PolymerElements/paper-toast#^1.3.0",
27
+    "iron-ajax": "PolymerElements/iron-ajax#^1.4.3",
28
+    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.3.1",
29
+    "iron-icons": "PolymerElements/iron-icons#^1.1.3",
30
+    "iron-pages": "PolymerElements/iron-pages#^1.0.8",
31
+    "paper-dialog": "PolymerElements/paper-dialog#^1.1.0",
32
+    "marked-element": "PolymerElements/marked-element#^1.1.3",
33
+    "paper-toolbar": "PolymerElements/paper-toolbar#^1.1.6",
34
+    "paper-header-panel": "PolymerElements/paper-header-panel#^1.1.6",
35
+    "paper-button": "PolymerElements/paper-button#^1.0.12",
36
+    "paper-input": "PolymerElements/paper-input#^1.1.15",
37
+    "iron-form": "PolymerElements/iron-form#^1.1.1",
38
+    "paper-checkbox": "PolymerElements/paper-checkbox#^1.3.0",
39
+    "paper-spinner": "PolymerElements/paper-spinner#^1.2.0",
40
+    "app-route": "PolymerElements/app-route#^0.9.2",
41
+    "vaadin-combo-box": "^1.1.2",
42
+    "paper-scroll-header-panel": "PolymerElements/paper-scroll-header-panel#^1.0.16",
43
+    "paper-card": "PolymerElements/paper-card#^1.1.2",
44
+    "paper-drawer-panel": "PolymerElements/paper-drawer-panel#^1.0.10"
45
+  }
46
+}

+ 147
- 0
test_repository/app/elements/admin/admin-add-tool-form.html Целия файл

@@ -0,0 +1,147 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-form/iron-form.html">
3
+<link rel="import" href="../../bower_components/paper-button/paper-button.html">
4
+<link rel="import" href="../../bower_components/paper-input/paper-input.html">
5
+<link rel="import" href="../../bower_components/paper-material/paper-material.html">
6
+<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
7
+
8
+<dom-module id="admin-add-tool-form">
9
+    <template>
10
+        <style is="custom-style">
11
+            .container {
12
+                margin: 16px;
13
+                padding: 16px;
14
+
15
+                background-color: white;
16
+            }
17
+
18
+            .button {
19
+                margin-top: 16px;
20
+            }
21
+
22
+            .login {
23
+                color: white;
24
+                background-color: var(--paper-blue-300);
25
+            }
26
+
27
+            .login[disabled] {
28
+                background-color: var(--paper-blue-100);
29
+            }
30
+
31
+            .cancel {
32
+                color: white;
33
+                background-color: var(--paper-red-300);
34
+            }
35
+
36
+            .toast {
37
+
38
+            }
39
+
40
+            .success {
41
+                background: var(--paper-green-300);
42
+                border: 1px solid var(--paper-green-500);
43
+            }
44
+
45
+            .failure {
46
+                background: var(--paper-red-300);
47
+                border: 1px solid var(--paper-red-500);
48
+            }
49
+        </style>
50
+
51
+        <paper-material id="container" class="container">
52
+            <form id="addToolForm" is="iron-form" method="post" action="/api/tool/guarded">
53
+                <paper-input id="toolId" label="Tool Id" type="number" required auto-validate></paper-input>
54
+                <paper-input id="toolName" label="Tool Name" type="text" required auto-validate></paper-input>
55
+
56
+                <paper-button id="submitButton" class="button login" raised disabled>Add Tool</paper-button>
57
+                <paper-button id="cancelButton" class="button cancel" raised>Cancel</paper-button>
58
+            </form>
59
+        </paper-material>
60
+
61
+        <paper-toast id="successToast" class="toast success" text="{{successText}}"></paper-toast>
62
+        <paper-toast id="failureToast" class="toast failure" text="{{failureText}}"></paper-toast>
63
+    </template>
64
+
65
+    <script>
66
+        Polymer({
67
+
68
+            is: 'admin-add-tool-form',
69
+
70
+            properties: {
71
+                /**
72
+                 * Message to display on the failure toast
73
+                 */
74
+                failureText: {
75
+                    type: String,
76
+                    value: "Failed to add tool!"
77
+                },
78
+
79
+                /**
80
+                 * Messsage to display on the success toast
81
+                 */
82
+                successText: {
83
+                    type: String,
84
+                    value: "Added Tool!"
85
+                }
86
+            },
87
+
88
+            //Polymer lifecycle method when element is attached to the DOM
89
+            attached: function () {
90
+                var me = this;
91
+
92
+                var addToolForm = me.$.addToolForm;
93
+                var toolId = me.$.toolId;
94
+                var toolName = me.$.toolName;
95
+                var submitButton = me.$.submitButton;
96
+                var cancelButton = me.$.cancelButton;
97
+
98
+                var successToast = me.$.successToast;
99
+                successToast.fitInto = me.parentElement;
100
+
101
+                var failureToast = me.$.failureToast;
102
+                failureToast.fitInto = me.parentElement;
103
+
104
+                submitButton.addEventListener('tap', function () {
105
+                    addToolForm.submit();
106
+                });
107
+
108
+                addToolForm.addEventListener('change', function (event) {
109
+                    // Validate the entire form to see if we should enable the `Submit` button.
110
+                    submitButton.disabled = !addToolForm.validate();
111
+                });
112
+
113
+                //Event that fires before the form is submitted. Adds the inputs to the body of the AJAX request.
114
+                addToolForm.addEventListener('iron-form-presubmit', function (event) {
115
+                    this.request.method = "POST";
116
+                    this.request.contentType = 'application/json';
117
+                    this.request.body = {
118
+                        token: sessionStorage.getItem('token'),
119
+                        toolId: toolId.value,
120
+                        toolName: toolName.value
121
+                    };
122
+                });
123
+
124
+                //Event that fires if an error is encountered
125
+                addToolForm.addEventListener('iron-form-error', function (event) {
126
+                    failureToast.open();
127
+                });
128
+
129
+                //Event the fires when a response is received
130
+                addToolForm.addEventListener('iron-form-response', function (event) {
131
+                    var response = event.detail.response;
132
+
133
+                    //Check that the document was actually added.
134
+                    //This *shouldn't* be required, but just check anyways
135
+                    if (response.created == true) {
136
+                        successToast.open();
137
+                    }
138
+
139
+                    else {
140
+                        console.log("HTTP 201 received but document wasn't added... ");
141
+                        console.log(response);
142
+                    }
143
+                });
144
+            },
145
+        });
146
+    </script>
147
+</dom-module>

+ 165
- 0
test_repository/app/elements/admin/admin-login.html Целия файл

@@ -0,0 +1,165 @@
1
+<dom-module id="admin-login">
2
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
3
+    <style>
4
+        paper-card {
5
+            display: block;
6
+
7
+            margin-top: 32px;
8
+            margin-left: auto;
9
+            margin-right: auto;
10
+
11
+            max-width: 500px;
12
+        }
13
+
14
+        .msg {
15
+            padding: 10px 20px 9px;
16
+            margin-bottom: 4px;
17
+        }
18
+
19
+        .msg-error {
20
+            background: var(--paper-red-100);
21
+            border-left: 3px solid var(--paper-red-500);
22
+            color: var(--paper-red-900);
23
+        }
24
+
25
+        .btn {
26
+            color: white;
27
+        }
28
+
29
+        .btn-primary {
30
+            background-color: var(--paper-blue-300);
31
+        }
32
+    </style>
33
+
34
+    <template>
35
+        <paper-card heading="Admin Login">
36
+            <div class="card-content">
37
+                <div class="layout horizontal center-justified">
38
+                    <paper-spinner id="spinner"></paper-spinner>
39
+                </div>
40
+
41
+                <paper-material class="msg msg-error" hidden="{{!loginError}}">{{errorMessage}}</paper-material>
42
+
43
+                <form id="form" class="layout vertical" is="iron-form" method="post" action="/api/admin/login">
44
+                    <paper-input id="username" label="Username (Portal Creds)" type="text" required></paper-input>
45
+                    <paper-input id="password" label="Password" type="password" required></paper-input>
46
+
47
+                    <paper-button id="submitButton" class="btn btn-primary" raised on-tap="_submit">Login</paper-button>
48
+                </form>
49
+            </div>
50
+        </paper-card>
51
+    </template>
52
+    <script>
53
+        Polymer({
54
+            is: 'admin-login',
55
+
56
+            properties: {
57
+                /**
58
+                 * Error message to display on the failure toast
59
+                 */
60
+                errorMessage: {
61
+                    type: String
62
+                },
63
+
64
+                /**
65
+                 * Whether or not the request is in flight
66
+                 */
67
+                inFlight: {
68
+                    type: Boolean,
69
+                    value: false,
70
+                    observer: '_inFlight'
71
+                },
72
+
73
+                /**
74
+                 * Whether or not there was a login error
75
+                 */
76
+                loginError: {
77
+                    type: Boolean,
78
+                    value: false
79
+                }
80
+            },
81
+
82
+            attached: function () {
83
+                var me = this;
84
+
85
+                var form = me.$.form;
86
+                var usernameInput = me.$.username;
87
+                var passwordInput = me.$.password;
88
+
89
+                //Grab the inputs from the form just before the form is submitted
90
+                form.addEventListener('iron-form-presubmit', function (event) {
91
+                    this.request.contentType = 'application/json';
92
+                    this.request.body = {username: usernameInput.value, password: passwordInput.value};
93
+                });
94
+
95
+                //When the form is submitted, mark it as in flight
96
+                form.addEventListener('iron-form-submit', function (event) {
97
+                    me.inFlight = true;
98
+                });
99
+
100
+                //On HTTP error, mark an error and set the error message
101
+                form.addEventListener('iron-form-error', function (event) {
102
+                    me.inFlight = false;
103
+                    me.errorMessage = event.detail.request.response.errors;
104
+                    me.loginError = true;
105
+                });
106
+
107
+                //When a valid response is recv
108
+                form.addEventListener('iron-form-response', function (event) {
109
+                    me.inFlight = false;
110
+
111
+                    var response = event.detail.response;
112
+
113
+                    //Fire an event so listeners know that a response was recv and was successful
114
+                    me.fire('admin-login-success', {token: response.token});
115
+                });
116
+            },
117
+
118
+            /**
119
+             * Helper method for disabling the form
120
+             * @private
121
+             */
122
+            _disableForm: function () {
123
+                this.$.username.disabled = true;
124
+                this.$.password.disabled = true;
125
+                this.$.submitButton.disabled = true;
126
+            },
127
+
128
+            /**
129
+             * Helper method for enabling the form
130
+             * @private
131
+             */
132
+            _enableForm: function () {
133
+                this.$.username.disabled = false;
134
+                this.$.password.disabled = false;
135
+                this.$.submitButton.disabled = false;
136
+            },
137
+
138
+            /**
139
+             * Observer method for when the request is in flight or not.
140
+             * If it is, form is disabled and spinner is active.
141
+             * Otherwise, form is enabled and spinner is disabled.
142
+             * @private
143
+             */
144
+            _inFlight: function () {
145
+                if (this.inFlight == true) {
146
+                    this.$.spinner.active = true;
147
+                    this._disableForm();
148
+                }
149
+
150
+                else {
151
+                    this.$.spinner.active = false;
152
+                    this._enableForm();
153
+                }
154
+            },
155
+
156
+            /**
157
+             * Helper method for submitting the form through the native submit()
158
+             * @private
159
+             */
160
+            _submit: function () {
161
+                this.$.form.submit();
162
+            }
163
+        });
164
+    </script>
165
+</dom-module>

+ 101
- 0
test_repository/app/elements/admin/admin-remove-tool-form.html Целия файл

@@ -0,0 +1,101 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-form/iron-form.html">
3
+<link rel="import" href="../../bower_components/paper-button/paper-button.html">
4
+<link rel="import" href="../../bower_components/paper-input/paper-input.html">
5
+<link rel="import" href="../../bower_components/paper-material/paper-material.html">
6
+<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
7
+
8
+<dom-module id="admin-remove-tool-form">
9
+    <template>
10
+        <style is="custom-style">
11
+            .container {
12
+                margin: 16px;
13
+                padding: 16px;
14
+
15
+                background-color: white;
16
+            }
17
+
18
+            .button {
19
+                margin-top: 16px;
20
+            }
21
+
22
+            .delete {
23
+                color: white;
24
+                background-color: var(--paper-yellow-300);
25
+            }
26
+
27
+            .delete[disabled] {
28
+                background-color: var(--paper-yellow-100);
29
+            }
30
+
31
+            .cancel {
32
+                color: white;
33
+                background-color: var(--paper-red-300);
34
+            }
35
+        </style>
36
+
37
+        <paper-material id="container" class="container">
38
+            <form id="removeToolForm" is="iron-form">
39
+                <paper-input id="toolId" label="Tool Id" type="number" required auto-validate></paper-input>
40
+
41
+                <paper-button id="submitButton" class="button delete" raised disabled>Remove Tool</paper-button>
42
+                <paper-button id="cancelButton" class="button cancel" raised>Cancel</paper-button>
43
+            </form>
44
+        </paper-material>
45
+
46
+        <paper-toast id="successToast" text="Removed Tool!"></paper-toast>
47
+    </template>
48
+
49
+    <script>
50
+        Polymer({
51
+
52
+            is: 'admin-remove-tool-form',
53
+
54
+            properties: {},
55
+
56
+            //Polymer lifecycle method when element is attached to the DOM
57
+            attached: function () {
58
+                var me = this;
59
+
60
+                var removeToolForm = me.$.removeToolForm;
61
+                var toolId = me.$.toolId;
62
+                var toolName = me.$.toolName;
63
+                var submitButton = me.$.submitButton;
64
+                var cancelButton = me.$.cancelButton;
65
+
66
+                var successToast = me.$.successToast;
67
+                successToast.fitInto = me.parentNode;
68
+
69
+                submitButton.addEventListener('tap', function () {
70
+                    removeToolForm.submit();
71
+                });
72
+
73
+                removeToolForm.addEventListener('change', function (event) {
74
+                    // Validate the entire form to see if we should enable the `Submit` button.
75
+                    submitButton.disabled = !removeToolForm.validate();
76
+                });
77
+
78
+                //Event that fires before the form is submitted. Adds the inputs to the body of the AJAX request.
79
+                removeToolForm.addEventListener('iron-form-presubmit', function (event) {
80
+                    this.request.method = "DELETE";
81
+                    this.request.url = "/api/tool/guarded/" + toolId.value;
82
+                    this.request.params = {
83
+                        token: sessionStorage.getItem('token'),
84
+                    };
85
+                });
86
+
87
+                removeToolForm.addEventListener('iron-form-error', function (event) {
88
+
89
+                });
90
+
91
+                //Event the fires when a response is received
92
+                removeToolForm.addEventListener('iron-form-response', function (event) {
93
+                    var response = event.detail.response;
94
+
95
+                    if (response.deleted == true)
96
+                        successToast.open();
97
+                });
98
+            }
99
+        });
100
+    </script>
101
+</dom-module>

+ 215
- 0
test_repository/app/elements/admin/markdown/admin-markdown-add.html Целия файл

@@ -0,0 +1,215 @@
1
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
3
+<link rel="import" href="../../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
4
+<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
5
+<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
6
+<link rel="import" href="../../../bower_components/paper-material/paper-material.html">
7
+<link rel="import" href="../../../bower_components/paper-toast/paper-toast.html">
8
+<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
9
+<link rel="import" href="../../markdown-editor.html">
10
+
11
+<dom-module id="admin-markdown-add">
12
+    <template>
13
+        <style is="custom-style" include="iron-flex iron-positioning"></style>
14
+        <style>
15
+            paper-material {
16
+                padding: 8px;
17
+                margin: 8px;
18
+
19
+                background-color: white;
20
+            }
21
+
22
+            markdown-editor {
23
+                background-color: white;
24
+            }
25
+
26
+            .separator {
27
+                margin: 0 4px;
28
+            }
29
+
30
+            .button {
31
+                color: white;
32
+                background-color: var(--paper-blue-300);
33
+            }
34
+
35
+            .markdown-container {
36
+                margin: 8px;
37
+            }
38
+
39
+            .toast {
40
+
41
+            }
42
+
43
+            .success {
44
+                background: var(--paper-green-300);
45
+                border: 1px solid var(--paper-green-500);
46
+            }
47
+
48
+            .failure {
49
+                background: var(--paper-red-300);
50
+                border: 1px solid var(--paper-red-500);
51
+            }
52
+        </style>
53
+
54
+        <paper-material class="header">
55
+            This form adds a new item to the markdown list
56
+        </paper-material>
57
+
58
+        <paper-material class="options horizontal layout">
59
+            <vaadin-combo-box id="toolList" label="Tool" class="flex" items="[[tools]]">
60
+            </vaadin-combo-box>
61
+
62
+            <div class="separator"></div>
63
+
64
+            <vaadin-combo-box id="sectionList" label="Sections" class="flex"
65
+                              items="{{sectionNames}}" value="About">
66
+            </vaadin-combo-box>
67
+
68
+            <paper-button id="save" class="button primary flex" on-tap="_addMarkdown" raised>ADD</paper-button>
69
+        </paper-material>
70
+
71
+        <paper-material class="horizontal layout">
72
+            <paper-input id="heading" class="flex" label="Heading" type="text"></paper-input>
73
+        </paper-material>
74
+
75
+        <div class="markdown-container vertical layout">
76
+            <markdown-editor id="editor"></markdown-editor>
77
+        </div>
78
+
79
+        <paper-toast id="successToast" class="toast success" text="{{message.success}}"></paper-toast>
80
+        <paper-toast id="failureToast" class="toast failure" text="{{message.failure}}"></paper-toast>
81
+    </template>
82
+    <script>
83
+        Polymer({
84
+            is: 'admin-markdown-add',
85
+
86
+            properties: {
87
+                /**
88
+                 * Object for success/failure messages
89
+                 */
90
+                message: {
91
+                    type: Object,
92
+                    value: {
93
+                        failure: "Failed to add tool!",
94
+                        success: "Markdown added successfully!"
95
+                    }
96
+                },
97
+
98
+                /**
99
+                 * Names of the possible sections to add to
100
+                 */
101
+                sectionNames: {
102
+                    type: Array,
103
+                    value: ['About', 'Training', 'Links']
104
+                },
105
+
106
+                /**
107
+                 * Array of tool names
108
+                 */
109
+                tools: {
110
+                    type: Array,
111
+                }
112
+            },
113
+
114
+            ready: function () {
115
+                var me = this;
116
+
117
+                //Tell the paper-toast's to fit into the parent element
118
+                me.$.successToast.fitInto = me.parentElement;
119
+                me.$.failureToast.fitInto = me.parentElement;
120
+
121
+                //Load in the tools to populate the <vaadin-combo-box>
122
+                me.tools = me._getTools();
123
+            },
124
+
125
+            /**
126
+             * Helper function for getting the tools to populate the tool list
127
+             * @private
128
+             */
129
+            _getTools: function () {
130
+                var me = this;
131
+
132
+                var tools_ajax = document.createElement('iron-ajax');
133
+                tools_ajax.auto = true;
134
+                tools_ajax.method = "GET";
135
+                tools_ajax.url = "/api/tools/info";
136
+
137
+                tools_ajax.addEventListener("response", function (e) {
138
+                    //Grab the tools from the response
139
+                    var tools = e.detail.response;
140
+
141
+                    //We need to transform the array to fit to <vaadin-combo-box> requirements
142
+                    me.tools = tools.map(function (obj) {
143
+                        return rObj = {
144
+                            value: obj.id,
145
+                            label: obj.name
146
+                        };
147
+                    });
148
+                });
149
+            },
150
+
151
+            /**
152
+             * Helper method for adding the markdown to the section
153
+             * @private
154
+             */
155
+            _addMarkdown: function () {
156
+                var me = this;
157
+
158
+                var toolListVal = me.$.toolList.value;
159
+                var sectionListVal = me.$.sectionList.value;
160
+
161
+                var successToast = me.$.successToast;
162
+                var failureToast = me.$.failureToast;
163
+
164
+                //Check that the values were supplied
165
+                if (toolListVal && sectionListVal) {
166
+                    var heading = me.$.heading.value;
167
+                    var contents = me.$.editor.markdown;
168
+
169
+                    //Check that the values were supplied
170
+                    if (heading && contents) {
171
+                        //Get the token for authentication
172
+                        var token = sessionStorage.getItem('token');
173
+
174
+                        var ajax = document.createElement('iron-ajax');
175
+                        ajax.auto = true;
176
+                        ajax.body = {token: token, heading: heading, contents: contents};
177
+                        ajax.contentType = 'application/json';
178
+                        ajax.method = "POST";
179
+                        ajax.url = "/api/tool/guarded/" + toolListVal + "/" + sectionListVal.toLowerCase();
180
+
181
+                        //When there is an error (HTTP 4XX)
182
+                        ajax.addEventListener('error', function (event) {
183
+                            me.set('message.failure', "Failed to add markdown...");
184
+                            failureToast.open();
185
+                        });
186
+
187
+                        ajax.addEventListener('response', function (event) {
188
+                            successToast.open();
189
+                        });
190
+                    }
191
+
192
+                    //Missing heading or contents
193
+                    else {
194
+                        if (!heading)
195
+                            me.set('message.failure', "Missing heading. Make sure you enter a heading.");
196
+                        else
197
+                            me.set('message.failure', "Missing contents. Make sure you entered some markdown.");
198
+
199
+                        failureToast.open();
200
+                    }
201
+                }
202
+
203
+                //Missing Tool or Section
204
+                else {
205
+                    if (!toolListVal)
206
+                        me.set('message.failure', "Missing tool. Make sure you selected a tool.");
207
+                    else
208
+                        me.set('message.failure', "Missing section. Make sure you selected a section.");
209
+
210
+                    failureToast.open();
211
+                }
212
+            },
213
+        });
214
+    </script>
215
+</dom-module>

+ 154
- 0
test_repository/app/elements/admin/markdown/admin-markdown-delete.html Целия файл

@@ -0,0 +1,154 @@
1
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
3
+<link rel="import" href="../../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
4
+<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
5
+<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
6
+<link rel="import" href="../../../bower_components/paper-material/paper-material.html">
7
+<link rel="import" href="../../../bower_components/paper-toast/paper-toast.html">
8
+<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
9
+
10
+<dom-module id="admin-markdown-delete">
11
+    <template>
12
+        <style is="custom-style" include="iron-flex iron-positioning"></style>
13
+        <style>
14
+            paper-material {
15
+                padding: 8px;
16
+                margin: 8px;
17
+            }
18
+
19
+            .separator {
20
+                margin: 0 4px;
21
+            }
22
+
23
+            .button {
24
+                color: white;
25
+                background-color: var(--paper-blue-300);
26
+            }
27
+
28
+            .markdown-container {
29
+                margin: 8px;
30
+            }
31
+
32
+            .toast {
33
+
34
+            }
35
+
36
+            .success {
37
+                background: var(--paper-green-300);
38
+                border: 1px solid var(--paper-green-500);
39
+            }
40
+
41
+            .failure {
42
+                background: var(--paper-red-300);
43
+                border: 1px solid var(--paper-red-500);
44
+            }
45
+        </style>
46
+
47
+        <paper-material class="header">
48
+            This form removes an item from the markdown list
49
+        </paper-material>
50
+
51
+        <paper-material class="options horizontal layout">
52
+            <vaadin-combo-box id="toolList" label="Tool" class="flex" items="[[tools]]"></vaadin-combo-box>
53
+
54
+            <div class="separator"></div>
55
+
56
+            <vaadin-combo-box id="sectionList" label="Sections" class="flex"
57
+                              items="{{sectionNames}}" value="About">
58
+            </vaadin-combo-box>
59
+
60
+            <paper-input id="itemNum" label="Item #" type="number"></paper-input>
61
+
62
+            <paper-button id="save" class="button primary flex" on-tap="_deleteMarkdown" raised>ADD</paper-button>
63
+        </paper-material>
64
+
65
+        <paper-toast id="successToast" class="toast success" text="{{message.success}}"></paper-toast>
66
+        <paper-toast id="failureToast" class="toast failure" text="{{message.failure}}"></paper-toast>
67
+    </template>
68
+    <script>
69
+        Polymer({
70
+            is: 'admin-markdown-delete',
71
+
72
+            properties: {
73
+                /**
74
+                 * Object for storing success/failure messages
75
+                 */
76
+                message: {
77
+                    type: Object,
78
+                    value: {
79
+                        failure: "Failed to add tool!",
80
+                        success: "Markdown added successfully!"
81
+                    }
82
+                },
83
+
84
+                /**
85
+                 * Names of the sections that can be deleted
86
+                 */
87
+                sectionNames: {
88
+                    type: Array,
89
+                    value: ['About', 'Training', 'Links']
90
+                },
91
+
92
+                /**
93
+                 * Array for storing the names of the tools
94
+                 */
95
+                tools: {
96
+                    type: Array,
97
+                }
98
+            },
99
+
100
+            ready: function () {
101
+                var me = this;
102
+
103
+                //Load in the tools to populate the <vaadin-combo-box>
104
+                me.tools = me._getTools();
105
+            },
106
+
107
+            /**
108
+             * Helper method for getting the names of the tools
109
+             * @private
110
+             */
111
+            _getTools: function () {
112
+                var me = this;
113
+
114
+                var tools_ajax = document.createElement('iron-ajax');
115
+                tools_ajax.auto = true;
116
+                tools_ajax.method = "GET";
117
+                tools_ajax.url = "/api/tools/info";
118
+
119
+                tools_ajax.addEventListener("response", function (e) {
120
+                    //Grab the tools from the response
121
+                    var tools = e.detail.response;
122
+
123
+                    //We need to transform the array to fit to <vaadin-combo-box> requirements
124
+                    me.tools = tools.map(function (obj) {
125
+                        return rObj = {
126
+                            value: obj.id,
127
+                            label: obj.name
128
+                        };
129
+                    });
130
+                });
131
+            },
132
+
133
+            /**
134
+             * Helper method for deleting an item from the tool section
135
+             * @private
136
+             */
137
+            _deleteMarkdown: function () {
138
+                var me = this;
139
+
140
+                var toolListVal = me.$.toolList.value;
141
+                var sectionListVal = me.$.sectionList.value;
142
+                var itemNumVal = me.$.itemNum.value;
143
+
144
+                var successToast = me.$.successToast;
145
+                var failureToast = me.$.failureToast;
146
+
147
+                //Only procced if all of these were supplied
148
+                if (toolListVal && sectionListVal && itemNumVal) {
149
+
150
+                }
151
+            },
152
+        });
153
+    </script>
154
+</dom-module>

+ 190
- 0
test_repository/app/elements/admin/markdown/admin-markdown-editor.html Целия файл

@@ -0,0 +1,190 @@
1
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
3
+<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
4
+<link rel="import" href="../../../bower_components/paper-toast/paper-toast.html">
5
+<link rel="import" href="../../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
6
+<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
7
+<link rel="import" href="../../markdown-editor.html">
8
+
9
+<dom-module id="admin-markdown-editor">
10
+    <template>
11
+        <style is="custom-style" include="iron-flex iron-positioning"></style>
12
+        <style>
13
+            .container {
14
+                height: 100%;
15
+            }
16
+
17
+            .row {
18
+                margin-top: 16px;
19
+                margin-bottom: 16px;
20
+            }
21
+
22
+            vaadin-combo-box {
23
+                margin-left: 16px;
24
+                margin-right: 16px;
25
+            }
26
+
27
+            .button {
28
+                background-color: var(--zebra-blue);
29
+                color: white;
30
+            }
31
+
32
+            .editor {
33
+                height: 100%;
34
+            }
35
+        </style>
36
+        <div id="container" class="container vertical layout">
37
+            <div class="row horizontal layout">
38
+                <vaadin-combo-box id="toolList" label="Tools" class="flex" items="{{tools}}"
39
+                                  value="JIRA"></vaadin-combo-box>
40
+                <vaadin-combo-box id="sectionList" label="Sections" class="flex"
41
+                                  items="{{sectionNames}}" value="About"></vaadin-combo-box>
42
+                <paper-input id="itemList" class="flex" label="Item #" type="number" required></paper-input>
43
+            </div>
44
+
45
+            <div class="row horizontal layout">
46
+                <paper-button id="load" raised class="button flex">LOAD</paper-button>
47
+                <paper-button id="save" raised class="button flex">SAVE</paper-button>
48
+            </div>
49
+
50
+            <div class="row horizontal layout">
51
+                <paper-input id="heading" class="flex" label="Heading" type="text" value="{{heading}}"></paper-input>
52
+            </div>
53
+
54
+            <div class="container vertical layout">
55
+                <markdown-editor id="editor" class="editor" markdown="{{markdown}}"></markdown-editor>
56
+            </div>
57
+        </div>
58
+
59
+        <paper-toast id="successToast" text="Saved!"></paper-toast>
60
+    </template>
61
+    <script>
62
+        Polymer({
63
+            is: 'admin-markdown-editor',
64
+
65
+            properties: {
66
+                heading: {
67
+                    type: String,
68
+                    value: "Test Heading"
69
+                },
70
+
71
+                /**
72
+                 * The markdown string in the editor
73
+                 */
74
+                markdown: {
75
+                    type: String
76
+                },
77
+
78
+                /**
79
+                 * Array of tool id/name combos
80
+                 */
81
+                tools: {
82
+                    type: Array
83
+                },
84
+
85
+                /**
86
+                 * Array of sections names for the tools
87
+                 */
88
+                sectionNames: {
89
+                    type: Array,
90
+                    value: ["About", "Training", "Links"]
91
+                }
92
+            },
93
+
94
+            //Polymer lifecycle method when element is attached to the DOM
95
+            attached: function () {
96
+                var me = this;
97
+                var toolList = me.$.toolList;
98
+                var sectionList = me.$.sectionList;
99
+                var itemList = me.$.itemList;
100
+
101
+                var loadButton = me.$.load;
102
+                var saveButton = me.$.save;
103
+
104
+                var heading = me.$.heading;
105
+
106
+                var successToast = me.$.successToast;
107
+                successToast.fitInto = me.$.container;
108
+
109
+                //Load in the tools
110
+                var tools_ajax = document.createElement('iron-ajax');
111
+                tools_ajax.auto = true;
112
+                tools_ajax.method = "GET";
113
+                tools_ajax.url = "/api/tools/info";
114
+
115
+                //When we get a response from our AJAX call
116
+                tools_ajax.addEventListener("response", function (e) {
117
+                    //Grab the tools from the response
118
+                    var tools = e.detail.response;
119
+
120
+                    //We need to transform the array to fit to <vaadin-combo-box> requirements
121
+                    me.tools = tools.map(function (obj) {
122
+                        return rObj = {
123
+                            value: obj.id,
124
+                            label: obj.name
125
+                        };
126
+                    });
127
+                });
128
+
129
+                loadButton.addEventListener('tap', function () {
130
+                    me.markdown = me._getMarkdown(toolList.value, sectionList.value.toLowerCase(), itemList.value);
131
+                });
132
+
133
+                saveButton.addEventListener('tap', function () {
134
+                    me._setMarkdown(heading.value, toolList.value, sectionList.value.toLowerCase());
135
+                });
136
+            },
137
+
138
+            /**
139
+             * Method for retrieving the markdown for the given tool&section combo
140
+             *
141
+             * Sends the tool name and section name to the DB and receives a response containing the markdown string
142
+             *
143
+             * @param toolId The id of the tool to retrieve
144
+             * @param sectionName The name of the section to retrieve
145
+             * @private
146
+             */
147
+            _getMarkdown: function (toolId, sectionName, itemNum) {
148
+                var me = this;
149
+
150
+                var markdown_ajax = document.createElement('iron-ajax');
151
+                markdown_ajax.auto = true;
152
+                markdown_ajax.method = "GET";
153
+                markdown_ajax.url = "/api/tool/" + toolId + "/" + sectionName + "/" + itemNum;
154
+
155
+                //When we get a response from our AJAX call
156
+                markdown_ajax.addEventListener("response", function (e) {
157
+                    //Set the current markdown to the received markdown
158
+                    me.heading = e.detail.response.heading;
159
+                    me.markdown = e.detail.response.contents;
160
+                });
161
+            },
162
+
163
+            /**
164
+             * Method for setting the markdown string of the given tool and section
165
+             *
166
+             * A `paper-toast` will pop up if the markdown was set successfully
167
+             *
168
+             * @param markdown The markdown string to be set
169
+             * @param toolName The name of the tool to set the markdown for
170
+             * @param sectionName The name of the section to set the markdown for
171
+             * @private
172
+             */
173
+            _setMarkdown: function (heading, markdown, toolId, sectionName) {
174
+                var me = this;
175
+
176
+                var markdown_ajax = document.createElement('iron-ajax');
177
+                markdown_ajax.auto = true;
178
+                markdown_ajax.body = {heading: heading, contents: markdown};
179
+                markdown_ajax.contentType = 'application/json';
180
+                markdown_ajax.method = "POST";
181
+                markdown_ajax.url = "/api/tool/" + toolId + "/" + sectionName;
182
+
183
+                //When we get a response from our AJAX call
184
+                markdown_ajax.addEventListener("response", function (e) {
185
+                    console.log(e.detail.response);
186
+                });
187
+            }
188
+        });
189
+    </script>
190
+</dom-module>

+ 19
- 0
test_repository/app/elements/colors.html Целия файл

@@ -0,0 +1,19 @@
1
+<style is="custom-style">
2
+    :root {
3
+        /* Zebra Color palette form: https://zebra.sharepoint.com/ourcompany/brandguidelines/Pages/default.aspx# */
4
+
5
+        /* Main */
6
+        --zebra-black: #000000;
7
+        --zebra-white: #FFFFFF;
8
+
9
+        /* Accents */
10
+        --zebra-red: #ED1C24;
11
+        --zebra-yellow: #FFD200;
12
+        --zebra-blue: #007CB0;
13
+
14
+        /* Neutrals */
15
+        --zebra-light-gray: #DBD8D6;
16
+        --zebra-medium-gray: #7E868C;
17
+        --zebra-dark-gray: #333D47;
18
+    }
19
+</style>

+ 75
- 0
test_repository/app/elements/elements.html Целия файл

@@ -0,0 +1,75 @@
1
+<!-- Polymer Core -->
2
+<link rel="import" href="../bower_components/polymer/polymer.html">
3
+
4
+<!-- Polymer App Elements -->
5
+<link rel="import" href="../bower_components/app-layout/app-drawer/app-drawer.html">
6
+<link rel="import" href="../bower_components/app-layout/app-drawer-layout/app-drawer-layout.html">
7
+<link rel="import" href="../bower_components/app-layout/app-header/app-header.html">
8
+<link rel="import" href="../bower_components/app-layout/app-header-layout/app-header-layout.html">
9
+<link rel="import" href="../bower_components/app-layout/app-scroll-effects/effects/waterfall.html">
10
+<link rel="import" href="../bower_components/app-layout/app-toolbar/app-toolbar.html">
11
+<link rel="import" href="../bower_components/app-route/app-route.html">
12
+<link rel="import" href="../bower_components/app-route/app-location.html">
13
+
14
+<!-- Polymer Paper Elements -->
15
+<link rel="import" href="../bower_components/paper-button/paper-button.html">
16
+<link rel="import" href="../bower_components/paper-card/paper-card.html">
17
+<link rel="import" href="../bower_components/paper-dialog/paper-dialog.html">
18
+<link rel="import" href="../bower_components/paper-drawer-panel/paper-drawer-panel.html">
19
+<link rel="import" href="../bower_components/paper-header-panel/paper-header-panel.html">
20
+<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
21
+<link rel="import" href="../bower_components/paper-input/paper-input.html">
22
+<link rel="import" href="../bower_components/paper-input/paper-textarea.html">
23
+<link rel="import" href="../bower_components/paper-item/paper-item.html">
24
+<link rel="import" href="../bower_components/paper-item/paper-item-body.html">
25
+<link rel="import" href="../bower_components/paper-item/paper-icon-item.html">
26
+<link rel="import" href="../bower_components/paper-material/paper-material.html">
27
+<link rel="import" href="../bower_components/paper-menu/paper-menu.html">
28
+<link rel="import" href="../bower_components/paper-menu/paper-submenu.html">
29
+<link rel="import" href="../bower_components/paper-spinner/paper-spinner.html">
30
+<link rel="import" href="../bower_components/paper-styles/color.html">
31
+<link rel="import" href="../bower_components/paper-tabs/paper-tab.html">
32
+<link rel="import" href="../bower_components/paper-tabs/paper-tabs.html">
33
+<link rel="import" href="../bower_components/paper-toast/paper-toast.html">
34
+<link rel="import" href="../bower_components/paper-toolbar/paper-toolbar.html">
35
+
36
+<!-- Polymer Iron Elements -->
37
+<link rel="import" href="../bower_components/iron-ajax/iron-ajax.html">
38
+<link rel="import" href="../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
39
+<link rel="import" href="../bower_components/iron-form/iron-form.html">
40
+<link rel="import" href="../bower_components/iron-icons/iron-icons.html">
41
+<link rel="import" href="../bower_components/iron-image/iron-image.html">
42
+<link rel="import" href="../bower_components/iron-pages/iron-pages.html">
43
+
44
+<!-- Polymer Marked Elements -->
45
+<link rel="import" href="../bower_components/marked-element/marked-element.html">
46
+
47
+<!-- Vaadin Elements -->
48
+<link rel="import" href="../bower_components/vaadin-combo-box/vaadin-combo-box.html">
49
+
50
+<!-- =============================================================================================================== -->
51
+<!--                                            CUSTOM ELEMENTS                                                      -->
52
+<!-- =============================================================================================================== -->
53
+<link rel="import" href="colors.html">
54
+<link rel="import" href="markdown-editor.html">
55
+<link rel="import" href="sample-content.html">
56
+<link rel="import" href="user-profile.html">
57
+<link rel="import" href="paper-card-list.html">
58
+
59
+<!-- Portal Elements -->
60
+<link rel="import" href="./portal/portal-home.html">
61
+<link rel="import" href="./portal/portal-news-list.html">
62
+<link rel="import" href="./portal/portal-tools.html">
63
+<link rel="import" href="./portal/portal-forms.html">
64
+
65
+<!-- Admin Tool Elements-->
66
+<link rel="import" href="./admin/admin-login.html">
67
+<link rel="import" href="./admin/admin-add-tool-form.html">
68
+<link rel="import" href="./admin/admin-remove-tool-form.html">
69
+<link rel="import" href="./admin/markdown/admin-markdown-editor.html">
70
+<link rel="import" href="./admin/markdown/admin-markdown-add.html">
71
+<link rel="import" href="./admin/markdown/admin-markdown-delete.html">
72
+
73
+<!-- JIRA Elements -->
74
+<link rel="import" href="./tools/jira/jira-create-issue-form.html">
75
+<link rel="import" href="./tools/jira/jira-login-dialog.html">

+ 145
- 0
test_repository/app/elements/markdown-editor.html Целия файл

@@ -0,0 +1,145 @@
1
+<link rel="import" href="../bower_components/polymer/polymer.html">
2
+
3
+<link rel="import" href="../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
4
+<link rel="import" href="../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
5
+<link rel="import" href="../bower_components/iron-icons/editor-icons.html">
6
+
7
+<link rel="import" href="../bower_components/paper-icon-button/paper-icon-button.html">
8
+
9
+<link rel="import" href="../bower_components/marked-element/marked-element.html">
10
+
11
+<dom-module id="markdown-editor">
12
+    <template>
13
+        <style is="custom-style" include="iron-flex iron-positioning"></style>
14
+        <style>
15
+            .container {
16
+                height: 100%;
17
+                @apply(--layout-vertical);
18
+            }
19
+
20
+            .actions {
21
+                background-color: var(--zebra-medium-gray);
22
+            }
23
+
24
+            .actions > paper-icon-button {
25
+                color: white;
26
+            }
27
+
28
+            .editor-preview {
29
+                @apply(--layout-flex);
30
+                @apply(--layout-horizontal);
31
+            }
32
+
33
+            .editor {
34
+                width: 98%;
35
+            }
36
+        </style>
37
+        <div class="container">
38
+            <!-- Editor Actions (bar) -->
39
+            <div class="actions">
40
+                <paper-icon-button id="bold" icon="editor:format-bold" on-tap="_insertMarkdown"></paper-icon-button>
41
+                <paper-icon-button id="italic" icon="editor:format-italic" on-tap="_insertMarkdown"></paper-icon-button>
42
+                <paper-icon-button id="link" icon="editor:insert-link" on-tap="_insertMarkdown"></paper-icon-button>
43
+                <paper-icon-button id="blockquote" icon="editor:format-indent-increase"
44
+                                   on-tap="_insertMarkdown"></paper-icon-button>
45
+                <paper-icon-button id="code" icon="code" on-tap="_insertMarkdown"></paper-icon-button>
46
+                <paper-icon-button id="photo" icon="editor:insert-photo" on-tap="_insertMarkdown"></paper-icon-button>
47
+                <paper-icon-button id="bulleted" icon="editor:format-list-bulleted"
48
+                                   on-tap="_insertMarkdown"></paper-icon-button>
49
+                <paper-icon-button id="numbered" icon="editor:format-list-numbered"
50
+                                   on-tap="_insertMarkdown"></paper-icon-button>
51
+                <paper-icon-button id="linebreak" class="action-icon" icon="remove"
52
+                                   on-tap="_insertMarkdown"></paper-icon-button>
53
+            </div>
54
+            <!-- Editor / Preview -->
55
+            <div class="editor-preview">
56
+                <div class="flex">
57
+                    <iron-autogrow-textarea id="markdownEditor" class="editor" value="{{markdown}}"
58
+                                            rows="5"></iron-autogrow-textarea>
59
+                </div>
60
+
61
+                <div class="flex">
62
+                    <marked-element id="markdownViewer" markdown="{{markdown}}"></marked-element>
63
+                </div>
64
+            </div>
65
+        </div>
66
+    </template>
67
+    <script>
68
+        Polymer({
69
+            is: 'markdown-editor',
70
+
71
+            properties: {
72
+                editorMarkdown: {
73
+                    type: String,
74
+                },
75
+
76
+                markdown: {
77
+                    type: String,
78
+                    value: "# Test Markdown"
79
+                }
80
+            },
81
+
82
+            attached: function () {
83
+                var me = this;
84
+
85
+                var markdownEditor = me.$.markdownEditor;
86
+                var markdownViewer = me.$.markdownViewer;
87
+
88
+                //Set the initial markdown value
89
+                markdownEditor.value = me.markdown;
90
+
91
+                //When the input changes in the textarea, set `markdown` so that the viewer can render it
92
+                markdownEditor.addEventListener('input', function () {
93
+                    me.markdown = markdownEditor.value;
94
+                });
95
+            },
96
+
97
+            _insertMarkdown: function (event) {
98
+                var me = this;
99
+
100
+                var action = event.currentTarget.id;
101
+
102
+                switch (action) {
103
+                    case "bold":
104
+                        me._insertInTextarea(me.$.markdownEditor, "**strong text**");
105
+                        break;
106
+                    case "italic":
107
+                        me._insertInTextarea(me.$.markdownEditor, "*italic text*");
108
+                        break;
109
+                    case "link":
110
+                        me._insertInTextarea(me.$.markdownEditor, "[link](http://path.to.link)");
111
+                        break;
112
+                    case "blockquote":
113
+                        me._insertInTextarea(me.$.markdownEditor, "> blockquote text");
114
+                        break;
115
+                    case "code":
116
+                        me._insertInTextarea(me.$.markdownEditor, "\t Code Block (4 spaces / 1 tab)");
117
+                        break;
118
+                    case "photo":
119
+                        me._insertInTextarea(me.$.markdownEditor, "![Alt text](/path/to/image.jpg)");
120
+                        break;
121
+                    case "bulleted":
122
+                        me._insertInTextarea(me.$.markdownEditor, "Unordered List \n* List Item 1 \n* List Item 2 \n* List Item 3");
123
+                        break;
124
+                    case "numbered":
125
+                        me._insertInTextarea(me.$.markdownEditor, "Ordered List \n1. List Item 1 \n2. List Item 2 \n3. List Item 3");
126
+                        break;
127
+                    case "linebreak":
128
+                        me._insertInTextarea(me.$.markdownEditor, "\n---\n");
129
+                        break;
130
+                }
131
+            },
132
+
133
+            _insertInTextarea: function (textarea, textToInsert) {
134
+                var start = textarea.selectionStart;
135
+                var end = textarea.selectionEnd;
136
+                var text = textarea.value;
137
+                var before = text.substring(0, start);
138
+                var after = text.substring(end, text.length);
139
+                textarea.value = (before + textToInsert + after);
140
+                textarea.selectionStart = textarea.selectionEnd = start + textToInsert.length;
141
+                textarea.focus()
142
+            }
143
+        });
144
+    </script>
145
+</dom-module>

+ 35
- 0
test_repository/app/elements/paper-card-list.html Целия файл

@@ -0,0 +1,35 @@
1
+<link rel="import" href="../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../bower_components/paper-card/paper-card.html">
3
+<link rel="import" href="../bower_components/marked-element/marked-element.html">
4
+
5
+<dom-module id="paper-card-list">
6
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
7
+    <style>
8
+        paper-card {
9
+            margin: 8px;
10
+        }
11
+    </style>
12
+
13
+    <template>
14
+        <div class="layout vertical">
15
+            <template is="dom-repeat" items="{{items}}">
16
+                <paper-card class="card flex" heading="{{item.heading}}">
17
+                    <div class="card-content">
18
+                        <marked-element markdown="{{item.contents}}"></marked-element>
19
+                    </div>
20
+                </paper-card>
21
+            </template>
22
+        </div>
23
+    </template>
24
+    <script>
25
+        Polymer({
26
+            is: 'paper-card-list',
27
+
28
+            properties: {
29
+                items: {
30
+                    type: Array
31
+                }
32
+            }
33
+        });
34
+    </script>
35
+</dom-module>

+ 148
- 0
test_repository/app/elements/portal/portal-forms.html Целия файл

@@ -0,0 +1,148 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
3
+<link rel="import" href="../../bower_components/paper-button/paper-button.html">
4
+<link rel="import" href="../../bower_components/paper-drawer-panel/paper-drawer-panel.html">
5
+<link rel="import" href="../../bower_components/paper-header-panel/paper-header-panel.html">
6
+<link rel="import" href="../../bower_components/paper-item/paper-item.html">
7
+<link rel="import" href="../../bower_components/paper-material/paper-material.html">
8
+<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
9
+<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
10
+<link rel="import" href="../tools/jira/jira-create-issue-form.html">
11
+
12
+<!--
13
+    'Wrapper' for the 'Forms' section of the Portal.
14
+
15
+    A left navbar switches between each form.
16
+    Forms can be enabled/disabled in the admin console (WIP) TODO
17
+-->
18
+<dom-module id="portal-forms">
19
+    <style>
20
+        .default-container {
21
+            margin: 8px 16px;
22
+            padding: 8px;
23
+            background-color: var(--paper-yellow-100);
24
+        }
25
+
26
+        .toast {
27
+            color: black;
28
+        }
29
+
30
+        .success {
31
+            background: var(--paper-green-100);
32
+            border: 1px solid var(--paper-green-300);
33
+        }
34
+
35
+        .failure {
36
+            background: var(--paper-red-100);
37
+            border: 1px solid var(--paper-red-300);
38
+        }
39
+
40
+        .issue-link {
41
+            color: black;
42
+        }
43
+
44
+        .close {
45
+            background-color: black;
46
+            color: white;
47
+        }
48
+    </style>
49
+
50
+    <template>
51
+        <paper-drawer-panel>
52
+            <paper-header-panel drawer>
53
+                <paper-menu selected="{{selected}}">
54
+                    <paper-item>Create Request/Change</paper-item>
55
+                </paper-menu>
56
+            </paper-header-panel>
57
+            <paper-header-panel main id="mainContent">
58
+                <paper-material class="default-container" hidden="{{loggedIn}}">
59
+                    <h3>Please login to access the forms.</h3>
60
+                    <p>Click the
61
+                        <paper-icon-button icon="account-box" disabled></paper-icon-button>
62
+                        icon in the top right to log in.
63
+                    </p>
64
+                </paper-material>
65
+
66
+                <iron-pages selected="{{selected}}" hidden="{{!loggedIn}}">
67
+                    <jira-create-issue-form id="jiraIssueForm"></jira-create-issue-form>
68
+                </iron-pages>
69
+
70
+                <paper-toast id="jiraSuccessToast" class="toast success" duration="0">
71
+                    <a id="issueUrl" class="issue-link" href="{{issueLink}}" target="_blank">Link to your created
72
+                        issue</a>
73
+                    <paper-button class="button close" onclick="jiraSuccessToast.toggle()">Close</paper-button>
74
+                </paper-toast>
75
+
76
+                <paper-toast id="jiraFailureToast" class="toast failure" duration="5000"
77
+                             text="{{errorMessage}}"></paper-toast>
78
+            </paper-header-panel>
79
+        </paper-drawer-panel>
80
+    </template>
81
+    <script>
82
+        Polymer({
83
+            is: 'portal-forms',
84
+
85
+            properties: {
86
+                /**
87
+                 * An error message to be displayed in the failure Toast.
88
+                 */
89
+                errorMessage: {
90
+                    type: String
91
+                },
92
+
93
+                /**
94
+                 * Link to the issue on JIRA.
95
+                 * Is returned by the <jira-create-issue-form> if successful.
96
+                 */
97
+                issueLink: {
98
+                    type: String
99
+                },
100
+
101
+                /**
102
+                 * The logged in state of the user.
103
+                 * Used to hide the forms if the user is NOT logged in.
104
+                 * Is two-way bound to allow parent elements to propagate down.
105
+                 */
106
+                loggedIn: {
107
+                    type: Boolean,
108
+                    value: false
109
+                },
110
+
111
+                /**
112
+                 * The currently selected page (i.e. form)
113
+                 */
114
+                selected: {
115
+                    type: Number,
116
+                    value: 0
117
+                }
118
+            },
119
+
120
+            attached: function () {
121
+                var me = this;
122
+
123
+                var jiraIssueForm = me.$.jiraIssueForm;
124
+
125
+                //Grab references to the <paper-toast>'s and tell them to fit into the main container
126
+                //Otherwise, they will overlap with the drawer (when it is shown)
127
+                var jiraSuccessToast = me.$.jiraSuccessToast;
128
+                jiraSuccessToast.fitInto = me.$.mainContent;
129
+                var jiraFailureToast = me.$.jiraFailureToast;
130
+                jiraFailureToast.fitInto = me.$.mainContent;
131
+
132
+                //Add an event listener to listen for issue creation events that the <jira-create-issue-form> fires
133
+                jiraIssueForm.addEventListener('jira-issue-created', function (event) {
134
+                    //Get the issue link that is returned
135
+                    me.issueLink = event.detail.issueUrl;
136
+                    jiraSuccessToast.open();
137
+                });
138
+
139
+                //Add an event listener to listen for issue creation failure events that the <jira-create-issue-form> fires
140
+                jiraIssueForm.addEventListener('jira-issue-failure', function (event) {
141
+                    //Set the error message that is returned
142
+                    me.errorMessage = event.detail.errorMsg;
143
+                    jiraFailureToast.open();
144
+                });
145
+            }
146
+        });
147
+    </script>
148
+</dom-module>

+ 68
- 0
test_repository/app/elements/portal/portal-home.html Целия файл

@@ -0,0 +1,68 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
3
+<link rel="import" href="../../bower_components/paper-drawer-panel/paper-drawer-panel.html">
4
+<link rel="import" href="../../bower_components/paper-header-panel/paper-header-panel.html">
5
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
6
+<link rel="import" href="../../bower_components/paper-item/paper-item.html">
7
+<link rel="import" href="../../bower_components/paper-material/paper-material.html">
8
+<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
9
+
10
+<!--
11
+    'Wrapper' for the 'Home' section of the Portal.
12
+    A left navbar switches between each section.
13
+-->
14
+<dom-module id="portal-home">
15
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
16
+    <style>
17
+        paper-item {
18
+            font-size: 14px;
19
+        }
20
+
21
+        iron-pages {
22
+            margin: 8px 16px;
23
+        }
24
+
25
+        .default-container {
26
+            height: 100px;
27
+            width: 100%;
28
+
29
+            background-color: white;
30
+        }
31
+    </style>
32
+    <template>
33
+        <paper-drawer-panel>
34
+            <paper-header-panel drawer>
35
+                <paper-menu selected="{{selected}}">
36
+                    <paper-item>Initiatives</paper-item>
37
+                    <paper-item>Our Team</paper-item>
38
+                </paper-menu>
39
+            </paper-header-panel>
40
+            <paper-header-panel main>
41
+                <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
42
+                <iron-pages class="page-container" selected="{{selected}}">
43
+                    <paper-material class="default-container"></paper-material>
44
+                    <paper-material class="default-container"></paper-material>
45
+                </iron-pages>
46
+            </paper-header-panel>
47
+        </paper-drawer-panel>
48
+    </template>
49
+    <script>
50
+        Polymer({
51
+            is: 'portal-home',
52
+
53
+            properties: {
54
+                /**
55
+                 * The currently selected page.
56
+                 * Defaults to 0.
57
+                 */
58
+                selected: {
59
+                    type: Number,
60
+                    value: 0
61
+                }
62
+            },
63
+
64
+            attached: function () {
65
+            },
66
+        });
67
+    </script>
68
+</dom-module>

+ 83
- 0
test_repository/app/elements/portal/portal-news-list.html Целия файл

@@ -0,0 +1,83 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
3
+<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
4
+<link rel="import" href="../../bower_components/paper-card/paper-card.html">
5
+
6
+<!--
7
+    'Wrapper' for the 'News' section of the Portal
8
+    Loads the list of news items from the DB and displays them as a list of cards
9
+-->
10
+<dom-module id="portal-news-list">
11
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
12
+    <style>
13
+
14
+    </style>
15
+
16
+    <template>
17
+        <div class="layout vertical">
18
+            <div class="separator"></div>
19
+            <template is="dom-repeat" items="{{newsItems}}">
20
+                <paper-card class="card full-width" heading="{{item.title}}">
21
+                    <div class="card-content">
22
+                        <div>{{item.contents}}</div>
23
+                        <div style="text-align: right">{{_formatDate(item.date)}}</div>
24
+                    </div>
25
+                </paper-card>
26
+            </template>
27
+        </div>
28
+    </template>
29
+    <script>
30
+        Polymer({
31
+            is: 'portal-news-list',
32
+
33
+            properties: {
34
+                /**
35
+                 * Array of news items.
36
+                 * Each news item is formatted like:
37
+                 *     {
38
+                 *          title: "...",
39
+                 *          contents: "...",
40
+                 *          data: ISODate()
41
+                 *     }
42
+                 */
43
+                newsItems: {
44
+                    type: Array
45
+                }
46
+            },
47
+
48
+            //Once the element is attached to the DOM, call the helper method to retrieve the news items
49
+            attached: function () {
50
+                this.getNews();
51
+            },
52
+
53
+            /**
54
+             * Retrieve news items JSON Array from the DB via the REST API
55
+             */
56
+            getNews: function () {
57
+                var me = this;
58
+
59
+                var ajax = document.createElement('iron-ajax');
60
+                ajax.auto = true;
61
+                ajax.method = "GET";
62
+                ajax.url = "/api/news/";
63
+
64
+                //When we get a response from our AJAX call
65
+                ajax.addEventListener("response", function (e) {
66
+                    //The response will be a JSON Array, so we can assign it directly
67
+                    me.newsItems = e.detail.response;
68
+                });
69
+            },
70
+
71
+            /**
72
+             * Helper method that uses Moment.js to format the ISO Date that is stored in the DB.
73
+             *
74
+             * @param {String} dateStr The date returned from the DB as a String
75
+             * @returns {String} Formatted date as a String
76
+             * @private
77
+             */
78
+            _formatDate: function (dateStr) {
79
+                return moment(dateStr).format("MM/DD/YYYY [@] h:mm:ss A");
80
+            }
81
+        });
82
+    </script>
83
+</dom-module>

+ 180
- 0
test_repository/app/elements/portal/portal-tools.html Целия файл

@@ -0,0 +1,180 @@
1
+<link rel="import" href="../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
3
+<link rel="import" href="../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
4
+<link rel="import" href="../../bower_components/paper-drawer-panel/paper-drawer-panel.html">
5
+<link rel="import" href="../../bower_components/paper-header-panel/paper-header-panel.html">
6
+<link rel="import" href="../../bower_components/paper-icon-button/paper-icon-button.html">
7
+<link rel="import" href="../../bower_components/paper-item/paper-item.html">
8
+<link rel="import" href="../../bower_components/paper-item/paper-item-body.html">
9
+<link rel="import" href="../../bower_components/paper-menu/paper-menu.html">
10
+<link rel="import" href="../../bower_components/paper-menu/paper-submenu.html">
11
+<link rel="import" href="../paper-card-list.html">
12
+
13
+<!--
14
+    Wrapper' for the 'Tools' section of the Portal
15
+    Loads in the information about the tools ('About', 'Training', 'Useful Links')
16
+-->
17
+<dom-module id="portal-tools">
18
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
19
+    <style>
20
+        paper-item {
21
+            font-size: 14px;
22
+        }
23
+
24
+        .sublist paper-item {
25
+            padding-left: 32px;
26
+        }
27
+    </style>
28
+
29
+    <template>
30
+        <paper-drawer-panel>
31
+            <paper-header-panel drawer>
32
+                <paper-menu selected="{{toolSelected}}">
33
+                    <template is="dom-repeat" items="{{toolNames}}">
34
+                        <paper-submenu>
35
+                            <paper-item class="menu-trigger">
36
+                                <paper-item-body>
37
+                                    <div>{{item}}</div>
38
+                                </paper-item-body>
39
+                                <paper-icon-button icon="arrow-drop-down"></paper-icon-button>
40
+                            </paper-item>
41
+                            <paper-menu class="menu-content sublist" selected="{{sectionSelected}}">
42
+                                <paper-item>About</paper-item>
43
+                                <paper-item>Training</paper-item>
44
+                                <paper-item>Useful Links</paper-item>
45
+                            </paper-menu>
46
+                        </paper-submenu>
47
+                    </template>
48
+                </paper-menu>
49
+            </paper-header-panel>
50
+
51
+            <paper-header-panel main>
52
+                <paper-icon-button icon="menu" paper-drawer-toggle></paper-icon-button>
53
+                <paper-card-list items="{{sectionItems}}"></paper-card-list>
54
+            </paper-header-panel>
55
+        </paper-drawer-panel>
56
+    </template>
57
+    <script>
58
+        Polymer({
59
+            is: 'portal-tools',
60
+
61
+            properties: {
62
+                /**
63
+                 * The items for the particular tool section (i.e. the array of 'about' items).
64
+                 */
65
+                sectionItems: {
66
+                    type: Array
67
+                },
68
+
69
+                /**
70
+                 * The particular section that is selected.
71
+                 * Has an observer for detecting when the user clicks on a different section in the sidebar.
72
+                 */
73
+                sectionSelected: {
74
+                    type: Number,
75
+                    //value: 0,
76
+                    observer: '_sectionChanged'
77
+                },
78
+
79
+                /**
80
+                 * Tool selected in the sidebar
81
+                 */
82
+                toolSelected: {
83
+                    type: Number,
84
+                    //value: 0
85
+                },
86
+
87
+                /**
88
+                 * Array of tools to be loaded from DB
89
+                 */
90
+                tools: {
91
+                    type: Array
92
+                },
93
+
94
+                /**
95
+                 * Names of tools to be loaded from the DB
96
+                 */
97
+                toolNames: {
98
+                    type: Array
99
+                }
100
+            },
101
+
102
+            /**
103
+             * When this element is attached to the DOM, load in the tools and tool names by calling the appropriate helper methods
104
+             */
105
+            attached: function () {
106
+                this.getTools();
107
+                this.getToolNames();
108
+            },
109
+
110
+            /**
111
+             * Helper method for retrieving the tools from the DB
112
+             */
113
+            getTools: function () {
114
+                var me = this;
115
+
116
+                var ajax = document.createElement('iron-ajax');
117
+                ajax.auto = true;
118
+                ajax.method = "GET";
119
+                ajax.url = "/api/tools/";
120
+
121
+                ajax.addEventListener("response", function (e) {
122
+                    me.tools = e.detail.response;
123
+                });
124
+            },
125
+
126
+            /**
127
+             * Helper method for retrieving the tool names from the DB
128
+             */
129
+            getToolNames: function () {
130
+                var me = this;
131
+
132
+                //GET the tool names
133
+                var ajax = document.createElement('iron-ajax');
134
+                ajax.auto = true;
135
+                ajax.method = "GET";
136
+                ajax.url = "/api/tools/names";
137
+
138
+                //When we get a response from our AJAX call
139
+                ajax.addEventListener("response", function (e) {
140
+                    me.toolNames = e.detail.response;
141
+                });
142
+            },
143
+
144
+            /**
145
+             * Observer for detecting when the section changes in the sidebar.
146
+             * Loads in a new tool section item array.
147
+             * @private
148
+             */
149
+            _sectionChanged: function () {
150
+                var me = this;
151
+
152
+                //Get the tool and section that was selected (via the <paper-menu> or <paper-submenu>).
153
+                var toolSelected = me.toolSelected;
154
+                var sectionSelected = me.sectionSelected;
155
+
156
+                //Check that there was an actual selection
157
+                if (toolSelected != undefined && sectionSelected != undefined) {
158
+                    var temp;
159
+
160
+                    //Number <=> Section
161
+                    //Need a switch/case statement to decide which section to load
162
+                    switch (sectionSelected) {
163
+                        case 0:
164
+                            temp = me.tools[toolSelected].about;
165
+                            break;
166
+                        case 1:
167
+                            temp = me.tools[toolSelected].training;
168
+                            break;
169
+                        case 2:
170
+                            temp = me.tools[toolSelected].links;
171
+                            break;
172
+                    }
173
+
174
+                    //Set the items = current selection
175
+                    me.sectionItems = temp;
176
+                }
177
+            }
178
+        });
179
+    </script>
180
+</dom-module>

+ 74
- 0
test_repository/app/elements/sample-content.html Целия файл

@@ -0,0 +1,74 @@
1
+<dom-module id="sample-content">
2
+
3
+    <script>
4
+        Polymer({
5
+            is: 'sample-content',
6
+            properties: {
7
+                size: {
8
+                    type: Number,
9
+                    value: 0
10
+                },
11
+                label: {
12
+                    value: ''
13
+                },
14
+                padding: {
15
+                    value: '16px'
16
+                },
17
+                margin: {
18
+                    value: '24px'
19
+                },
20
+                boxShadow: {
21
+                    value: '0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 3px 1px -2px rgba(0, 0, 0, 0.2)'
22
+                }
23
+            },
24
+            observers: [
25
+                '_render(size, label, padding, margin, boxShadow)'
26
+            ],
27
+            _lorem_ipsum_strings: [
28
+                'Lorem ipsum dolor sit amet, per in nusquam nominavi periculis, sit elit oportere ea.',
29
+                'Ut labores minimum atomorum pro. Laudem tibique ut has.',
30
+                'Fugit adolescens vis et, ei graeci forensibus sed.',
31
+                'Convenire definiebas scriptorem eu cum. Sit dolor dicunt consectetuer no.',
32
+                'Ea duis bonorum nec, falli paulo aliquid ei eum.',
33
+                'Usu eu novum principes, vel quodsi aliquip ea.',
34
+                'Has at minim mucius aliquam, est id tempor laoreet.',
35
+                'Pro saepe pertinax ei, ad pri animal labores suscipiantur.',
36
+                'Detracto suavitate repudiandae no eum. Id adhuc minim soluta nam.',
37
+                'Iisque perfecto dissentiet cum et, sit ut quot mandamus, ut vim tibique splendide instructior.',
38
+                'Id nam odio natum malorum, tibique copiosae expetenda mel ea.',
39
+                'Cu mei vide viris gloriatur, at populo eripuit sit.',
40
+                'Modus commodo minimum eum te, vero utinam assueverit per eu.',
41
+                'No nam ipsum lorem aliquip, accumsan quaerendum ei usu.'
42
+            ],
43
+            ready: function () {
44
+                this.style.display = 'block';
45
+            },
46
+            _randomString: function (size) {
47
+                var ls = this._lorem_ipsum_strings;
48
+                var s = '';
49
+                do {
50
+                    s += ls[Math.floor(Math.random() * ls.length)];
51
+                    size--;
52
+                } while (size > 0);
53
+                return s;
54
+            },
55
+            _randomLetter: function () {
56
+                return String.fromCharCode(65 + Math.floor(Math.random() * 26));
57
+            },
58
+            _render: function (size, label, padding, margin, boxShadow) {
59
+                var html = '';
60
+                for (var i = 0; i < size; i++) {
61
+                    html +=
62
+                            '<div style="box-shadow: ' + boxShadow + '; padding: ' + padding + '; margin: ' + margin + '; border-radius: 5px; background-color: #fff; color: #757575;">' +
63
+                            '<div style="display: inline-block; height: 64px; width: 64px; border-radius: 50%; background: #ddd; line-height: 64px; font-size: 30px; color: #555; text-align: center;">' + this._randomLetter() + '</div>' +
64
+                            '<div style="font-size: 22px; margin: 16px 0; color: #212121;">' + this.label + ' ' + this._randomString() + '</div>' +
65
+                            '<p style="font-size: 16px;">' + this._randomString() + '</p>' +
66
+                            '<p style="font-size: 14px;">' + this._randomString(3) + '</p>' +
67
+                            '</div>';
68
+                    this.innerHTML = html;
69
+                }
70
+            }
71
+        });
72
+    </script>
73
+
74
+</dom-module>

+ 233
- 0
test_repository/app/elements/tools/jira/jira-create-issue-form.html Целия файл

@@ -0,0 +1,233 @@
1
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
3
+<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
4
+<link rel="import" href="../../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
5
+<link rel="import" href="../../../bower_components/iron-form/iron-form.html">
6
+<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
7
+<link rel="import" href="../../../bower_components/paper-dialog/paper-dialog.html">
8
+<link rel="import" href="../../../bower_components/paper-icon-button/paper-icon-button.html">
9
+<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
10
+<link rel="import" href="../../../bower_components/paper-material/paper-material.html">
11
+<link rel="import" href="../../../bower_components/paper-spinner/paper-spinner.html">
12
+<link rel="import" href="../../../bower_components/vaadin-combo-box/vaadin-combo-box.html">
13
+
14
+<!--
15
+    An element for creating an issue on JIRA
16
+-->
17
+<dom-module id="jira-create-issue-form">
18
+    <template>
19
+        <style is="custom-style" include="iron-flex iron-positioning iron-flex-alignment"></style>
20
+        <style>
21
+            .container {
22
+                margin: 16px;
23
+                background-color: white;
24
+            }
25
+
26
+            form {
27
+                margin: 8px;
28
+            }
29
+
30
+            .separator {
31
+                margin: 8px 0;
32
+            }
33
+
34
+            iron-autogrow-textarea {
35
+                width: 100%;
36
+            }
37
+
38
+            .button {
39
+                color: white;
40
+            }
41
+
42
+            .submit {
43
+                background-color: var(--zebra-blue);
44
+            }
45
+        </style>
46
+
47
+        <paper-material class="layout vertical container">
48
+            <div class="layout horizontal end-justified">
49
+                <paper-icon-button id="help" icon="help" on-tap="_openHelp"></paper-icon-button>
50
+            </div>
51
+
52
+            <form id="issueForm" is="iron-form" method="post" action="/api/jira/issue">
53
+                <vaadin-combo-box id="issueType" class="flex" items="[[issueTypes]]"
54
+                                  label="Issue Type" required></vaadin-combo-box>
55
+
56
+                <vaadin-combo-box id="tool" class="flex" items="[[tools]]" label="Tool" required></vaadin-combo-box>
57
+
58
+                <paper-input id="summary" class="flex" label="Summary" required></paper-input>
59
+
60
+                <div class="separator"></div>
61
+
62
+                <span>Description</span>
63
+                <div class="layout horizontal">
64
+                    <iron-autogrow-textarea id="description" class="flex" rows="5" required></iron-autogrow-textarea>
65
+                </div>
66
+
67
+                <div class="separator"></div>
68
+
69
+                <div class="layout horizontal">
70
+                    <paper-button id="submit" class="button submit" on-tap="submit" raised>Submit</paper-button>
71
+                </div>
72
+            </form>
73
+        </paper-material>
74
+
75
+        <paper-dialog id="dialog" modal>
76
+            <paper-spinner id="spinner"></paper-spinner>
77
+        </paper-dialog>
78
+
79
+        <paper-dialog id="helpDialog" no-overlap horizontal-align="right" vertical-align="top">
80
+            <h2>Request vs Change</h2>
81
+        </paper-dialog>
82
+    </template>
83
+    <script>
84
+        Polymer({
85
+            is: 'jira-create-issue-form',
86
+
87
+            properties: {
88
+                /**
89
+                 * The state of the request (i.e. is it in flight or not).
90
+                 * Has an observer to disable the form if it is in flight.
91
+                 */
92
+                inFlight: {
93
+                    type: Boolean,
94
+                    value: false,
95
+                    observer: '_inFlight'
96
+                },
97
+
98
+                /**
99
+                 * The issue types that can be accepted by JIRA.
100
+                 * NOTE: The structure of the Objects in the array is intended (to comply with <vaadin-combo-box>
101
+                 */
102
+                issueTypes: {
103
+                    type: Array,
104
+                    value: [
105
+                        {value: 11200, label: "Request"},
106
+                        {value: 11201, label: "Change"}
107
+                    ]
108
+                },
109
+
110
+                /**
111
+                 * The array of tools (tool names/ids)
112
+                 */
113
+                tools: {
114
+                    type: Array
115
+                }
116
+            },
117
+
118
+            /**
119
+             * When this element is attached to the DOM, do some setup tasks.
120
+             */
121
+            attached: function () {
122
+                var me = this;
123
+
124
+                //Get the tools info (tool ids and names)
125
+                me._getToolsInfo();
126
+
127
+                //Grab a handle on all of our elements (form elements, etc)
128
+                var dialog = me.$.dialog;
129
+                var form = me.$.issueForm;
130
+                var issueType = me.$.issueType;
131
+                var tool = me.$.tool;
132
+                var summary = me.$.summary;
133
+                var description = me.$.description;
134
+
135
+                //Before the form is submitted, get the value of all the inputs and pack them into the request body
136
+                //Also pack the cookie into the request body
137
+                form.addEventListener('iron-form-presubmit', function (event) {
138
+                    this.request.contentType = 'application/json';
139
+                    this.request.body = {
140
+                        cookie: Cookies.get("JSESSIONID"),
141
+                        issueType: issueType.value,
142
+                        toolId: tool.value,
143
+                        summary: summary.value,
144
+                        description: description.value
145
+                    };
146
+                });
147
+
148
+                //Once the form is submitted, notify that the request is in flight and open the dialog containing the spinner
149
+                form.addEventListener('iron-form-submit', function (event) {
150
+                    me.inFlight = true;
151
+                    dialog.open();
152
+                });
153
+
154
+                //Once a response is received, notify that the response is not in flight and close the dialog
155
+                form.addEventListener('iron-form-response', function (event) {
156
+                    me.inFlight = false;
157
+                    dialog.close();
158
+
159
+                    //Fire an event containing a link to the issue
160
+                    var response = event.detail.response;
161
+                    me.fire('jira-issue-created', {issueUrl: response.issue.link});
162
+                });
163
+
164
+                //Once an error is encountered, notify that the response is not in flight and close the dialog
165
+                form.addEventListener('iron-form-error', function (event) {
166
+                    me.inFlight = false;
167
+                    dialog.close();
168
+
169
+                    //Fire an event containing the error message received
170
+                    var response = event.detail.request.response;
171
+                    me.fire('jira-issue-failure', {errorMsg: response.error});
172
+                });
173
+            },
174
+
175
+            //Helper function to submit the form
176
+            submit: function () {
177
+                this.$.issueForm.submit();
178
+            },
179
+
180
+            /**
181
+             * Helper function to retrieve the info about the tools from the DB.
182
+             * @private
183
+             */
184
+            _getToolsInfo: function () {
185
+                var me = this;
186
+
187
+                var ajax = document.createElement('iron-ajax');
188
+                ajax.auto = true;
189
+                ajax.method = "GET";
190
+                ajax.url = "/api/tools/info";
191
+
192
+                //Once we receive a response from the server, we need to update the 'tools' prop.
193
+                ajax.addEventListener("response", function (e) {
194
+                    var temp = e.detail.response;
195
+
196
+                    //Map to conform with <vaadin-combo-box>
197
+                    //label = name | value = id
198
+                    me.tools = temp.map(function (obj) {
199
+                        var rObj = {};
200
+                        rObj.label = obj.name;
201
+                        rObj.value = obj.id;
202
+                        return rObj;
203
+                    })
204
+                });
205
+            },
206
+
207
+            /**
208
+             * Helper method for opening the help dialog and positioning it next to the calling element.
209
+             * @private
210
+             */
211
+            _openHelp: function () {
212
+                this.$.helpDialog.positionTarget = this.$.help;
213
+                this.$.helpDialog.open();
214
+            },
215
+
216
+            /**
217
+             * Helper method that disables the form elements if the request is mid flight.
218
+             * Also enables/disables the spinner.
219
+             * @private
220
+             */
221
+            _inFlight: function () {
222
+                var me = this;
223
+                var inFlight = me.inFlight;
224
+
225
+                me.$.spinner.active = inFlight;
226
+                me.$.issueType.disabled = inFlight;
227
+                me.$.tool.disabled = inFlight;
228
+                me.$.summary.disabled = inFlight;
229
+                me.$.description.disabled = inFlight;
230
+            }
231
+        });
232
+    </script>
233
+</dom-module>

+ 197
- 0
test_repository/app/elements/tools/jira/jira-login-dialog.html Целия файл

@@ -0,0 +1,197 @@
1
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
2
+<link rel="import" href="../../../bower_components/iron-flex-layout/iron-flex-layout-classes.html">
3
+<link rel="import" href="../../../bower_components/iron-form/iron-form.html">
4
+<link rel="import" href="../../../bower_components/iron-icon/iron-icon.html">
5
+<link rel="import" href="../../../bower_components/paper-button/paper-button.html">
6
+<link rel="import" href="../../../bower_components/paper-dialog/paper-dialog.html">
7
+<link rel="import" href="../../../bower_components/paper-icon-button/paper-icon-button.html">
8
+<link rel="import" href="../../../bower_components/paper-input/paper-input.html">
9
+<link rel="import" href="../../../bower_components/paper-spinner/paper-spinner.html">
10
+
11
+<dom-module id="jira-login-dialog">
12
+    <style is="custom-style" include="iron-positioning iron-flex iron-flex-alignment"></style>
13
+    <style>
14
+        .button {
15
+            color: white;
16
+        }
17
+
18
+        .button[disabled] {
19
+            opacity: 0.25;
20
+        }
21
+
22
+        .login {
23
+            background-color: var(--zebra-blue);
24
+        }
25
+
26
+        .close {
27
+            background-color: var(--zebra-red);
28
+        }
29
+
30
+        .container {
31
+            margin: 8px 0;
32
+        }
33
+
34
+        .success {
35
+            color: var(--paper-green-500);
36
+        }
37
+
38
+        .failure {
39
+            color: var(--paper-red-500);
40
+        }
41
+    </style>
42
+
43
+    <template>
44
+        <paper-dialog id="dialog" modal>
45
+            <div class="layout vertical">
46
+                <div class="layout horizontal end-justified">
47
+                    <paper-icon-button id="close" class="" icon="close" on-tap="close"></paper-icon-button>
48
+                </div>
49
+
50
+                <div class="layout horizontal center-justified container">
51
+                    <paper-spinner id="spinner"></paper-spinner>
52
+                </div>
53
+
54
+                <div class="layout horizontal center-justified container">
55
+                    <iron-icon class="success" icon="check-circle" hidden="{{!response.success}}"></iron-icon>
56
+                    <iron-icon class="failure" icon="error" hidden="{{!response.failure}}"></iron-icon>
57
+                </div>
58
+                <!--
59
+
60
+                                <paper-icon-button id="close" class="horizontal end-justified layout" icon="close" on-tap="close"></paper-icon-button>
61
+                -->
62
+                <form id="form" class="layout vertical" is="iron-form" method="post" action="/api/jira/login">
63
+                    <paper-input id="username" label="Username (Zebra CoreID)" type="text" required></paper-input>
64
+                    <paper-input id="password" label="Password" type="password" required></paper-input>
65
+
66
+                    <paper-button id="submit" class="button login" on-tap="_submit" raised>Login</paper-button>
67
+                    <!--<paper-button id="close" class="button close" on-tap="close" raised>Close</paper-button>-->
68
+                </form>
69
+            </div>
70
+        </paper-dialog>
71
+    </template>
72
+    <script>
73
+        Polymer({
74
+            is: 'jira-login-dialog',
75
+
76
+            properties: {
77
+                /**
78
+                 * The state of the request (i.e. is it in flight or not).
79
+                 * Has an observer to disable the form if it is in flight.
80
+                 */
81
+                inFlight: {
82
+                    type: Boolean,
83
+                    value: false,
84
+                    observer: '_inFlight'
85
+                },
86
+
87
+                /**
88
+                 * The state of the response
89
+                 */
90
+                response: {
91
+                    type: Object,
92
+                    value: {
93
+                        "success": false,
94
+                        "failure": false,
95
+                    }
96
+                }
97
+            },
98
+
99
+            /**
100
+             * When this element is attached to the DOM, setup event listeners
101
+             */
102
+            attached: function () {
103
+                var me = this;
104
+
105
+                //Grab a handle on the form and elements
106
+                var form = me.$.form;
107
+                var usernameInput = me.$.username;
108
+                var passwordInput = me.$.password;
109
+
110
+                //Before the form is submitted, get the values from the inputs and add them to the body
111
+                form.addEventListener('iron-form-presubmit', function (event) {
112
+                    this.request.contentType = 'application/json';
113
+                    this.request.body = {username: usernameInput.value, password: passwordInput.value};
114
+                });
115
+
116
+                //When the form is submitted, notify that the request is in flight
117
+                form.addEventListener('iron-form-submit', function (event) {
118
+                    me.inFlight = true;
119
+                    me.set('response.failure', false);
120
+                    me.set('response.success', false);
121
+                });
122
+
123
+                //When an error is encountered, notify that the request is no longer in flight.
124
+                //Also mark that there was a failure.
125
+                form.addEventListener('iron-form-error', function (event) {
126
+                    me.inFlight = false;
127
+                    me.set('response.failure', true);
128
+                });
129
+
130
+                //When a response is received, notify that the request is no longer in flight.
131
+                //Also create a new cookie, etc.
132
+                form.addEventListener('iron-form-response', function (event) {
133
+                    me.inFlight = false;
134
+
135
+                    var response = event.detail.response;
136
+
137
+                    //Login Success. HTTP 200 (OK)
138
+                    if (event.detail.status == 200) {
139
+                        me.set('response.success', true);
140
+
141
+                        //Create a cookie.
142
+                        me._createCookie(response.session.name, response.session.value);
143
+
144
+                        //Fire an event to let our listeners know a login was successful
145
+                        me.fire('loggedIn', {loggedIn: true});
146
+
147
+                        //Close the form since the user logged in
148
+                        me.$.dialog.close();
149
+                    }
150
+                });
151
+            },
152
+
153
+            close: function () {
154
+                this.$.dialog.close();
155
+            },
156
+
157
+            open: function () {
158
+                this.$.dialog.open();
159
+            },
160
+
161
+            toggle: function () {
162
+                this.$.dialog.toggle();
163
+            },
164
+
165
+            _submit: function () {
166
+                this.$.form.submit();
167
+            },
168
+
169
+            /**
170
+             * Helper method that disables the form elements if the request is mid flight.
171
+             * Also enables/disables the spinner.
172
+             * @private
173
+             */
174
+            _inFlight: function () {
175
+                var me = this;
176
+                var inFlight = me.inFlight;
177
+
178
+                me.$.spinner.active = inFlight;
179
+                me.$.username.disabled = inFlight;
180
+                me.$.password.disabled = inFlight;
181
+                me.$.submit.disabled = inFlight;
182
+                me.$.close.disabled = inFlight;
183
+            },
184
+
185
+            /**
186
+             * Helper method to create a cookie using js-cookie.
187
+             * @param name The name of the cookie to create
188
+             * @param value The value of the cookie
189
+             * @private
190
+             */
191
+            _createCookie: function (name, value) {
192
+                //Set the cookie to expire in 12 hours (0.5 * 1 day)
193
+                Cookies.set(name, value, {expires: 0.5});
194
+            }
195
+        });
196
+    </script>
197
+</dom-module>

+ 32
- 0
test_repository/app/elements/user-profile.html Целия файл

@@ -0,0 +1,32 @@
1
+<dom-module id="user-profile">
2
+    <style>
3
+        paper-card {
4
+        }
5
+    </style>
6
+
7
+    <template>
8
+        <paper-card heading="{{name}}" image="{{image}}">
9
+            <div class="card-content">{{description}}</div>
10
+        </paper-card>
11
+    </template>
12
+    <script>
13
+        Polymer({
14
+            is: 'user-profile',
15
+
16
+            properties: {
17
+                description: {
18
+                    type: String,
19
+                },
20
+
21
+                name: {
22
+                    type: String,
23
+                },
24
+
25
+                image: {
26
+                    type: String,
27
+                    value: "../images/avatars/placeholder.png"
28
+                }
29
+            }
30
+        });
31
+    </script>
32
+</dom-module>

BIN
test_repository/app/favicon.ico Целия файл


BIN
test_repository/app/images/avatars/placeholder.png Целия файл


BIN
test_repository/app/images/zebra_logo_64.png Целия файл


+ 29
- 0
test_repository/app/index.html Целия файл

@@ -0,0 +1,29 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+<head>
4
+    <title>Zebra EMC Engineering Tools Portal</title>
5
+
6
+    <script src="scripts/webcomponents.min.js"></script>
7
+
8
+    <link rel="import" href="app-shell.html">
9
+
10
+    <style is="custom-style" include="iron-flex iron-positioning"></style>
11
+    <style is="custom-style">
12
+        * {
13
+            font-family: Arial, Helvetica, sans-serif;
14
+        }
15
+
16
+        body {
17
+            margin: 0;
18
+            background-color: #e1e1e1;
19
+        }
20
+    </style>
21
+
22
+    <script src="scripts/js-cookie.js" type="text/javascript"></script>
23
+    <script src="scripts/moment.min.js" type="text/javascript"></script>
24
+</head>
25
+
26
+<body>
27
+<app-shell></app-shell>
28
+</body>
29
+</html>

+ 151
- 0
test_repository/app/scripts/js-cookie.js Целия файл

@@ -0,0 +1,151 @@
1
+/*!
2
+ * JavaScript Cookie v2.1.2
3
+ * https://github.com/js-cookie/js-cookie
4
+ *
5
+ * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
6
+ * Released under the MIT license
7
+ */
8
+;(function (factory) {
9
+    if (typeof define === 'function' && define.amd) {
10
+        define(factory);
11
+    } else if (typeof exports === 'object') {
12
+        module.exports = factory();
13
+    } else {
14
+        var OldCookies = window.Cookies;
15
+        var api = window.Cookies = factory();
16
+        api.noConflict = function () {
17
+            window.Cookies = OldCookies;
18
+            return api;
19
+        };
20
+    }
21
+}(function () {
22
+    function extend () {
23
+        var i = 0;
24
+        var result = {};
25
+        for (; i < arguments.length; i++) {
26
+            var attributes = arguments[ i ];
27
+            for (var key in attributes) {
28
+                result[key] = attributes[key];
29
+            }
30
+        }
31
+        return result;
32
+    }
33
+
34
+    function init (converter) {
35
+        function api (key, value, attributes) {
36
+            var result;
37
+            if (typeof document === 'undefined') {
38
+                return;
39
+            }
40
+
41
+            // Write
42
+
43
+            if (arguments.length > 1) {
44
+                attributes = extend({
45
+                    path: '/'
46
+                }, api.defaults, attributes);
47
+
48
+                if (typeof attributes.expires === 'number') {
49
+                    var expires = new Date();
50
+                    expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
51
+                    attributes.expires = expires;
52
+                }
53
+
54
+                try {
55
+                    result = JSON.stringify(value);
56
+                    if (/^[\{\[]/.test(result)) {
57
+                        value = result;
58
+                    }
59
+                } catch (e) {}
60
+
61
+                if (!converter.write) {
62
+                    value = encodeURIComponent(String(value))
63
+                        .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
64
+                } else {
65
+                    value = converter.write(value, key);
66
+                }
67
+
68
+                key = encodeURIComponent(String(key));
69
+                key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
70
+                key = key.replace(/[\(\)]/g, escape);
71
+
72
+                return (document.cookie = [
73
+                    key, '=', value,
74
+                    attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE
75
+                    attributes.path    && '; path=' + attributes.path,
76
+                    attributes.domain  && '; domain=' + attributes.domain,
77
+                    attributes.secure ? '; secure' : ''
78
+                ].join(''));
79
+            }
80
+
81
+            // Read
82
+
83
+            if (!key) {
84
+                result = {};
85
+            }
86
+
87
+            // To prevent the for loop in the first place assign an empty array
88
+            // in case there are no cookies at all. Also prevents odd result when
89
+            // calling "get()"
90
+            var cookies = document.cookie ? document.cookie.split('; ') : [];
91
+            var rdecode = /(%[0-9A-Z]{2})+/g;
92
+            var i = 0;
93
+
94
+            for (; i < cookies.length; i++) {
95
+                var parts = cookies[i].split('=');
96
+                var cookie = parts.slice(1).join('=');
97
+
98
+                if (cookie.charAt(0) === '"') {
99
+                    cookie = cookie.slice(1, -1);
100
+                }
101
+
102
+                try {
103
+                    var name = parts[0].replace(rdecode, decodeURIComponent);
104
+                    cookie = converter.read ?
105
+                        converter.read(cookie, name) : converter(cookie, name) ||
106
+                    cookie.replace(rdecode, decodeURIComponent);
107
+
108
+                    if (this.json) {
109
+                        try {
110
+                            cookie = JSON.parse(cookie);
111
+                        } catch (e) {}
112
+                    }
113
+
114
+                    if (key === name) {
115
+                        result = cookie;
116
+                        break;
117
+                    }
118
+
119
+                    if (!key) {
120
+                        result[name] = cookie;
121
+                    }
122
+                } catch (e) {}
123
+            }
124
+
125
+            return result;
126
+        }
127
+
128
+        api.set = api;
129
+        api.get = function (key) {
130
+            return api(key);
131
+        };
132
+        api.getJSON = function () {
133
+            return api.apply({
134
+                json: true
135
+            }, [].slice.call(arguments));
136
+        };
137
+        api.defaults = {};
138
+
139
+        api.remove = function (key, attributes) {
140
+            api(key, '', extend(attributes, {
141
+                expires: -1
142
+            }));
143
+        };
144
+
145
+        api.withConverter = init;
146
+
147
+        return api;
148
+    }
149
+
150
+    return init(function () {});
151
+}));

+ 4195
- 0
test_repository/app/scripts/moment.min.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 14
- 0
test_repository/app/scripts/webcomponents.min.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 143
- 0
test_repository/gulpfile.js Целия файл

@@ -0,0 +1,143 @@
1
+const PATHS = {
2
+    //Input Paths
3
+    admin_in: 'app/admin/**/*',
4
+    elements_in: 'app/elements/elements.html',
5
+    html_in: 'app/*.html',
6
+    img_in: 'app/images/**/*',
7
+    scripts_in: 'app/scripts/**/*',
8
+
9
+    //Output Paths
10
+    admin_out: 'dist/admin',
11
+    elements_out: 'dist/elements',
12
+    html_out: 'dist/',
13
+    img_out: 'dist/images',
14
+    scripts_out: 'dist/scripts'
15
+};
16
+
17
+var gulp = require('gulp');
18
+var crisper = require('gulp-crisper');
19
+var minify = require('gulp-htmlmin');
20
+var gulpif = require('gulp-if');
21
+var gulpSeq = require('gulp-sequence');
22
+var uglify = require('gulp-uglify');
23
+var vulcanize = require('gulp-vulcanize');
24
+
25
+var del = require('del');
26
+
27
+gulp.task('build', ['default'], function () {
28
+    return gulp.src(['dist']).pipe(gulp.dest('_build'));
29
+});
30
+
31
+/**
32
+ * Default Gulp task
33
+ *
34
+ * Runs a clean (to clear out /build) and then builds the project
35
+ */
36
+gulp.task('default', gulpSeq('clean', 'build'));
37
+
38
+/**
39
+ * Gulp clean task
40
+ *
41
+ * Cleans out /build by deleting the entire folder using 'del'
42
+ */
43
+gulp.task('clean', function () {
44
+    return del(['dist']);
45
+});
46
+
47
+/**
48
+ * Gulp build task
49
+ *
50
+ * Builds the project by doing the following:
51
+ * 1) Vulcanizing HTML imports by calling helper method
52
+ * 2) Moving all HTML files
53
+ * ...
54
+ * TODO: More
55
+ */
56
+gulp.task('build', ['vulcanize', 'moveAdmin', 'moveHTML', 'moveImg', 'moveScripts'], function () {
57
+
58
+});
59
+
60
+/** Gulp move admin task. Moves the /admin folders to dist */
61
+gulp.task('moveAdmin', function () {
62
+    return moveAdmin();
63
+});
64
+
65
+/** Helper method to move all HTML files in /app/admin to /dist/admin */
66
+function moveAdmin() {
67
+    return gulp.src(PATHS.admin_in).pipe(gulpif('*.html', minifyHTML())).pipe(gulp.dest(PATHS.admin_out));
68
+}
69
+
70
+/** Gulp move HTML task. Moves all HTML files in /app by calling helper method */
71
+gulp.task('moveHTML', function () {
72
+    return moveHTML();
73
+});
74
+
75
+/** Helper method to move all HTML files /app to the /dist dir */
76
+function moveHTML() {
77
+    return gulp.src(PATHS.html_in).pipe(gulpif('*.html', minifyHTML())).pipe(gulp.dest(PATHS.html_out));
78
+}
79
+
80
+/** Gulp move images task. Moves given images to /dist by calling helper method */
81
+gulp.task('moveImg', function () {
82
+    return moveImg();
83
+});
84
+
85
+/** Helper method to move all images in /app to the /dist dir */
86
+function moveImg() {
87
+    return gulp.src(PATHS.img_in).pipe(gulp.dest(PATHS.img_out));
88
+}
89
+
90
+/** Gulp move scripts task. Moves scripts to /dist by calling helper method */
91
+gulp.task('moveScripts', function () {
92
+    return moveScripts();
93
+});
94
+
95
+/** Helper method to move all scripts in /app to the /dist dir */
96
+function moveScripts() {
97
+    return gulp.src(PATHS.scripts_in).pipe(gulp.dest(PATHS.scripts_out));
98
+}
99
+
100
+/** Gulp vulcanize task. Calls helper method to vulcanize HTML imports */
101
+gulp.task('vulcanize', function () {
102
+    return vulcanizeImports();
103
+});
104
+
105
+/**
106
+ * Vulcanize the given HTML file.
107
+ *
108
+ * This will use the 'vulcanize' tool to inline all HTML imports in a given file (reduces network activity)
109
+ *
110
+ * Order of Events:
111
+ * 1) Pipes to source HTML file to 'vulcanize' with args. Inlines the HTML/CSS/JS and strips all comments (except @license decelerations)
112
+ * 2) Pipes to 'crisper' to separate the JS into it's own file for CSP compliance and reduction of HTML parser load
113
+ * 3) Pipes to 'HTMLminifier' to minify the HTML code (remove whitespace, etc). ONLY MINIFIES IF .HTML
114
+ * 4) Pipes to 'uglify' to minify the JS code. ONLY MINIFIES IF .JS
115
+ * 5) Pipes the output to the specified directory
116
+ */
117
+function vulcanizeImports() {
118
+    return gulp.src(PATHS.elements_in)
119
+        .pipe(vulcanizeHTML())
120
+        .pipe(crisper())
121
+        .pipe(gulpif('*.html', minifyHTML()))
122
+        .pipe(gulpif('*.js', uglifyJS()))
123
+        .pipe(gulp.dest(PATHS.elements_out))
124
+}
125
+
126
+function vulcanizeHTML() {
127
+    console.log("=== VULCANIZE ===");
128
+    return vulcanize({
129
+        inlineScripts: true,
130
+        inlineCss: true,
131
+        stripComments: true
132
+    });
133
+}
134
+
135
+function minifyHTML() {
136
+    console.log("=== MINIFY ===");
137
+    return minify({collapseWhitespace: true})
138
+}
139
+
140
+function uglifyJS() {
141
+    console.log("=== UGLIFY ===");
142
+    return uglify({preserveComments: 'some'});
143
+}

+ 36
- 0
test_repository/package.json Целия файл

@@ -0,0 +1,36 @@
1
+{
2
+  "name": "zebra_emc_tools_portal",
3
+  "version": "0.0.1",
4
+  "description": "Zebra EMC Engineering Tools Portal",
5
+  "main": "server.js",
6
+  "scripts": {
7
+    "start-test": "node server/server.js -l -t",
8
+    "test": "echo \"Error: no test specified\" && exit 1"
9
+  },
10
+  "author": "Kyle Crowley <nfgr84@zebra.com>",
11
+  "license": "UNLICENSED",
12
+  "devDependencies": {
13
+    "del": "^2.2.1",
14
+    "gulp": "^3.9.1",
15
+    "gulp-crisper": "^1.1.0",
16
+    "gulp-htmlmin": "^2.0.0",
17
+    "gulp-if": "^2.0.1",
18
+    "gulp-sequence": "^0.4.5",
19
+    "gulp-uglify": "^1.5.4",
20
+    "gulp-vulcanize": "^6.1.0"
21
+  },
22
+  "dependencies": {
23
+    "bcryptjs": "^2.3.0",
24
+    "body-parser": "^1.15.2",
25
+    "express": "^4.14.0",
26
+    "ip": "^1.1.3",
27
+    "joi": "^9.0.4",
28
+    "jsonwebtoken": "^7.1.9",
29
+    "minimist": "^1.2.0",
30
+    "mongoose": "^4.5.8",
31
+    "node-rest-client": "^2.0.1",
32
+    "node-uuid": "^1.4.7",
33
+    "url": "^0.11.0",
34
+    "winston": "^2.2.0"
35
+  }
36
+}

+ 39
- 0
test_repository/server/config/index.js Целия файл

@@ -0,0 +1,39 @@
1
+/* Configuration file for the server */
2
+
3
+var config = {};
4
+
5
+config.db = {};
6
+config.db.tools = {};
7
+
8
+config.jira = {};
9
+config.jira.api = {};
10
+config.jira.toolSupport = {};
11
+
12
+//Server Config
13
+config.httpPort = 8081;
14
+config.httpsPort = 8443;
15
+config.secretKey = "38107824-cdc7-4769-8aa2-1eac80993245";
16
+
17
+//MongoDB Config
18
+config.db.username = "portalServer";
19
+config.db.password = "T00lsTeam";
20
+config.db.tools.path = "mongodb://localhost:27017/portal";
21
+
22
+//JIRA Config
23
+//URL to use for JIRA REST API calls (dev/prod)
24
+//Prod = https://jiraemv.zebra.com
25
+//Dev = https://dev-jiraemv.zebra.lan OR https://10.183.4.31
26
+//config.jira.url = "10.183.4.31";
27
+config.jira.url = "dev-jiraemv.zebra.lan";
28
+
29
+//API endpoints
30
+config.jira.api.user = "/rest/api/2/user";
31
+config.jira.api.login = "/rest/auth/1/session";
32
+config.jira.api.issue = "/rest/api/2/issue";
33
+
34
+//Project/Issue
35
+config.jira.toolSupport.projectId = 10300;
36
+config.jira.toolSupport.requestId = 11200;
37
+config.jira.toolSupport.changeId = 11201;
38
+
39
+module.exports = config;

+ 85
- 0
test_repository/server/controllers/admin.js Целия файл

@@ -0,0 +1,85 @@
1
+/* Controller for /admin resources */
2
+
3
+var jwt = require('jsonwebtoken');
4
+var ip = require('ip');
5
+
6
+var config = require('../config');
7
+var Admin = require('../models/Admin');
8
+var logger = require('../utils/logger');
9
+var passCrypt = require('../utils/passCrypt');
10
+var validation = require('../validation');
11
+
12
+/**
13
+ * Function for creating an admin in the DB.
14
+ * @param req The request object sent over from the route.
15
+ * @param res The response object sent over from the route.
16
+ */
17
+exports.createAdmin = function (req, res) {
18
+    var username = req.body.username;
19
+    var password = req.body.password;
20
+
21
+    //Check that a valid username/password was supplied
22
+    var valid = validation.validateUserPass(res, {username: username, password: password});
23
+
24
+    //If valid
25
+    if (valid) {
26
+        //Make a new Admin obj with the given creds
27
+        var admin = new Admin({
28
+            _id: username,
29
+            username: username,
30
+            //Hash the password using the bcrypt util
31
+            password: passCrypt.generateHash(password)
32
+        });
33
+
34
+        //Save the new admin in the DB
35
+        admin.save(function (err) {
36
+            if (err) {
37
+                logger.info("[" + ip.address() + "] New admin creation failed. Admin username= " + username);
38
+
39
+                res.status(422).send({message: "New admin creation failed. Admin username= " + username, errors: err});
40
+            }
41
+
42
+            else {
43
+                logger.info("[" + ip.address() + "] New admin creation succeeded. Admin username= " + username);
44
+
45
+                res.status(201).send({added: true});
46
+            }
47
+        });
48
+    }
49
+};
50
+
51
+/**
52
+ * Function for admin login
53
+ * @param req The request object sent over from the route.
54
+ * @param res The response object sent over from the route.
55
+ */
56
+exports.login = function (req, res) {
57
+    //Find the admin in the DB that matches the supplied username
58
+    Admin.findOne({username: req.body.username}, function (err, admin) {
59
+        //Following if statements check for general errors/bad username
60
+        if (err)
61
+            res.status(400).send({errors: err});
62
+
63
+        if (!admin) {
64
+            res.status(404).send({success: false, errors: "Authentication failed. User not found."});
65
+        }
66
+
67
+        //Otherwise, a matching admin was found
68
+        else if (admin) {
69
+            //If the passwords match (i.e. user gave the correct password)
70
+            if (passCrypt.checkPass(req.body.password, admin.password)) {
71
+                //Create a token
72
+                var token = jwt.sign({username: admin.username}, config.secretKey, {
73
+                    expiresIn: "1d"
74
+                });
75
+
76
+                res.status(200).send({success: true, token: token});
77
+            }
78
+
79
+            //Passwords don't match (i.e. user gave the WRONG password)
80
+            else {
81
+                res.status(401).send({success: false, errors: "Authentication failed. Incorrect password."});
82
+            }
83
+        }
84
+    });
85
+};

+ 70
- 0
test_repository/server/controllers/news.js Целия файл

@@ -0,0 +1,70 @@
1
+var ip = require('ip');
2
+
3
+var NewsItem = require('../models/NewsItem');
4
+var logger = require('../utils/logger');
5
+var validation = require('../validation');
6
+
7
+/*      CREATE (GUARDED)        */
8
+
9
+/**
10
+ * Function for creating a new news item in the DB.
11
+ * @param req The request object sent over from the route.
12
+ * @param res The response object sent over from the route.
13
+ */
14
+exports.createNewsItem = function (req, res) {
15
+    //Get the title and contents from the request body
16
+    var title = req.body.title;
17
+    var contents = req.body.contents;
18
+
19
+    //Check that valid params were supplied
20
+    var valid = validation.validateNewsItem(res, {title: title, contents: contents});
21
+
22
+    //If so, continue
23
+    if (valid) {
24
+        //Make a new NewsItem object with the given info
25
+        var newNews = new NewsItem({
26
+            title: title,
27
+            contents: contents
28
+        });
29
+
30
+        //Save the new news item in the DB
31
+        newNews.save(function (err) {
32
+            //If there was an error while saving
33
+            if (err) {
34
+                logger.info("[" + ip.address() + "] New news item creation failed.");
35
+                res.status(422).send({created: false, errors: err});
36
+            }
37
+
38
+            //Otherwise, new item was added successfully
39
+            else {
40
+                logger.info("[" + ip.address() + "] New news item creation succeeded.");
41
+                res.status(201).send({created: true});
42
+            }
43
+        });
44
+    }
45
+
46
+    //Else condition is handled in validation
47
+};
48
+
49
+/*      READ (UNGUARDED)        */
50
+
51
+/**
52
+ * Function for getting all news items from the DB.
53
+ * @param req The request object sent over from the route.
54
+ * @param res The response object sent over from the route.
55
+ */
56
+exports.getAll = function (req, res) {
57
+    //Find all news item, but only return the title, contents and date.
58
+    NewsItem.find({}, {_id: false, title: true, contents: true, date: true}, function (err, news) {
59
+        //If there was an error during the find, send back an error.
60
+        if (err)
61
+            res.status(404).send({message: "Could not retrieve News.", errors: err});
62
+        //Otherwise, send back the items.
63
+        else
64
+            res.status(200).send(news);
65
+    });
66
+};
67
+
68
+/*      UPDATE (GUARDED)        */
69
+
70
+/*      DELETE (GUARDED)        */

+ 267
- 0
test_repository/server/controllers/tool.js Целия файл

@@ -0,0 +1,267 @@
1
+var ip = require('ip');
2
+
3
+var Tool = require('../models/Tool');
4
+var logger = require('../utils/logger');
5
+var validation = require('../validation');
6
+
7
+/*      CREATE (GUARDED)     */
8
+
9
+/**
10
+ * Function for creating a new Tool in the DB.
11
+ * @param req The request object sent over from the route.
12
+ * @param res The response object sent over from the route.
13
+ */
14
+exports.createTool = function (req, res) {
15
+    //Get the toolId and toolName from the body of the request
16
+    var toolId = parseInt(req.body.toolId);
17
+    var toolName = req.body.toolName;
18
+
19
+    //Check that the tool id and name are valid
20
+    var valid = validation.validateToolIdName(res, {toolId: toolId, toolName: toolName});
21
+
22
+    //Only proceed if the parameters were valid.
23
+    //Bad params will be handled in helper method
24
+    if (valid) {
25
+        //Declare a new Tool object using the model
26
+        var newTool = new Tool({
27
+            _id: toolId,
28
+            id: toolId,
29
+            name: toolName,
30
+
31
+            about: [],
32
+            training: [],
33
+            links: []
34
+        });
35
+
36
+        //Save to MongoDB
37
+        newTool.save(function (err) {
38
+            //If there was an error while saving, send it back to the client
39
+            if (err) {
40
+                var message = "";
41
+
42
+                //In this case, 11000 is the error code for a duplicate key
43
+                if (err.code == 11000)
44
+                    message = "New tool creation failed. Duplicate tool id. Please check all inputs.";
45
+                //Otherwise, just set the message = error message from mongoose
46
+                else
47
+                    message = err.errmsg;
48
+
49
+                //Send an HTTP 422 (Unprocessable Entity) with the error message.
50
+                //Why HTTP 422: HTTP 400 is bad syntax. Correct syntax was supplied, but something went wrong during the request
51
+                res.status(422).send({created: false, message: message, errors: err});
52
+
53
+                //Log this request
54
+                logger.info("[" + ip.address() + "] New tool creation failed. Tool id= " + toolId + " Reason: " + message);
55
+            }
56
+
57
+            //Otherwise, the addition was successful
58
+            else {
59
+                //Send an HTTP 201 (Created)
60
+                res.status(201).send({created: true});
61
+
62
+                //Log this request
63
+                logger.info("[" + ip.address() + "] New tool creation succeeded. toolId= " + toolId);
64
+            }
65
+        });
66
+    }
67
+};
68
+
69
+/*      READ (UNGUARDED)        */
70
+
71
+/**
72
+ * Get a particular tool.
73
+ * @param req The request object sent over from the route.
74
+ * @param res The response object sent over from the route.
75
+ */
76
+exports.getTool = function (req, res) {
77
+    //Get the toolId from the body of the request
78
+    var toolId = parseInt(req.params.toolId);
79
+
80
+    //Check that the tool id is valid.
81
+    var valid = validation.validateToolId(res, {toolId: toolId});
82
+
83
+    //Only proceed if the parameters were valid.
84
+    //Bad params will be handled in helper method
85
+    if (valid) {
86
+        //Look for a matching document in the DB
87
+        Tool.findOne({'id': toolId}, function (err, tool) {
88
+            //Catch any general errors and send them back to the client
89
+            if (err)
90
+                res.status(404).send({message: "Error finding matching document.", errors: err});
91
+
92
+            //Otherwise, send back the matching tool
93
+            else {
94
+                //If the matching matching tool could not be found
95
+                if (tool == undefined || tool == null)
96
+                    res.status(404).send({message: "Could not find matching tool.", errors: err});
97
+                //Otherwise, send back the matching tool
98
+                else
99
+                    res.status(200).send(tool);
100
+            }
101
+        });
102
+    }
103
+};
104
+
105
+/**
106
+ * Function for getting a particular tool's section.
107
+ * @param req The request object sent over from the route.
108
+ * @param res The response object sent over from the route.
109
+ */
110
+exports.getToolSection = function (req, res) {
111
+    //Get the tool id and section name from the params of the request
112
+    var toolId = parseInt(req.params.toolId);
113
+    var sectionName = req.params.sectionName.toLowerCase();
114
+
115
+    //Check that the tool id and section name is valid.
116
+    var valid = validation.validateToolAndSection(res, {toolId: toolId, sectionName: sectionName});
117
+
118
+    //Only proceed if the parameters were valid.
119
+    //Bad params will be handled in helper method
120
+    if (valid) {
121
+        //Look for a matching document in the DB
122
+        Tool.findOne({'id': toolId}, function (err, tool) {
123
+            //Catch any general errors and send them back to the client
124
+            if (err)
125
+                res.status(404).send({message: "Error finding matching document.", errors: err});
126
+
127
+            //Otherwise, send back the matching tool
128
+            else {
129
+                //If the matching toolId + sectionName combo was not found, send back a 404
130
+                if (tool[sectionName] == undefined || tool[sectionName] == null)
131
+                    res.status(404).send({
132
+                        message: "Could not find matching toolId and sectionName combo.",
133
+                        errors: err
134
+                    });
135
+
136
+                //Otherwise, send back the matching section
137
+                else
138
+                    res.status(200).send(tool[sectionName]);
139
+            }
140
+        });
141
+    }
142
+};
143
+
144
+/**
145
+ * Function for getting a particular item from a tool's section.
146
+ * @param req The request object sent over from the route.
147
+ * @param res The response object sent over from the route.
148
+ */
149
+exports.getToolSectionItem = function (req, res) {
150
+    //Get the tool id and section name from the params of the request
151
+    var toolId = parseInt(req.params.toolId);
152
+    var sectionName = req.params.sectionName.toLowerCase();
153
+    var itemNum = parseInt(req.params.itemNum);
154
+
155
+    //Check that the tool id, section name and item number are valid
156
+    var valid = validation.validateToolSectionItem(res, {toolId: toolId, sectionName: sectionName, itemNum: itemNum});
157
+
158
+    if (valid) {
159
+        Tool.findOne({'id': toolId}, function (err, tool) {
160
+            //Catch any general errors and send them back to the client
161
+            if (err)
162
+                res.status(404).send({errors: err});
163
+
164
+            //Otherwise, there was (or was not) a matching document
165
+            else {
166
+                //If the matching toolId + sectionName combo was not found, send back a 404
167
+                if (tool[sectionName] == undefined || tool[sectionName] == null)
168
+                    res.status(404).send({
169
+                        message: "Could not find matching toolId and sectionName combo.",
170
+                        errors: err
171
+                    });
172
+
173
+                //Otherwise, send back the item
174
+                else {
175
+                    if (tool[sectionName][itemNum] == undefined || tool[sectionName][itemNum] == null)
176
+                        res.status(404).send({message: "Could not find given item.", errors: err});
177
+                    else
178
+                        res.status(200).send(tool[sectionName][itemNum]);
179
+                }
180
+            }
181
+        });
182
+    }
183
+};
184
+
185
+/**
186
+ * Function for getting the count of items in a tool's section.
187
+ * @param req The request object sent over from the route.
188
+ * @param res The response object sent over from the route.
189
+ */
190
+exports.getToolSectionItemCount = function (req, res) {
191
+    //Get the tool id and section name from the params of the request
192
+    var toolId = parseInt(req.params.toolId);
193
+    var sectionName = req.params.sectionName.toLowerCase();
194
+
195
+    //Check that the tool id and section name are valid.
196
+    var valid = validation.validateToolAndSection(res, {toolId: toolId, sectionName: sectionName});
197
+
198
+    //Only proceed if the parameters were valid.
199
+    //Bad params will be handled in helper method
200
+    if (valid) {
201
+        //Look for a matching document in the DB
202
+        Tool.findOne({'id': toolId}, function (err, tool) {
203
+            //Catch any general errors and send them back to the client
204
+            if (err)
205
+                res.status(404).send({message: "Error finding matching document.", errors: err});
206
+
207
+            //Otherwise, send back the matching tool
208
+            else {
209
+                //If the matching toolId + sectionName combo was not found, send back a 404
210
+                if (tool[sectionName] == undefined || tool[sectionName] == null)
211
+                    res.status(404).send({
212
+                        message: "Could not find matching toolId and sectionName combo.",
213
+                        errors: err
214
+                    });
215
+                //Otherwise, send back the matching section
216
+                else
217
+                    res.status(200).send({count: tool[sectionName].length});
218
+            }
219
+        });
220
+    }
221
+};
222
+
223
+/*      UPDATE (GUARDED)      */
224
+
225
+/**
226
+ * Function for adding an item to a tool's section
227
+ * @param req The request object sent over from the route.
228
+ * @param res The response object sent over from the route.
229
+ */
230
+//TODO: Add this
231
+exports.addItem = function (req, res) {
232
+};
233
+
234
+/*      DELETE (GUARDED)      */
235
+
236
+/**
237
+ * Function for deleting an item from a tool's section.
238
+ * @param req The request object sent over from the route.
239
+ * @param res The response object sent over from the route.
240
+ */
241
+//TODO: Add this
242
+exports.deleteItem = function (req, res) {
243
+};
244
+
245
+/**
246
+ * Function for deleting a tool from the DB.
247
+ * @param req The request object sent over from the route.
248
+ * @param res The response object sent over from the route.
249
+ */
250
+exports.deleteTool = function (req, res) {
251
+    //Get the tool id and section name from the params of the request
252
+    var toolId = parseInt(req.params.toolId);
253
+
254
+    //Check that the tool id is valid.
255
+    var valid = validation.validateToolId(res, {toolId: toolId});
256
+
257
+    if (valid) {
258
+        Tool.remove({_id: toolId}, function (err) {
259
+            if (err)
260
+                res.status(404).send({deleted: false, message: "Error finding matching document.", errors: err});
261
+            else
262
+                res.status(200).send({deleted: true});
263
+        });
264
+    }
265
+};
266
+
267
+

+ 85
- 0
test_repository/server/controllers/tools.js Целия файл

@@ -0,0 +1,85 @@
1
+var ip = require('ip');
2
+
3
+var Tool = require('../models/Tool');
4
+var logger = require('../utils/logger');
5
+var validation = require('../validation');
6
+
7
+/*      CREATE (GUARDED)        */
8
+
9
+/*      READ (UNGUARDED)        */
10
+
11
+/**
12
+ * Function for getting all the tools from the DB.
13
+ * @param req The request object sent over from the route.
14
+ * @param res The response object sent over from the route.
15
+ */
16
+exports.getTools = function (req, res) {
17
+    //Find all tools
18
+    Tool.find({}, function (err, tools) {
19
+        if (err)
20
+            res.status(404).send({message: "Could not retrieve Tools.", errors: err});
21
+        else
22
+            res.status(200).send(tools);
23
+    });
24
+};
25
+
26
+/**
27
+ * Function for getting info for all the tools (ids and names)
28
+ * @param req The request object sent over from the route.
29
+ * @param res The response object sent over from the route.
30
+ */
31
+exports.getToolInfo = function (req, res) {
32
+    //Find all tools, only returning the id and name for each
33
+    Tool.find({}, {_id: false, id: true, name: true}, function (err, tools) {
34
+        if (err)
35
+            res.status(404).send({message: "Could not retrieve Tools.", errors: err});
36
+        else
37
+            res.status(200).send(tools);
38
+    });
39
+};
40
+
41
+/**
42
+ * Function for getting all the tool names
43
+ * @param req The request object sent over from the route.
44
+ * @param res The response object sent over from the route.
45
+ */
46
+exports.getToolNames = function (req, res) {
47
+    //Find all tools, only returning the name
48
+    Tool.find({}, {_id: false, name: true}, function (err, tools) {
49
+        var toolsMap = [];
50
+
51
+        tools.forEach(function (tool) {
52
+            toolsMap.push(tool.name);
53
+        });
54
+
55
+        if (err)
56
+            res.status(404).send({message: "Could not retrieve Tools.", errors: err});
57
+        else
58
+            res.status(200).send(toolsMap);
59
+    });
60
+};
61
+
62
+/**
63
+ * Function for getting all the tool ids
64
+ * @param req The request object sent over from the route.
65
+ * @param res The response object sent over from the route.
66
+ */
67
+exports.getToolIds = function (req, res) {
68
+    //Find all the tools, only returning the id
69
+    Tool.find({}, {_id: false, id: true}, function (err, tools) {
70
+        var toolsMap = [];
71
+
72
+        tools.forEach(function (tool) {
73
+            toolsMap.push(tool.id);
74
+        });
75
+
76
+        if (err)
77
+            res.status(404).send({message: "Could not retrieve Tools.", errors: err});
78
+        else
79
+            res.status(200).send(toolsMap);
80
+    });
81
+};
82
+
83
+/*      UPDATE (GUARDED)        */
84
+
85
+/*      DELETE (GUARDED)        */

+ 31
- 0
test_repository/server/guards/adminGuard.js Целия файл

@@ -0,0 +1,31 @@
1
+/* Guard that only allows admins to access whatever route the guard is attached to */
2
+
3
+var jwt = require('jsonwebtoken');
4
+
5
+var config = require('../config');
6
+
7
+exports.adminGuard = function (req, res, next) {
8
+    //Check for a token in the body, params or headers
9
+    var token = req.body.token || req.query.token || req.headers['x-access-token'];
10
+
11
+    //Check that a token was actually supplied
12
+    if (token) {
13
+        //Now check that the token is actually legitimate
14
+        jwt.verify(token, config.secretKey, function (err, decoded) {
15
+            //If there was an error during the verification, the user is not authenticated
16
+            if (err)
17
+                return res.status(403).send({success: false, error: "Failed to authenticate token."});
18
+
19
+            //Otherwise, the user is valid
20
+            else {
21
+                req.decoded = decoded;
22
+                next();
23
+            }
24
+        });
25
+    }
26
+
27
+    //Otherwise, do not proceed and send back an error
28
+    else {
29
+        return res.status(403).send({success: false, error: "No token provided in request."});
30
+    }
31
+};

+ 28
- 0
test_repository/server/models/Admin.js Целия файл

@@ -0,0 +1,28 @@
1
+/* Model for an Admin in the DB */
2
+
3
+var mongoose = require('mongoose');
4
+var Schema = mongoose.Schema;
5
+
6
+var AdminSchema = new Schema(
7
+    {
8
+        _id: String,
9
+
10
+        username: {
11
+            type: String,
12
+            required: true
13
+        },
14
+
15
+        password: {
16
+            type: String,
17
+            required: true
18
+        },
19
+
20
+        name: String,
21
+        description: String
22
+    },
23
+    {
24
+        collection: 'admins'
25
+    }
26
+);
27
+
28
+module.exports = mongoose.model('Admin', AdminSchema);

+ 22
- 0
test_repository/server/models/NewsItem.js Целия файл

@@ -0,0 +1,22 @@
1
+/* Model for a News item in the DB */
2
+
3
+var mongoose = require('mongoose');
4
+var Schema = mongoose.Schema;
5
+
6
+var NewsItemSchema = new Schema(
7
+    {
8
+        title: String,
9
+
10
+        contents: String,
11
+
12
+        date: {
13
+            type: Date,
14
+            default: Date.now
15
+        }
16
+    },
17
+    {
18
+        collection: 'news'
19
+    }
20
+);
21
+
22
+module.exports = mongoose.model('NewsItem', NewsItemSchema);

+ 21
- 0
test_repository/server/models/Tool.js Целия файл

@@ -0,0 +1,21 @@
1
+/* Model for an Tool in the DB */
2
+
3
+var mongoose = require('mongoose');
4
+var Schema = mongoose.Schema;
5
+
6
+var ToolSchema = new Schema(
7
+    {
8
+        _id: Number,
9
+        id: Number,
10
+        name: String,
11
+
12
+        about: Array,
13
+        training: Array,
14
+        links: Array
15
+    },
16
+    {
17
+        collection: 'tools'
18
+    }
19
+);
20
+
21
+module.exports = mongoose.model('Tool', ToolSchema);

+ 20
- 0
test_repository/server/routes/admin/guarded.js Целия файл

@@ -0,0 +1,20 @@
1
+/* API Routes for /api/admin/guarded (Guarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+var adminCtrl = require('../../controllers/admin');
6
+var adminGuard = require('../../guards/adminGuard');
7
+
8
+//Middleware to use for every request
9
+router.use(function (req, res, next) {
10
+    //Use the admin guard
11
+    adminGuard.adminGuard(req, res, next);
12
+});
13
+
14
+router.route('/')
15
+//Creates a new admin
16
+    .post(function (req, res) {
17
+        adminCtrl.createAdmin(req, res)
18
+    });
19
+
20
+module.exports = router;

+ 8
- 0
test_repository/server/routes/admin/index.js Целия файл

@@ -0,0 +1,8 @@
1
+/* API Routes for /api/admin/... (Guarded/Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+router.use('/', require('./unguarded'));
6
+router.use('/guarded', require('./guarded'));
7
+
8
+module.exports = router;

+ 19
- 0
test_repository/server/routes/admin/unguarded.js Целия файл

@@ -0,0 +1,19 @@
1
+/* API Routes for /api/admin/ (Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+var adminCtrl = require('../../controllers/admin');
6
+
7
+//Middleware to use for every request
8
+router.use(function (req, res, next) {
9
+    next();
10
+});
11
+
12
+router.route('/login')
13
+
14
+//Authenticates admin
15
+    .post(function (req, res) {
16
+        adminCtrl.login(req, res);
17
+    });
18
+
19
+module.exports = router;

+ 23
- 0
test_repository/server/routes/admins/index.js Целия файл

@@ -0,0 +1,23 @@
1
+//TODO: Modify this to use controllers/guards
2
+/* API Routes for /api/admins/ (Unguarded Routes) */
3
+
4
+var router = require('express').Router();
5
+var Admin = require('../../models/Admin');
6
+
7
+//Middleware to use for every request
8
+router.use(function (req, res, next) {
9
+    next();
10
+});
11
+
12
+router.route('/info')
13
+//Gets all admin names/descriptions
14
+    .get(function (req, res) {
15
+        Admin.find({}, 'name description', function (err, admins) {
16
+            if (err)
17
+                res.send({errors: err});
18
+            else
19
+                res.status(200).send(admins);
20
+        })
21
+    });
22
+
23
+module.exports = router;

+ 37
- 0
test_repository/server/routes/index.js Целия файл

@@ -0,0 +1,37 @@
1
+//Top-Level Router
2
+//Mounts all the individual routes on a single router that the app can then use
3
+
4
+var router = require('express').Router();
5
+
6
+//NOTE: Each 'sub-router' should go below and use the following convention:
7
+//router.use(<PATH>, require(<MODULE>));
8
+//where PATH = sub-path of main router
9
+//where MODULE = the module containing the exported sub-router
10
+
11
+//Mount the 'admin' router onto the API Router
12
+//URL will look like this: '.../api/admin/...'
13
+router.use('/admin', require('./admin'));
14
+
15
+//Mount the 'admins' router onto the API Router
16
+//URL will look like this: '.../api/admins/...'
17
+router.use('/admins', require('./admins'));
18
+
19
+//Mount the 'news' router onto the API Router
20
+//URL will look like this: '.../api/news/...'
21
+router.use('/news', require('./news'));
22
+
23
+//Mount the 'tool' router onto the API Router
24
+//URL will look like this: '.../api/tool/...'
25
+router.use('/tool', require('./tool'));
26
+
27
+//Mount the 'tools' router onto the API Router
28
+//URL will look like this: '.../api/tools/...'
29
+router.use('/tools', require('./tools'));
30
+
31
+//Mount the 'jira' router on the API Router
32
+//URL will look like this: '.../api/jira/...'
33
+router.use('/jira', require('./jira'));
34
+
35
+
36
+//!IMPORTANT! We need to export the router so our app can use the router
37
+module.exports = router;

+ 363
- 0
test_repository/server/routes/jira/index.js Целия файл

@@ -0,0 +1,363 @@
1
+//TODO: Modify this to use controllers/guards
2
+
3
+/* API Routes for /api/jira/ (Unguarded Routes) */
4
+var config = require('../../config');
5
+var logger = require('../../utils/logger');
6
+
7
+var fs = require('fs');
8
+var ip = require('ip');
9
+var url = require('url');
10
+
11
+var Client = require('node-rest-client').Client;
12
+var JiraRestClient = new Client();
13
+
14
+var router = require('express').Router();
15
+
16
+//Middleware to use for every request
17
+router.use(function (req, res, next) {
18
+    next();
19
+});
20
+
21
+router.route('/issue')
22
+
23
+//Create a new issue in JIRA
24
+    .post(function (req, res) {
25
+        //Check that a cookie exists first and foremost. This resource requires authentication.
26
+        if (req.body.cookie) {
27
+            //Also check that the summary, description and toolId were supplied
28
+            if (req.body.summary && req.body.description && req.body.toolId && req.body.issueType) {
29
+                //At this point, it is safe to attempt to retrieve the data
30
+                var cookie = req.body.cookie,
31
+                    summary = req.body.summary,
32
+                    description = req.body.description,
33
+                    toolId = req.body.toolId,
34
+                    issueType = req.body.issueType;
35
+
36
+                //Arguments to the POST request
37
+                var args = {
38
+                    data: {
39
+                        fields: {
40
+                            project: {
41
+                                //Project ID SHOULD NOT change. If it does change in JIRA, update the config
42
+                                id: config.jira.toolSupport.projectId
43
+                            },
44
+                            issuetype: {
45
+                                //Issuetype ID SHOULD NOT change. If it does change in JIRA, update the config
46
+                                id: issueType
47
+                            },
48
+                            //Summary that was passed in by the user
49
+                            summary: summary,
50
+                            customfield_10501: {
51
+                                //JIRA REQUIRES that this be a string and not a number despite the fact that it is represented as a number in JSON
52
+                                //Regardless, cast to a string. If it was supplied as a string already, this should have no effect
53
+                                id: "" + toolId
54
+                            },
55
+                            //Description that was passed in by the user
56
+                            description: description
57
+                        }
58
+                    },
59
+                    headers: {
60
+                        cookie: "JSESSIONID=" + cookie,
61
+                        "Content-Type": "application/json"
62
+                    }
63
+                };
64
+
65
+                JiraRestClient.post(_constructUrl(config.jira.api.issue), args, function (data, response) {
66
+                    var statusCode = response.statusCode;
67
+
68
+                    //HTTP 201 | Created
69
+                    if (statusCode == 201) {
70
+                        res.status(201).send({
71
+                            success: true,
72
+                            issue: {
73
+                                id: data.id,
74
+                                key: data.key,
75
+                                link: _constructIssueUrl(data.key)
76
+                            }
77
+                        });
78
+
79
+                        logger.info("[" + ip.address() + "] JIRA Issue Created: " + data.key);
80
+                    }
81
+
82
+                    //HTTP 400 | Bad Request
83
+                    else if (statusCode == 400) {
84
+                        res.status(400).send({
85
+                            success: false,
86
+                            error: "Invalid Input. Check for missing fields or invalid values (see 'errorMessages' for specific errors).",
87
+                            errorMessages: data.errorMessages
88
+                        });
89
+                    }
90
+
91
+                    else if (statusCode == 401) {
92
+                        res.status(401).send({
93
+                            success: false,
94
+                            error: "Authorization failure."
95
+                        });
96
+                    }
97
+
98
+                    //Fallback
99
+                    else {
100
+                        res.status(statusCode).send({
101
+                            success: false,
102
+                            error: "Uncaught Error. Status Code= " + statusCode
103
+                        })
104
+                    }
105
+                });
106
+            }
107
+
108
+            else {
109
+                res.status(401).send('Missing field. Check that you supplied a description, summary, tool id AND issue type id.');
110
+            }
111
+        }
112
+
113
+        else {
114
+            res.status(401).send('Missing cookie. This resource requires authentication. Check that you supplied a cookie.');
115
+        }
116
+    });
117
+
118
+router.route('/user')
119
+
120
+//Get a particular users details (display name/avatar)
121
+    .get(function (req, res) {
122
+        //Check that the cookie and the username were passed in
123
+        //BOTH are required, so one check can be used (validity checks are handle below (HTTP 401/404)
124
+        //If it was, continue
125
+        if (req.query.cookie && req.query.username) {
126
+            var cookie = req.query.cookie;
127
+            var username = req.query.username;
128
+            var args = {
129
+                headers: {
130
+                    "cookie": "JSESSIONID=" + cookie,
131
+                    "Content-Type": "application/json"
132
+                },
133
+
134
+                parameters: {username: username}
135
+            };
136
+
137
+            JiraRestClient.get(_constructUrl(config.jira.api.user), args, function (data, response) {
138
+                //Possible HTTP Status Codes according to JIRA REST API Docs: 200,401,404
139
+                var statusCode = response.statusCode;
140
+
141
+                //HTTP 200 | OK
142
+                if (statusCode == 200) {
143
+
144
+                    //TODO: Cache the avatar
145
+
146
+
147
+                    res.status(200).send({
148
+                        success: true,
149
+                        avatar: data.avatarUrls["32x32"],
150
+                        displayName: data.displayName
151
+                    });
152
+                }
153
+
154
+                //HTTP 401 | Unauthorized
155
+                else if (statusCode == 401) {
156
+                    res.status(401).send({
157
+                        success: false,
158
+                        error: "Authentication Error. User is not authenticated."
159
+                    })
160
+                }
161
+
162
+                //HTTP 404 | Not Found
163
+                else if (statusCode == 404) {
164
+                    res.status(404).send({
165
+                        success: false,
166
+                        error: "Not Found. The requested user was not found."
167
+                    })
168
+                }
169
+
170
+                //Fallback
171
+                else {
172
+                    res.status(statusCode).send({
173
+                        success: false,
174
+                        error: "Uncaught Error. Status Code= " + statusCode
175
+                    })
176
+                }
177
+            });
178
+        }
179
+
180
+        //If either param was not passed in, send a HTTP 400 back to the client
181
+        else {
182
+            res.status(400).send('Missing parameters. Check that you supplied a valid JIRA cookie and a valid JIRA username.');
183
+        }
184
+    });
185
+
186
+router.route('/login')
187
+
188
+//Login to JIRA
189
+    .post(function (req, res) {
190
+
191
+        if (req.body.username && req.body.password) {
192
+            var username = req.body.username;
193
+            var password = req.body.password;
194
+
195
+            var args = {
196
+                data: {
197
+                    "username": username,
198
+                    "password": password
199
+                },
200
+                headers: {
201
+                    "Content-Type": "application/json"
202
+                }
203
+            };
204
+
205
+            JiraRestClient.post(_constructUrl(config.jira.api.login), args, function (data, response) {
206
+                var statusCode = response.statusCode;
207
+
208
+                //HTTP 200 | Success
209
+                if (statusCode == 200) {
210
+                    res.send({
211
+                        success: true,
212
+                        session: data.session
213
+                    });
214
+                    logger.info("[" + ip.address() + "] JIRA login success for user: " + username);
215
+                }
216
+
217
+                //HTTP 401 | Unauthorized
218
+                else if (statusCode == 401) {
219
+                    res.status(401).send({
220
+                        success: false,
221
+                        error: "Authentication Error. Invalid user credentials."
222
+                    });
223
+                    logger.info("[" + ip.address() + "] JIRA login failure for user: " + username);
224
+                }
225
+
226
+                //HTTP 403 | Forbidden
227
+                else if (statusCode == 403) {
228
+                    res.status(403).send({
229
+                        success: false,
230
+                        error: "Login Denied. CAPTCHA required, connection throttled, etc. Try logging in later."
231
+                    });
232
+                    logger.info("[" + ip.address() + "] JIRA login failure for user: " + username);
233
+                }
234
+
235
+                //Fallback
236
+                else {
237
+                    res.status(statusCode).send({
238
+                        success: false,
239
+                        error: "Uncaught Error. Status Code= " + statusCode
240
+                    });
241
+                    logger.info("[" + ip.address() + "] JIRA login failure for user: " + username);
242
+                }
243
+            });
244
+        }
245
+
246
+        //If either param was not passed in, send a HTTP 400 back to the client
247
+        else {
248
+            res.status(400).send('Missing parameters. Check that you supplied a valid username and password.');
249
+        }
250
+    })
251
+
252
+    //Logout of JIRA
253
+    .delete(function (req, res) {
254
+        if (req.query.cookie) {
255
+            var args = {
256
+                headers: {
257
+                    "cookie": "JSESSIONID=" + req.query.cookie,
258
+                    "Content-Type": "application/json"
259
+                }
260
+            };
261
+
262
+            JiraRestClient.delete(_constructUrl(config.jira.api.login), args, function (data, response) {
263
+                var statusCode = response.statusCode;
264
+
265
+                if (statusCode == 204) {
266
+                    res.status(204).send({loggedOut: true});
267
+                    logger.info("[" + ip.address() + "] Logout succeeded.");
268
+                }
269
+
270
+                else if (statusCode == 401) {
271
+                    res.status(401).send({loggedOut: false});
272
+                    logger.info("[" + ip.address() + "] Logout failed.");
273
+                }
274
+
275
+                else {
276
+                    res.status(statusCode).send({
277
+                        loggedOut: false,
278
+                        error: "Uncaught Error. Status Code= " + statusCode
279
+                    });
280
+                    logger.info("[" + ip.address() + "] Logout failed.");
281
+                }
282
+            });
283
+        }
284
+
285
+        else {
286
+            res.status(400).send('Missing cookie. Make sure you supplied a valid cookie.');
287
+        }
288
+    })
289
+
290
+    //Get the user's username from the session cookie
291
+    .get(function (req, res) {
292
+        //Check that the cookie was actually passed in
293
+        if (req.query.cookie) {
294
+            //Grab cookie from the query params
295
+            var cookie = req.query.cookie;
296
+
297
+            //Pack the cookie into the header (for authentication)
298
+            var args = {
299
+                headers: {
300
+                    "cookie": "JSESSIONID=" + cookie,
301
+                    "Content-Type": "application/json"
302
+                }
303
+            };
304
+
305
+            JiraRestClient.get(_constructUrl(config.jira.api.login), args, function (data, response) {
306
+                var statusCode = response.statusCode;
307
+
308
+                //HTTP 200 | OK
309
+                if (statusCode == 200) {
310
+                    res.status(200).send({
311
+                        success: true,
312
+                        name: data.name
313
+                    });
314
+                }
315
+
316
+                //HTTP 401 | Unauthorized
317
+                else if (statusCode == 401) {
318
+                    res.status(401).send({
319
+                        success: false,
320
+                        error: "Authentication Error. User is not authenticated."
321
+                    })
322
+                }
323
+
324
+                //Fallback
325
+                else {
326
+                    res.status(statusCode).send({
327
+                        success: false,
328
+                        error: "Uncaught Error. Status Code= " + statusCode
329
+                    })
330
+                }
331
+            });
332
+        }
333
+
334
+        //If the cookie was not passed in, send an HTTP 400 back to the client
335
+        else {
336
+            res.status(400).send('Missing parameters. Check that you supplied a valid JIRA cookie.');
337
+        }
338
+    });
339
+
340
+//Helper method for constructing URLs for JIRA REST API
341
+function _constructUrl(endpoint) {
342
+    var fullUrl = url.format({
343
+        protocol: 'https',
344
+        hostname: config.jira.url,
345
+        port: 443,
346
+        pathname: endpoint
347
+    });
348
+
349
+    return decodeURIComponent(fullUrl);
350
+}
351
+
352
+//Helper method for constructing URLs for JIRA issues
353
+function _constructIssueUrl(key) {
354
+    var fullUrl = url.format({
355
+        protocol: 'https',
356
+        hostname: config.jira.url,
357
+        pathname: '/browse/' + key
358
+    });
359
+
360
+    return decodeURIComponent(fullUrl);
361
+}
362
+
363
+module.exports = router;

+ 21
- 0
test_repository/server/routes/news/guarded.js Целия файл

@@ -0,0 +1,21 @@
1
+/* API Routes for /api/news/guarded (Guarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+var newsCtrl = require('../../controllers/news');
6
+var adminGuard = require('../../guards/adminGuard');
7
+
8
+//Middleware to use for every request
9
+router.use(function (req, res, next) {
10
+    //Use the admin guard
11
+    adminGuard.adminGuard(req, res, next);
12
+});
13
+
14
+router.route('/')
15
+
16
+//Creates a new news item
17
+    .post(function (req, res) {
18
+        newsCtrl.createNewsItem(req, res)
19
+    });
20
+
21
+module.exports = router;

+ 8
- 0
test_repository/server/routes/news/index.js Целия файл

@@ -0,0 +1,8 @@
1
+/* API Routes for /api/news/... (Guarded/Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+router.use('/', require('./unguarded'));
6
+router.use('/guarded', require('./guarded'));
7
+
8
+module.exports = router;

+ 19
- 0
test_repository/server/routes/news/unguarded.js Целия файл

@@ -0,0 +1,19 @@
1
+/* API Routes for /api/news/ (Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+var newsCtrl = require('../../controllers/news');
6
+
7
+//Middleware to use for every request
8
+router.use(function (req, res, next) {
9
+    next();
10
+});
11
+
12
+router.route('/')
13
+
14
+//Get all news items
15
+    .get(function (req, res) {
16
+        newsCtrl.getAll(req, res);
17
+    });
18
+
19
+module.exports = router;

+ 43
- 0
test_repository/server/routes/tool/guarded.js Целия файл

@@ -0,0 +1,43 @@
1
+/* API Routes for /api/tool/guarded (Guarded Routes) */
2
+
3
+var router = require('express').Router();
4
+var config = require('../../config');
5
+
6
+var adminGuard = require('../../guards/adminGuard');
7
+var toolCtrl = require('../../controllers/tool');
8
+
9
+//Middleware to use for every request
10
+router.use(function (req, res, next) {
11
+    //Use the admin guard
12
+    adminGuard.adminGuard(req, res, next);
13
+});
14
+
15
+router.route('/')
16
+
17
+//Creates a new tool in the DB
18
+    .post(function (req, res) {
19
+        toolCtrl.createTool(req, res)
20
+    });
21
+
22
+router.route('/:toolId')
23
+
24
+//Deletes a tool in the DB
25
+    .delete(function (req, res) {
26
+        toolCtrl.deleteTool(req, res)
27
+    });
28
+
29
+router.route('/:toolId/:sectionName')
30
+
31
+//Adds a new item (i.e. card) to a tool section
32
+    .post(function (req, res) {
33
+        toolCtrl.addItem(req, res)
34
+    });
35
+
36
+router.route('/:toolId/:sectionName/:item')
37
+
38
+//Deletes an item (i.e. card) from a tool section
39
+    .delete(function (req, res) {
40
+        toolCtrl.deleteItem(req, res)
41
+    });
42
+
43
+module.exports = router;

+ 8
- 0
test_repository/server/routes/tool/index.js Целия файл

@@ -0,0 +1,8 @@
1
+/* API Routes for /api/tool/... (Guarded/Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+
5
+router.use('/', require('./unguarded'));
6
+router.use('/guarded', require('./guarded'));
7
+
8
+module.exports = router;

+ 580
- 0
test_repository/server/routes/tool/index_BACK.js Целия файл

@@ -0,0 +1,580 @@
1
+//Dedicated router for retrieving 'tool'
2
+var logger = require('../../../utils/logger');
3
+
4
+var ip = require('ip');
5
+
6
+var router = require('express').Router();
7
+var Tool = require('../../../models/Tool');
8
+
9
+//Middleware to use for every request
10
+router.use(function (req, res, next) {
11
+    next();
12
+});
13
+
14
+/* ================================================================================================================== */
15
+/**
16
+ * ROUTE:       /api/tool/
17
+ * METHOD(S):   POST
18
+ */
19
+/* ================================================================================================================== */
20
+router.route('/')
21
+
22
+/**
23
+ * Request:     POST /api/tool/
24
+ * Returns:     JSON Object
25
+ * Logged:      YES
26
+ *
27
+ * Description:
28
+ * Creates a new (empty) tool in the DB.
29
+ * Requires that an id and name be supplied for the new tool in the body of the request.
30
+ *
31
+ * Body (Required):
32
+ *      toolId      [Number]    The id of the tool to be added.
33
+ *      toolName    [String]    The name of the tool to be added.
34
+ *
35
+ * Responses:
36
+ *      HTTP 200    [Success]   Returned if tool was created.
37
+ *          { created: true }
38
+ *
39
+ *      HTTP 400    [Error]     Returned if there was a missing parameter. Response -> 'errors' will contain the missing field
40
+ *          {
41
+ *              created: false,
42
+ *              errors: "Missing parameter 'XXXXXX'"
43
+ *          }
44
+ *      HTTP 422    [Error]     Returned if the request went through successfully, but there was an issue creating the tool.
45
+ *          {
46
+ *              created: false,
47
+ *              errors: "New tool creation failed. Duplicate tool id. Please check all inputs."
48
+ *          }
49
+ */
50
+    .post(function (req, res) {
51
+        //Get the toolId and toolName from the body of the request
52
+        var toolId = req.body.toolId;
53
+        var toolName = req.body.toolName;
54
+
55
+        //Check that the request body contains the required data
56
+        if (toolId && toolName) {
57
+            //Parse the int just to make sure it is actually an integer before we create the tool
58
+            var _id = parseInt(toolId);
59
+
60
+            //Declare a new Tool object using the model
61
+            var newTool = new Tool({
62
+                _id: _id,
63
+                id: _id,
64
+                name: toolName,
65
+
66
+                about: [],
67
+                training: [],
68
+                links: []
69
+            });
70
+
71
+            //Save to MongoDB
72
+            newTool.save(function (err) {
73
+                //If there was an error while saving, send it back to the client
74
+                if (err) {
75
+                    var message = "";
76
+
77
+                    //In this case, 11000 is the error code for a duplicate key
78
+                    if (err.code == 11000)
79
+                        message = "New tool creation failed. Duplicate tool id. Please check all inputs.";
80
+                    //Otherwise, just set the message = error message from mongoose
81
+                    else
82
+                        message = err.errmsg;
83
+
84
+                    //Send an HTTP 422 (Unprocessable Entity) with the error message.
85
+                    //Why HTTP 422: HTTP 400 is bad syntax. Correct syntax was supplied, but something went wrong during the request
86
+                    res.status(422).send({created: false, errors: message});
87
+
88
+                    //Log this request
89
+                    logger.info("[" + ip.address() + "] New tool creation failed. Tool id=" + toolId + " Reason: " + message);
90
+                }
91
+
92
+                //Otherwise, the addition was successful
93
+                else {
94
+                    //Send an HTTP 201 (Created)
95
+                    res.status(201).send({created: true});
96
+
97
+                    //Log this request
98
+                    logger.info("[" + ip.address() + "] New tool creation succeeded. toolId=" + toolId);
99
+                }
100
+            });
101
+        }
102
+
103
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
104
+        else {
105
+            var message = "";
106
+
107
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
108
+            if (!req.body.toolId)
109
+                message = "Missing body parameter 'toolId'.";
110
+            else
111
+                message = "Missing body parameter 'toolName'.";
112
+
113
+            //Send an HTTP 400 (Bad Request) back with the error message
114
+            res.status(400).send({created: false, errors: message});
115
+        }
116
+    });
117
+
118
+/* ================================================================================================================== */
119
+/**
120
+ * ROUTE:       /api/tool/:tool_id
121
+ * METHOD(S):   GET
122
+ */
123
+/* ================================================================================================================== */
124
+router.route('/:toolId')
125
+/**
126
+ * Request:     GET /api/tool/:toolId
127
+ * Returns:     JSON Object
128
+ * Logged:      NO
129
+ *
130
+ * Description:
131
+ * Retrieves a single tool from the DB in its entirety.
132
+ *
133
+ * Body (Required):
134
+ *      toolId      [Number]    The id of the tool to retrieve.
135
+ *
136
+ * Responses:
137
+ *      HTTP 200    [Success]   Returned if a tool was found with a matching id.
138
+ *          {
139
+ *              _id: 01234,
140
+ *              id: 01234,
141
+ *              name: "Test Tool",
142
+ *              about: [],
143
+ *              training: [],
144
+ *              links: []
145
+ *          }
146
+ *
147
+ *      HTTP 400    [Error]     Returned if the 'toolId' param was not supplied.
148
+ *          { errors: ""Missing parameter 'toolId'" }
149
+ *
150
+ *      HTTP 404    [Error]     Returned if a tool could not be found with the given id.
151
+ *          { errors: "Could not find a tool with id=12345" }
152
+ */
153
+    .get(function (req, res) {
154
+        //Get the toolId from the request params
155
+        var toolId = req.params.toolId;
156
+
157
+        //Check that a toolId was given
158
+        if (toolId) {
159
+            //Query the DB for a matching document
160
+            Tool.findOne({'id': toolId}, function (err, tool) {
161
+                //If there was an error while querying, send it as an error back to the client
162
+                if (err)
163
+                    res.status(404).send({errors: err});
164
+                //If the tool retrieved is null, a matching tool was not found
165
+                else if (tool == null)
166
+                    res.status(404).send({errors: "Could not find a tool with id=" + toolId});
167
+                //Otherwise, a matching tool was found
168
+                else
169
+                    res.status(200).send(tool);
170
+            });
171
+        }
172
+
173
+        //Otherwise, the toolId is missing
174
+        else {
175
+            //Send an HTTP 400 (Bad Request) back with the error message
176
+            res.status(400).send({errors: "Missing parameter 'toolId'"});
177
+        }
178
+    });
179
+
180
+/* ================================================================================================================== */
181
+/**
182
+ * ROUTE:       /api/tool/:toolId/:sectionName
183
+ * METHOD(S):   GET / POST
184
+ */
185
+/* ================================================================================================================== */
186
+router.route('/:toolId/:sectionName')
187
+/**
188
+ * Request:     GET /api/tool/:toolId/:sectionName
189
+ * Returns:     JSON Array
190
+ * Logged:      NO
191
+ *
192
+ * Description:
193
+ * Retrieves a single tool section for a particular tool from the DB.
194
+ *
195
+ * Params (Required):
196
+ *      toolId              [Number]    The id of the tool to retrieve.
197
+ *      sectionName         [String]    The name of the section (i.e. 'About') to retrieve.
198
+ *
199
+ * Responses:
200
+ *      HTTP 200    [Success]   Returned if the matching toolId and sectionName combo was found.
201
+ *      [
202
+ *        {
203
+     *          "heading": "What is JIRA?",
204
+     *          "contents": "JIRA is the name of our new Change Management System that will replace ClearQuest, CDG and CEM. JIRA is a proprietary issue tracking product, developed by [Atlassian](https://atlassian.com). It provides bug and issue tracking, and project management functions. It also provides the capability to manage Agile Backlog records."
205
+     *        }
206
+ *
207
+ *        ...
208
+ *      ]
209
+ *
210
+ *      HTTP 400    [Failure]   Returned if there was a missing param in the request body
211
+ *      { errors: "Missing body parameter 'toolId'" }
212
+ *
213
+ *      HTTP 404    [Failure]   Returned if the matching toolId and sectionName combo was NOT found.
214
+ *      { errors: "Could not find matching toolId and sectionName combo." }
215
+ */
216
+    .get(function (req, res) {
217
+        //Get the tool id and section name from the params of the request
218
+        var toolId = req.params.toolId;
219
+        var sectionName = req.params.sectionName.toLowerCase();
220
+
221
+        //Check that the params were actually supplied in the request
222
+        if (toolId && sectionName) {
223
+            //Look for a matching document in the DB
224
+            Tool.findOne({'id': toolId}, function (err, tool) {
225
+                //Catch any general errors and send them back to the client
226
+                if (err)
227
+                    res.status(404).send({errors: err});
228
+                //Otherwise, there was (or was not) a matching document
229
+                else
230
+                //If the matching toolId + sectionName combo was not found, send back a 404
231
+                if (tool[sectionName] == undefined || tool[sectionName] == null)
232
+                    res.status(404).send({errors: "Could not find matching toolId and sectionName combo."});
233
+                //Otherwise, send back the matching JSON Array
234
+                else
235
+                    res.status(200).send(tool[sectionName]);
236
+            });
237
+        }
238
+
239
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
240
+        else {
241
+            var message = "";
242
+
243
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
244
+            if (!toolId)
245
+                message = "Missing parameter 'toolId'.";
246
+            else
247
+                message = "Missing parameter 'sectionName'.";
248
+
249
+            //Send an HTTP 400 (Bad Request) back with the error message
250
+            res.status(400).send({errors: message});
251
+        }
252
+    })
253
+
254
+    /**
255
+     * Request:     POST /api/tool/:toolId/:sectionName
256
+     * Returns:     JSON Object
257
+     * Logged:      YES
258
+     *
259
+     * Description:
260
+     * Adds a new item to the tool/section name list (i.e. a new card)
261
+     *
262
+     * Body (Required):
263
+     *      toolId              [Number]    The id of the tool to retrieve.
264
+     *      sectionName         [String]    The name of the section (i.e. 'About') to retrieve.
265
+     *
266
+     * Responses:
267
+     *      HTTP 201    [Success]   Returned if the new item was successfully added.
268
+     *      { added: true }
269
+     *
270
+     *      HTTP 400    [Failure]   Returned if there was a missing param.
271
+     *      { errors: "Missing parameter 'toolId'" }
272
+     *
273
+     *      HTTP 404    [Failure]   Returned if the matching toolId and sectionName combo was NOT found.
274
+     *      { errors: "Could not find matching toolId and sectionName combo." }
275
+     *
276
+     *      HTTP 422    [Failure]   Returned if there was an error while saving the document.
277
+     *      { added: false, errors: err }
278
+     */
279
+    .post(function (req, res) {
280
+        //Get the tool id and section name from the params of the request
281
+        var toolId = req.params.toolId;
282
+        var sectionName = req.params.sectionName.toLowerCase();
283
+
284
+        //Check that the params were actually supplied in the request
285
+        if (toolId && sectionName) {
286
+            var heading = req.body.heading;
287
+            var contents = req.body.contents;
288
+
289
+            //Check that the heading and contents were supplied in the body of the request
290
+            if (heading && contents) {
291
+                Tool.findOne({'id': toolId}, function (err, tool) {
292
+                    //Catch any general errors and send them back to the client
293
+                    if (err)
294
+                        res.status(404).send({errors: err});
295
+
296
+                    else {
297
+                        //If the matching toolId + sectionName combo was not found, send back a 404
298
+                        if (tool[sectionName] == undefined || tool[sectionName] == null)
299
+                            res.status(404).send({errors: "Could not find matching toolId and sectionName combo."});
300
+                        //Otherwise, a matching toolId and sectionName combo was found
301
+                        else {
302
+                            //Push a new document with the request body supplied to the sectionName array
303
+                            tool[sectionName].push({"heading": heading, "contents": contents});
304
+
305
+                            //Attempt to save the document (tool)
306
+                            tool.save(function (err) {
307
+                                //If there was an error while saving, send back a 422 (Unprocessable Entity)
308
+                                if (err) {
309
+                                    logger.info("[" + ip.address() + "] New item addition failed. Tool ID / Section Name=" + toolId + " " + sectionName + " | Reason= " + err);
310
+                                    res.status(422).send({added: false, errors: err});
311
+                                }
312
+                                //Otherwise, the document was saved successfully
313
+                                else {
314
+                                    logger.info("[" + ip.address() + "] New item addition succeeded. Tool ID / Section Name=" + toolId + " " + sectionName);
315
+                                    res.status(201).send({added: true});
316
+                                }
317
+                            });
318
+                        }
319
+                    }
320
+                });
321
+            }
322
+
323
+            else {
324
+                var errorMessage = "";
325
+
326
+                //If-else block to determine which parameter was missing. Sets the message text = missing param
327
+                if (!toolId)
328
+                    errorMessage = "Missing parameter 'heading'.";
329
+                else
330
+                    errorMessage = "Missing parameter 'contents'.";
331
+
332
+                //Send an HTTP 400 (Bad Request) back with the error message
333
+                res.status(400).send({errors: errorMessage});
334
+            }
335
+        }
336
+
337
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
338
+        else {
339
+            var message = "";
340
+
341
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
342
+            if (!toolId)
343
+                message = "Missing parameter 'toolId'.";
344
+            else
345
+                message = "Missing parameter 'sectionName'.";
346
+
347
+            //Send an HTTP 400 (Bad Request) back with the error message
348
+            res.status(400).send({errors: message});
349
+        }
350
+    });
351
+
352
+/* ================================================================================================================== */
353
+/**
354
+ * ROUTE:       /api/tool/:toolId/:sectionName/count
355
+ * METHOD(S):   GET / POST
356
+ */
357
+/* ================================================================================================================== */
358
+router.route('/:toolId/:sectionName/count')
359
+
360
+/**
361
+ * Request:     GET /api/tool/:toolId/:sectionName/count
362
+ * Returns:     JSON Object
363
+ * Logged:      NO
364
+ *
365
+ * Description:
366
+ * Retrieves the count of items in a tool & section combo.
367
+ *
368
+ * Params (Required):
369
+ *      toolId              [Number]    The id of the tool to retrieve.
370
+ *      sectionName         [String]    The name of the section (i.e. 'About') to retrieve.
371
+ *
372
+ * Responses:
373
+ *      HTTP 200    [Success]   Returned if a matching tool and section combo was found.
374
+ *      { count: 10 }
375
+ *
376
+ *      HTTP 400    [Failure]   Returned if there was a missing param.
377
+ *      { errors: "Missing parameter 'toolId'" }
378
+ *
379
+ *      HTTP 404    [Failure]   Returned if the matching toolId and sectionName combo was NOT found.
380
+ *      { errors: "Could not find matching toolId and sectionName combo." }
381
+ */
382
+    .get(function (req, res) {
383
+        var toolId = req.params.toolId;
384
+        var sectionName = req.params.sectionName.toLowerCase();
385
+
386
+        //Check that the params were actually supplied in the request
387
+        if (toolId && sectionName) {
388
+            Tool.findOne({'id': toolId}, function (err, tool) {
389
+                //Catch any general errors and send them back to the client
390
+                if (err)
391
+                    res.status(404).send({errors: err});
392
+                //Otherwise, there was (or was not) a matching document
393
+                else
394
+                //If the matching toolId + sectionName combo was not found, send back a 404
395
+                if (tool[sectionName] == undefined || tool[sectionName] == null)
396
+                    res.status(404).send({errors: "Could not find matching toolId and sectionName combo."});
397
+                //Otherwise, send back the count
398
+                else
399
+                    res.status(200).send({count: tool[sectionName].length});
400
+            });
401
+        }
402
+
403
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
404
+        else {
405
+            var message = "";
406
+
407
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
408
+            if (!toolId)
409
+                message = "Missing parameter 'toolId'.";
410
+            else
411
+                message = "Missing parameter 'sectionName'.";
412
+
413
+            //Send an HTTP 400 (Bad Request) back with the error message
414
+            res.status(400).send({errors: message});
415
+        }
416
+    });
417
+
418
+/* ================================================================================================================== */
419
+/**
420
+ * ROUTE:       /api/tool/:toolId/:sectionName/:itemNum
421
+ * METHOD(S):   GET / DELETE
422
+ */
423
+/* ================================================================================================================== */
424
+router.route('/:toolId/:sectionName/:itemNum')
425
+
426
+/**
427
+ * Request:     GET /api/tool/:toolId/:sectionName/:itemNum
428
+ * Returns:     JSON Object
429
+ * Logged:      NO
430
+ *
431
+ * Description:
432
+ * Retrieves the particular item (i.e. card) from the given section name list for the given tool
433
+ *
434
+ * Params (Required):
435
+ *      toolId              [Number]    The id of the tool to retrieve.
436
+ *      sectionName         [String]    The name of the section (i.e. 'About') to retrieve.
437
+ *      itemNum             [Number]    The position of the item in the list (ex: 0 = 1st item).
438
+ *
439
+ * Responses:
440
+ *      HTTP 200    [Success]   Returned if a matching item was found
441
+ *      { heading: "What is JIRA?", contents: "..." }
442
+ *
443
+ *      HTTP 400    [Failure]   Returned if there was a missing param.
444
+ *      { errors: "Missing parameter 'toolId'" }
445
+ *
446
+ *      HTTP 404    [Failure]   Returned if the matching toolId and sectionName combo was NOT found. ALSO returned if the matching item was NOT found.
447
+ *      { errors: "Could not find matching toolId and sectionName combo." }
448
+ */
449
+    .get(function (req, res) {
450
+        var toolId = req.params.toolId;
451
+        var sectionName = req.params.sectionName.toLowerCase();
452
+        var itemNum = req.params.itemNum;
453
+
454
+        //Check that the params were actually supplied in the request
455
+        if (toolId && sectionName && itemNum) {
456
+            Tool.findOne({'id': toolId}, function (err, tool) {
457
+                //Catch any general errors and send them back to the client
458
+                if (err)
459
+                    res.status(404).send({errors: err});
460
+
461
+                //Otherwise, there was (or was not) a matching document
462
+                else {
463
+                    //If the matching toolId + sectionName combo was not found, send back a 404
464
+                    if (tool[sectionName] == undefined || tool[sectionName] == null)
465
+                        res.status(404).send({errors: "Could not find matching toolId and sectionName combo."});
466
+
467
+                    //Otherwise, send back the item
468
+                    else {
469
+                        if (tool[sectionName][itemNum] == undefined || tool[sectionName][itemNum] == null)
470
+                            res.status(404).send({errors: "Could not find given item."});
471
+                        else
472
+                            res.status(200).send(tool[sectionName][itemNum]);
473
+                    }
474
+                }
475
+            });
476
+        }
477
+
478
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
479
+        else {
480
+            var message = "";
481
+
482
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
483
+            if (!toolId)
484
+                message = "Missing parameter 'toolId'.";
485
+            else if (!sectionName)
486
+                message = "Missing parameter 'sectionName'.";
487
+            else
488
+                message = "Missing parameter 'itemNum'.";
489
+
490
+            //Send an HTTP 400 (Bad Request) back with the error message
491
+            res.status(400).send({errors: message});
492
+        }
493
+    })
494
+
495
+    /**
496
+     * Request:     DELETE /api/tool/:toolId/:sectionName/:itemNum
497
+     * Returns:     JSON Object
498
+     * Logged:      YES
499
+     *
500
+     * Description:
501
+     * Deletes a particular item (i.e. card) from the given section name list for the given tool.
502
+     *
503
+     * Params (Required):
504
+     *      toolId              [Number]    The id of the tool to retrieve.
505
+     *      sectionName         [String]    The name of the section (i.e. 'About') to retrieve.
506
+     *      itemNum             [Number]    The position of the item in the list (ex: 0 = 1st item).
507
+     *
508
+     * Responses:
509
+     *      HTTP 200    [Success]   Returned if a matching item was found
510
+     *      { heading: "What is JIRA?", contents: "..." }
511
+     *
512
+     *      HTTP 400    [Failure]   Returned if there was a missing param.
513
+     *      { errors: "Missing parameter 'toolId'" }
514
+     *
515
+     *      HTTP 404    [Failure]   Returned if the matching toolId and sectionName combo was NOT found. ALSO returned if the matching item was NOT found.
516
+     *      { errors: "Could not find matching toolId and sectionName combo." }
517
+     *
518
+     *      HTTP 422    [Failure]   Returned if there was an error while saving the updated document.
519
+     *      { deleted: false, errors: "..."}
520
+     */
521
+    .delete(function (req, res) {
522
+        var toolId = req.params.toolId;
523
+        var sectionName = req.params.sectionName.toLowerCase();
524
+        var itemNum = req.params.itemNum;
525
+
526
+        //Check that the params were actually supplied in the request
527
+        if (toolId && sectionName && itemNum) {
528
+            Tool.findOne({'id': toolId}, function (err, tool) {
529
+                //Catch any general errors and send them back to the client
530
+                if (err)
531
+                    res.status(404).send({errors: err});
532
+
533
+                //Get the item that the user wants to delete
534
+                var itemToDel = tool[sectionName][itemNum];
535
+
536
+                //If the item doesn't exist, send an error back to the client
537
+                if (!itemToDel) {
538
+                    res.status(404).send({deleted: false, errors: "Item to delete was not found."})
539
+                }
540
+
541
+                //Otherwise, the deletion step can be executed
542
+                else {
543
+                    //Remove the first indexed item
544
+                    tool[sectionName].splice(itemNum, 1);
545
+
546
+                    //Attempt to save the tool now that item was removed
547
+                    tool.save(function (err) {
548
+                        //If there was an error while saving, send the error back to the client
549
+                        if (err) {
550
+                            logger.info("[" + ip.address() + "] Item deletion failed. Tool/Section/Item = " + toolId + "/" + sectionName + "/" + itemNum);
551
+                            res.status(422).send({deleted: false, errors: err});
552
+                        }
553
+                        //Otherwise, the save was successful
554
+                        else {
555
+                            logger.info("[" + ip.address() + "] Item deletion succeeded. Tool/Section/Item = " + toolId + "/" + sectionName + "/" + itemNum);
556
+                            res.status(200).send({deleted: true});
557
+                        }
558
+                    });
559
+                }
560
+            });
561
+        }
562
+
563
+        //Otherwise, the required data was not supplied in the request. DO NOT attempt to access the DB in this case.
564
+        else {
565
+            var message = "";
566
+
567
+            //If-else block to determine which parameter was missing. Sets the message text = missing param
568
+            if (!toolId)
569
+                message = "Missing parameter 'toolId'.";
570
+            else if (!sectionName)
571
+                message = "Missing parameter 'sectionName'.";
572
+            else
573
+                message = "Missing parameter 'itemNum'.";
574
+
575
+            //Send an HTTP 400 (Bad Request) back with the error message
576
+            res.status(400).send({errors: message});
577
+        }
578
+    });
579
+
580
+module.exports = router;

+ 39
- 0
test_repository/server/routes/tool/unguarded.js Целия файл

@@ -0,0 +1,39 @@
1
+/* API Routes for /api/tool/ (Unguarded Routes) */
2
+
3
+var router = require('express').Router();
4
+var toolCtrl = require('../../controllers/tool');
5
+
6
+//Middleware to use for every request
7
+router.use(function (req, res, next) {
8
+    next();
9
+});
10
+
11
+router.route('/:toolId')
12
+
13
+//Get a particular tool
14
+    .get(function (req, res) {
15
+        toolCtrl.getTool(req, res);
16
+    });
17
+
18
+router.route('/:toolId/:sectionName')
19
+
20
+//Get a particular tool's section
21
+    .get(function (req, res) {
22
+        toolCtrl.getToolSection(req, res);
23
+    });
24
+
25
+router.route('/:toolId/:sectionName/count')
26
+
27
+//Get the count of items in a tool's section
28
+    .get(function (req, res) {
29
+        toolCtrl.getToolSectionItemCount(req, res);
30
+    });
31
+
32
+router.route('/:toolId/:sectionName/:itemNum')
33
+
34
+//Get a particular item for a tool's section
35
+    .get(function (req, res) {
36
+        toolCtrl.getToolSectionItem(req, res);
37
+    });
38
+
39
+module.exports = router;

+ 42
- 0
test_repository/server/routes/tools/index.js Целия файл

@@ -0,0 +1,42 @@
1
+//TODO: Modify this to use controllers/guards
2
+
3
+/* API Routes for /api/tools/ (Unguarded Routes) */
4
+
5
+var router = require('express').Router();
6
+
7
+var toolsCtrl = require('../../controllers/tools');
8
+
9
+//Middleware to use for every request
10
+router.use(function (req, res, next) {
11
+    next();
12
+});
13
+
14
+router.route('/')
15
+
16
+//Get all the tools from the DB
17
+    .get(function (req, res) {
18
+        toolsCtrl.getTools(req, res);
19
+    });
20
+
21
+router.route('/info')
22
+
23
+//Get info on all the tools (ids and names)
24
+    .get(function (req, res) {
25
+        toolsCtrl.getToolInfo(req, res);
26
+    });
27
+
28
+router.route('/names')
29
+
30
+//Get all tool names
31
+    .get(function (req, res) {
32
+        toolsCtrl.getToolNames(req, res);
33
+    });
34
+
35
+router.route('/ids')
36
+
37
+//Get all tool ids
38
+    .get(function (req, res) {
39
+        toolsCtrl.getToolIds(req, res);
40
+    });
41
+
42
+module.exports = router;

+ 77
- 0
test_repository/server/server.js Целия файл

@@ -0,0 +1,77 @@
1
+//User Defined Modules
2
+var config = require('./config');
3
+var logger = require('./utils/logger');
4
+
5
+//Low-Level Modules
6
+var ip = require('ip');
7
+var minimist = require('minimist')(process.argv.slice(2));
8
+
9
+//ExpressJS (Server-Side) Modules
10
+var express = require('express');
11
+var app = express();
12
+var bodyParser = require('body-parser');
13
+
14
+//MongoDB Modules
15
+var mongoose = require('mongoose');
16
+
17
+// =========================================== START SETUP ========================================================== //
18
+//Set the port for the server
19
+var port = process.env.PORT || config.httpPort;
20
+
21
+// ========================================= EXPRESS ======================================================== //
22
+//Configure app to use bodyParser()
23
+//Allows for parsing information from POST requests
24
+app.use(bodyParser.urlencoded({extended: true}));
25
+app.use(bodyParser.json());
26
+
27
+//Tell the app to use the router config we defined in /routes
28
+//In this case, every route is prefixed with '/api/'
29
+//This could be anything, but considering it's an API operation, this prefix should be suitable
30
+app.use('/api', require('./routes/index'));
31
+
32
+// ====================================== CMD LINE ARGS ===================================================== //
33
+//Check if the prod flag was set (-p or --prod)
34
+var prod = false;
35
+if (minimist.p || minimist.prod) {
36
+    prod = true;
37
+    app.use(express.static('dist'));
38
+}
39
+
40
+else {
41
+    app.use(express.static('app'));
42
+}
43
+
44
+//Check if the test flag was set (-t or --test)
45
+var test = false;
46
+if (minimist.t || minimist.test) {
47
+    test = true;
48
+    /*
49
+     WARNING!!!
50
+
51
+     The following code tells NodeJS to ignore ALL HTTPS security (SSL/TLS).
52
+     This is OK for testing, but should NEVER be enabled in production.
53
+     Since the server was started with the -t flag, we are running on a test version.
54
+     */
55
+    process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
56
+}
57
+
58
+// ========================================= MONGODB ======================================================== //
59
+var dbOptions = {
60
+    user: config.db.username,
61
+    pass: config.db.password,
62
+    auth: {
63
+        authdb: 'admin'
64
+    }
65
+};
66
+
67
+mongoose.connect(config.db.tools.path, dbOptions);
68
+mongoose.connection.on('error', function () {
69
+    logger.error("ERROR CONNECTING TO DB. Check that the DB is online.");
70
+    process.exit(1);
71
+});
72
+
73
+// ============================================ END SETUP =========================================================== //
74
+
75
+
76
+app.listen(port);
77
+console.log('Server is running on port: ' + port);

+ 25
- 0
test_repository/server/utils/gen_admin_pass.js Целия файл

@@ -0,0 +1,25 @@
1
+/* Admin password encryption */
2
+
3
+var minimist = require('minimist')(process.argv.slice(2));
4
+var bcrypt = require('bcryptjs');
5
+
6
+var rounds = 10;
7
+
8
+if (minimist.r || minimist.rounds)
9
+    rounds = parseInt(minimist.r);
10
+
11
+if (minimist.i || minimist.in)
12
+    generateHash(rounds, minimist.i);
13
+
14
+else {
15
+    console.error("NO INPUT SUPPLIED!");
16
+}
17
+
18
+function generateHash(rounds, text) {
19
+    console.log("Hashing with " + rounds + " rounds");
20
+
21
+    var salt = bcrypt.genSaltSync(rounds);
22
+    var hash = bcrypt.hashSync(text, salt);
23
+
24
+    console.log("HASH: " + hash);
25
+}

+ 58
- 0
test_repository/server/utils/logger.js Целия файл

@@ -0,0 +1,58 @@
1
+/* Common logging functions */
2
+
3
+var winston = require('winston');
4
+
5
+//Add a debugging level logger
6
+winston.loggers.add('debug', {
7
+    console: {
8
+        level: 'debug',
9
+        colorize: true,
10
+    },
11
+
12
+    file: {
13
+        filename: './logs/debug.log'
14
+    }
15
+});
16
+
17
+//Add an error level logger
18
+winston.loggers.add('error', {
19
+    console: {
20
+        level: 'error',
21
+        colorize: true,
22
+    },
23
+
24
+    file: {
25
+        filename: './logs/errors.log'
26
+    }
27
+});
28
+
29
+//Add an info level logger
30
+winston.loggers.add('info', {
31
+    console: {
32
+        level: 'info',
33
+        colorize: true,
34
+    },
35
+
36
+    file: {
37
+        filename: './logs/info.log'
38
+    }
39
+});
40
+
41
+//Export the loggers so we can use them throughout
42
+var debug = winston.loggers.get('debug');
43
+var error = winston.loggers.get('error');
44
+var info = winston.loggers.get('info');
45
+
46
+module.exports = {
47
+    debug: function (message) {
48
+        debug.debug(message);
49
+    },
50
+
51
+    error: function (message) {
52
+        error.error(message)
53
+    },
54
+
55
+    info: function (message) {
56
+        info.info(message);
57
+    }
58
+};

+ 19
- 0
test_repository/server/utils/passCrypt/index.js Целия файл

@@ -0,0 +1,19 @@
1
+/* Encryption helper functions */
2
+
3
+var bcrypt = require('bcryptjs');
4
+
5
+const ROUNDS = 10;
6
+
7
+//Generate the salted hash given a String and a work factor (constant)
8
+var generateHash = function generateHash(text) {
9
+    var salt = bcrypt.genSaltSync(ROUNDS);
10
+    return bcrypt.hashSync(text, salt);
11
+};
12
+
13
+//If the password in the DB (HASHED PWD) = password passed in (HASHED PWD), it is a valid pass
14
+var checkPass = function checkPass(plainPass, hashedPass) {
15
+    return bcrypt.compareSync(plainPass, hashedPass);
16
+};
17
+
18
+exports.generateHash = generateHash;
19
+exports.checkPass = checkPass;

+ 126
- 0
test_repository/server/validation/index.js Целия файл

@@ -0,0 +1,126 @@
1
+/* Common validation functions */
2
+
3
+var Joi = require('joi');
4
+
5
+/**
6
+ * Validator for a new News item.
7
+ * Checks that a title String and contents String was supplied.
8
+ *
9
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
10
+ * @param params The params to validate the schema against
11
+ */
12
+exports.validateNewsItem = function (res, params) {
13
+    var schema = {
14
+        title: Joi.string().required(),
15
+        contents: Joi.string().required()
16
+    };
17
+
18
+    return commonValidator(res, schema, params);
19
+};
20
+
21
+/**
22
+ * Validator for a user/pass combo.
23
+ * Checks that a username String and password String was supplied.
24
+ *
25
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
26
+ * @param params The params to validate the schema against
27
+ */
28
+exports.validateUserPass = function (res, params) {
29
+    var schema = {
30
+        username: Joi.string().required(),
31
+        password: Joi.string().required()
32
+    };
33
+
34
+    return commonValidator(res, schema, params);
35
+};
36
+
37
+/**
38
+ * Validator for a tool id.
39
+ * Checks that the tool id is a positive integer.
40
+ *
41
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
42
+ * @param params The params to validate the schema against
43
+ */
44
+exports.validateToolId = function (res, params) {
45
+    var schema = {
46
+        toolId: Joi.number().integer().positive().required()
47
+    };
48
+
49
+    return commonValidator(res, schema, params);
50
+};
51
+
52
+/**
53
+ * Validator for a tool id and tool name.
54
+ * Checks that the tool id is a positive integer and that the tool name is a String.
55
+ *
56
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
57
+ * @param params The params to validate the schema against
58
+ */
59
+exports.validateToolIdName = function (res, params) {
60
+    var schema = {
61
+        toolId: Joi.number().integer().positive().required(),
62
+        toolName: Joi.string().required()
63
+    };
64
+
65
+    return commonValidator(res, schema, params);
66
+};
67
+
68
+/**
69
+ * Validator for a tool id and section name.
70
+ * Checks that the tool id is a positive integer and that the tool section is a String.
71
+ *
72
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
73
+ * @param params The params to validate the schema against
74
+ */
75
+exports.validateToolAndSection = function (res, params) {
76
+    var schema = {
77
+        toolId: Joi.number().integer().positive().required(),
78
+        sectionName: Joi.string().required()
79
+    };
80
+
81
+    return commonValidator(res, schema, params);
82
+};
83
+
84
+/**
85
+ * Validator for a tool id, section name and item number.
86
+ * Checks that the tool id is a positive integer, that the tool section is a String and that the item number is positive integer.
87
+ *
88
+ * @param res The response object passed over from calling method (so that an error can be sent if validation fails)
89
+ * @param params The params to validate the schema against
90
+ */
91
+exports.validateToolSectionItem = function (res, params) {
92
+    var schema = {
93
+        toolId: Joi.number().integer().positive().required(),
94
+        sectionName: Joi.string().required(),
95
+        itemNum: Joi.number().integer().positive().required()
96
+    };
97
+
98
+    return commonValidator(res, schema, params);
99
+};
100
+
101
+/**
102
+ * Validator helper method. Wraps the Joi.validate() method such that params are checked against a schema.
103
+ * Also sends back a generic HTTP 400 for a bad request.
104
+ * @param res The response object sent from calling method so that errors can be sent back to client.
105
+ * @param schema The schema to validate the params against.
106
+ * @param params The params to validate the schema against.
107
+ * @returns {boolean} The validity of the params (i.e. do they fulfil the requirements).
108
+ */
109
+function commonValidator(res, schema, params) {
110
+    var valid = false;
111
+
112
+    //Run the validation
113
+    Joi.validate(params, schema, function (err, value) {
114
+        //If there is no error, the params are valid.
115
+        if (err == null)
116
+            valid = true;
117
+        //Otherwise, the params are invalid, and we should send back an error to the client
118
+        else {
119
+            res.status(400).send({message: "Missing/Bad params.", errors: err});
120
+            valid = false;
121
+        }
122
+    });
123
+
124
+    //The caller needs to know if the validation went through successfully or not, so send back the Boolean.
125
+    return valid;
126
+}

+ 2
- 0
test_repository/test.js Целия файл

@@ -0,0 +1,2 @@
1
+// This is just for testing purpose
2
+// Added another line

+ 37
- 0
toWriteTest.py Целия файл

@@ -0,0 +1,37 @@
1
+def writeHeaderstoNewTSV(fileName,headers):
2
+	"""
3
+	Writes the headers to the first line of the .tsv file
4
+
5
+	Args:
6
+		fileName (String): Name of the destination file, ex: "data.tsv"
7
+		headers (List(String)): Headers to be written, ex: ["header1","header2"....]
8
+
9
+	"""
10
+	assert fileName[-4:] ==".tsv", "fileName must be '.tsv' file not '%s'" %(fileName)
11
+
12
+	f = open (fileName,"w")
13
+	for headerIndex in range(len(headers)):
14
+		if headerIndex!=len(headers)-1:
15
+			# write header along with\t 
16
+			f.write(headers[headerIndex]+"\t")
17
+		else:
18
+			# write last word along with\n
19
+			f.write(headers[len(headers)-1]+"\n")
20
+	f.close()
21
+
22
+writeHeaderstoNewTSV("FlaskTest/static/data/commits_by_author.tsv",["date","author1","author2","author3","author4","author5","author6","author7"])
23
+a= open("FlaskTest/static/data/commits_by_author.tsv","r")
24
+print (a.readlines())
25
+
26
+
27
+
28
+
29
+# conversion from space to tsv for fallback if all else fails
30
+# sed 's/ /\t/g' file.dat > file.tsv
31
+
32
+
33
+
34
+
35
+# ./gitstats /home/gschultz/gitstats/test_repository /home/gschultz/gitstats/output_test
36
+# ./gitstats /home/gschultz/gitstats/Tools_Portal_TESTING ~/var/www/html/output_TEST
37
+# ./gitstats test_repository output_test