sidekiq 6.2.2 → 6.5.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +150 -1
  3. data/LICENSE +3 -3
  4. data/README.md +9 -4
  5. data/bin/sidekiq +3 -3
  6. data/bin/sidekiqload +70 -66
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +232 -94
  13. data/lib/sidekiq/cli.rb +60 -40
  14. data/lib/sidekiq/client.rb +46 -66
  15. data/lib/sidekiq/{util.rb → component.rb} +12 -42
  16. data/lib/sidekiq/delay.rb +3 -1
  17. data/lib/sidekiq/extensions/generic_proxy.rb +1 -1
  18. data/lib/sidekiq/fetch.rb +22 -19
  19. data/lib/sidekiq/job.rb +8 -3
  20. data/lib/sidekiq/job_logger.rb +15 -27
  21. data/lib/sidekiq/job_retry.rb +78 -55
  22. data/lib/sidekiq/job_util.rb +71 -0
  23. data/lib/sidekiq/launcher.rb +62 -54
  24. data/lib/sidekiq/logger.rb +8 -18
  25. data/lib/sidekiq/manager.rb +35 -34
  26. data/lib/sidekiq/metrics/deploy.rb +47 -0
  27. data/lib/sidekiq/metrics/query.rb +153 -0
  28. data/lib/sidekiq/metrics/shared.rb +94 -0
  29. data/lib/sidekiq/metrics/tracking.rb +134 -0
  30. data/lib/sidekiq/middleware/chain.rb +82 -38
  31. data/lib/sidekiq/middleware/current_attributes.rb +63 -0
  32. data/lib/sidekiq/middleware/i18n.rb +6 -4
  33. data/lib/sidekiq/middleware/modules.rb +21 -0
  34. data/lib/sidekiq/monitor.rb +1 -1
  35. data/lib/sidekiq/paginator.rb +8 -8
  36. data/lib/sidekiq/processor.rb +47 -41
  37. data/lib/sidekiq/rails.rb +22 -4
  38. data/lib/sidekiq/redis_client_adapter.rb +154 -0
  39. data/lib/sidekiq/redis_connection.rb +85 -54
  40. data/lib/sidekiq/ring_buffer.rb +29 -0
  41. data/lib/sidekiq/scheduled.rb +54 -30
  42. data/lib/sidekiq/testing/inline.rb +4 -4
  43. data/lib/sidekiq/testing.rb +37 -36
  44. data/lib/sidekiq/transaction_aware_client.rb +45 -0
  45. data/lib/sidekiq/version.rb +1 -1
  46. data/lib/sidekiq/web/action.rb +3 -3
  47. data/lib/sidekiq/web/application.rb +25 -9
  48. data/lib/sidekiq/web/csrf_protection.rb +2 -2
  49. data/lib/sidekiq/web/helpers.rb +30 -18
  50. data/lib/sidekiq/web.rb +8 -4
  51. data/lib/sidekiq/worker.rb +136 -13
  52. data/lib/sidekiq.rb +114 -31
  53. data/sidekiq.gemspec +2 -2
  54. data/web/assets/javascripts/application.js +113 -60
  55. data/web/assets/javascripts/chart.min.js +13 -0
  56. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  57. data/web/assets/javascripts/dashboard.js +50 -67
  58. data/web/assets/javascripts/graph.js +16 -0
  59. data/web/assets/javascripts/metrics.js +262 -0
  60. data/web/assets/stylesheets/application-dark.css +19 -23
  61. data/web/assets/stylesheets/application-rtl.css +0 -4
  62. data/web/assets/stylesheets/application.css +55 -109
  63. data/web/locales/el.yml +43 -19
  64. data/web/locales/en.yml +8 -1
  65. data/web/locales/pt-br.yml +27 -9
  66. data/web/views/_footer.erb +1 -1
  67. data/web/views/_nav.erb +1 -1
  68. data/web/views/_poll_link.erb +2 -5
  69. data/web/views/_summary.erb +7 -7
  70. data/web/views/busy.erb +4 -4
  71. data/web/views/dashboard.erb +9 -8
  72. data/web/views/layout.erb +1 -1
  73. data/web/views/metrics.erb +69 -0
  74. data/web/views/metrics_for_job.erb +87 -0
  75. data/web/views/queue.erb +14 -10
  76. data/web/views/queues.erb +1 -1
  77. metadata +27 -12
  78. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  79. data/lib/sidekiq/exception_handler.rb +0 -27
@@ -0,0 +1,262 @@
1
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
2
+ Chart.defaults.borderColor = "#333"
3
+ Chart.defaults.color = "#aaa"
4
+ }
5
+
6
+ class BaseChart {
7
+ constructor(id, options) {
8
+ this.ctx = document.getElementById(id);
9
+ this.options = options
10
+ this.fallbackColor = "#999";
11
+ this.colors = [
12
+ // Colors taken from https://www.chartjs.org/docs/latest/samples/utils.html
13
+ "#537bc4",
14
+ "#4dc9f6",
15
+ "#f67019",
16
+ "#f53794",
17
+ "#acc236",
18
+ "#166a8f",
19
+ "#00a950",
20
+ "#58595b",
21
+ "#8549ba",
22
+ "#991b1b",
23
+ ];
24
+
25
+ this.chart = new Chart(this.ctx, {
26
+ type: this.options.chartType,
27
+ data: { labels: this.options.labels, datasets: this.datasets },
28
+ options: this.chartOptions,
29
+ });
30
+ }
31
+
32
+ addMarksToChart() {
33
+ this.options.marks.forEach(([bucket, label], i) => {
34
+ this.chart.options.plugins.annotation.annotations[`deploy-${i}`] = {
35
+ type: "line",
36
+ xMin: bucket,
37
+ xMax: bucket,
38
+ borderColor: "rgba(220, 38, 38, 0.4)",
39
+ borderWidth: 2,
40
+ };
41
+ });
42
+ }
43
+ }
44
+
45
+ class JobMetricsOverviewChart extends BaseChart {
46
+ constructor(id, options) {
47
+ super(id, { ...options, chartType: "line" });
48
+ this.swatches = [];
49
+
50
+ this.addMarksToChart();
51
+ this.chart.update();
52
+ }
53
+
54
+ registerSwatch(id) {
55
+ const el = document.getElementById(id);
56
+ el.onchange = () => this.toggle(el.value, el.checked);
57
+ this.swatches[el.value] = el;
58
+ this.updateSwatch(el.value);
59
+ }
60
+
61
+ updateSwatch(kls) {
62
+ const el = this.swatches[kls];
63
+ const ds = this.chart.data.datasets.find((ds) => ds.label == kls);
64
+ el.checked = !!ds;
65
+ el.style.color = ds ? ds.borderColor : null;
66
+ }
67
+
68
+ toggle(kls, visible) {
69
+ if (visible) {
70
+ this.chart.data.datasets.push(this.dataset(kls));
71
+ } else {
72
+ const i = this.chart.data.datasets.findIndex((ds) => ds.label == kls);
73
+ this.colors.unshift(this.chart.data.datasets[i].borderColor);
74
+ this.chart.data.datasets.splice(i, 1);
75
+ }
76
+
77
+ this.updateSwatch(kls);
78
+ this.chart.update();
79
+ }
80
+
81
+ dataset(kls) {
82
+ const color = this.colors.shift() || this.fallbackColor;
83
+
84
+ return {
85
+ label: kls,
86
+ data: this.options.series[kls],
87
+ borderColor: color,
88
+ backgroundColor: color,
89
+ borderWidth: 2,
90
+ pointRadius: 2,
91
+ };
92
+ }
93
+
94
+ get datasets() {
95
+ return Object.entries(this.options.series)
96
+ .filter(([kls, _]) => this.options.visible.includes(kls))
97
+ .map(([kls, _]) => this.dataset(kls));
98
+ }
99
+
100
+ get chartOptions() {
101
+ return {
102
+ aspectRatio: 4,
103
+ scales: {
104
+ y: {
105
+ beginAtZero: true,
106
+ title: {
107
+ text: "Total execution time (sec)",
108
+ display: true,
109
+ },
110
+ },
111
+ },
112
+ interaction: {
113
+ mode: "x",
114
+ },
115
+ plugins: {
116
+ legend: {
117
+ display: false,
118
+ },
119
+ tooltip: {
120
+ callbacks: {
121
+ title: (items) => `${items[0].label} UTC`,
122
+ label: (item) =>
123
+ `${item.dataset.label}: ${item.parsed.y.toFixed(1)} seconds`,
124
+ footer: (items) => {
125
+ const bucket = items[0].label;
126
+ const marks = this.options.marks.filter(([b, _]) => b == bucket);
127
+ return marks.map(([b, msg]) => `Deploy: ${msg}`);
128
+ },
129
+ },
130
+ },
131
+ },
132
+ };
133
+ }
134
+ }
135
+
136
+ class HistTotalsChart extends BaseChart {
137
+ constructor(id, options) {
138
+ super(id, { ...options, chartType: "bar" });
139
+ }
140
+
141
+ get datasets() {
142
+ return [{
143
+ data: this.options.series,
144
+ backgroundColor: this.colors[0],
145
+ borderWidth: 0,
146
+ }];
147
+ }
148
+
149
+ get chartOptions() {
150
+ return {
151
+ aspectRatio: 6,
152
+ scales: {
153
+ y: {
154
+ beginAtZero: true,
155
+ title: {
156
+ text: "Total jobs",
157
+ display: true,
158
+ },
159
+ },
160
+ },
161
+ interaction: {
162
+ mode: "x",
163
+ },
164
+ plugins: {
165
+ legend: {
166
+ display: false,
167
+ },
168
+ tooltip: {
169
+ callbacks: {
170
+ label: (item) => `${item.parsed.y} jobs`,
171
+ },
172
+ },
173
+ },
174
+ };
175
+ }
176
+ }
177
+
178
+ class HistBubbleChart extends BaseChart {
179
+ constructor(id, options) {
180
+ super(id, { ...options, chartType: "bubble" });
181
+
182
+ this.addMarksToChart();
183
+ this.chart.update();
184
+ }
185
+
186
+ get datasets() {
187
+ const data = [];
188
+ let maxCount = 0;
189
+
190
+ Object.entries(this.options.hist).forEach(([bucket, hist]) => {
191
+ hist.forEach((count, histBucket) => {
192
+ if (count > 0) {
193
+ data.push({
194
+ x: bucket,
195
+ // histogram data is ordered fastest to slowest, but this.histIntervals is
196
+ // slowest to fastest (so it displays correctly on the chart).
197
+ y:
198
+ this.options.histIntervals[this.options.histIntervals.length - 1 - histBucket] /
199
+ 1000,
200
+ count: count,
201
+ });
202
+
203
+ if (count > maxCount) maxCount = count;
204
+ }
205
+ });
206
+ });
207
+
208
+ // Chart.js will not calculate the bubble size. We have to do that.
209
+ const maxRadius = this.ctx.offsetWidth / this.options.labels.length;
210
+ const minRadius = 1
211
+ const multiplier = (maxRadius / maxCount) * 1.5;
212
+ data.forEach((entry) => {
213
+ entry.r = entry.count * multiplier + minRadius;
214
+ });
215
+
216
+ return [{
217
+ data: data,
218
+ backgroundColor: "#537bc4",
219
+ borderColor: "#537bc4",
220
+ }];
221
+ }
222
+
223
+ get chartOptions() {
224
+ return {
225
+ aspectRatio: 3,
226
+ scales: {
227
+ x: {
228
+ type: "category",
229
+ labels: this.options.labels,
230
+ },
231
+ y: {
232
+ title: {
233
+ text: "Execution time (sec)",
234
+ display: true,
235
+ },
236
+ },
237
+ },
238
+ interaction: {
239
+ mode: "x",
240
+ },
241
+ plugins: {
242
+ legend: {
243
+ display: false,
244
+ },
245
+ tooltip: {
246
+ callbacks: {
247
+ title: (items) => `${items[0].raw.x} UTC`,
248
+ label: (item) =>
249
+ `${item.parsed.y} seconds: ${item.raw.count} job${
250
+ item.raw.count == 1 ? "" : "s"
251
+ }`,
252
+ footer: (items) => {
253
+ const bucket = items[0].raw.x;
254
+ const marks = this.options.marks.filter(([b, _]) => b == bucket);
255
+ return marks.map(([b, msg]) => `Deploy: ${msg}`);
256
+ },
257
+ },
258
+ },
259
+ },
260
+ };
261
+ }
262
+ }
@@ -1,6 +1,6 @@
1
1
  html, body {
2
- background-color: #333 !important;
3
- color: #ddd;
2
+ background-color: #171717 !important;
3
+ color: #DEDEDE;
4
4
  }
5
5
 
6
6
  a,
@@ -8,12 +8,12 @@ a,
8
8
  .summary_bar ul .count,
9
9
  span.current-interval,
10
10
  .navbar .navbar-brand {
11
- color: #c04;
11
+ color: #d04;
12
12
  }
13
13
 
14
- .history-graph + .active,
14
+ .history-graph.active,
15
15
  .beacon .dot {
16
- background-color: #c04;
16
+ background-color: #d04;
17
17
  }
18
18
 
19
19
  .navbar .navbar-brand:hover {
@@ -30,15 +30,15 @@ span.current-interval,
30
30
 
31
31
  .navbar-inverse {
32
32
  background-color: #222;
33
- border-color: #555;
33
+ border-color: #444;
34
34
  }
35
35
 
36
36
  table {
37
- background-color: #282828;
37
+ background-color: #1D1D1D;
38
38
  }
39
39
 
40
40
  .table-striped > tbody > tr:nth-of-type(odd) {
41
- background-color: #333;
41
+ background-color: #2E2E2E;
42
42
  }
43
43
 
44
44
  .table-bordered,
@@ -48,7 +48,7 @@ table {
48
48
  .table-bordered > tfoot > tr > th,
49
49
  .table-bordered > thead > tr > td,
50
50
  .table-bordered > thead > tr > th {
51
- border: 1px solid #555;
51
+ border: 1px solid #444;
52
52
  }
53
53
 
54
54
  .table-hover > tbody > tr:hover {
@@ -72,9 +72,7 @@ table {
72
72
  background-color: #31708f;
73
73
  }
74
74
 
75
- a:link,
76
- a:active,
77
- a:hover {
75
+ a:link, a:active, a:hover, a:visited {
78
76
  color: #ddd;
79
77
  }
80
78
 
@@ -85,15 +83,13 @@ input {
85
83
  }
86
84
 
87
85
  .summary_bar .summary {
88
- background-color: #222;
89
- border: 1px solid #555;
90
-
91
- box-shadow: 0 0 5px rgba(255, 255, 255, 0.1);
86
+ background-color: #232323;
87
+ border: 1px solid #444;
92
88
  }
93
89
 
94
90
  .navbar-default {
95
- background-color: #222;
96
- border-color: #555;
91
+ background-color: #0F0F0F;
92
+ border-color: #444;
97
93
  }
98
94
 
99
95
  .navbar-default .navbar-nav > .active > a,
@@ -112,7 +108,7 @@ input {
112
108
  .pagination > li > span {
113
109
  color: #ddd;
114
110
  background-color: #333;
115
- border-color: #555;
111
+ border-color: #444;
116
112
  }
117
113
  .pagination > .disabled > a,
118
114
  .pagination > .disabled > a:focus,
@@ -122,18 +118,18 @@ input {
122
118
  .pagination > .disabled > span:hover {
123
119
  color: #ddd;
124
120
  background-color: #333;
125
- border-color: #555;
121
+ border-color: #444;
126
122
  }
127
123
 
128
124
  .stat {
129
- border: 1px solid rgba(255, 255, 255, 0.1);
125
+ border: 1px solid #888;
130
126
  }
131
127
 
132
128
  .rickshaw_graph .detail {
133
- background: rgba(255, 255, 255, .1)
129
+ background: #888;
134
130
  }
135
131
  .rickshaw_graph .x_tick {
136
- border-color: rgba(255, 255, 255, .2);
132
+ border-color: #888;
137
133
  }
138
134
 
139
135
  .rickshaw_graph .y_ticks.glow text {
@@ -46,10 +46,6 @@ form .btn-group .btn {
46
46
  .navbar .poll-wrapper {
47
47
  margin: 4px 0 0 4px;
48
48
  }
49
-
50
- .navbar .dropdown-menu a {
51
- text-align: right;
52
- }
53
49
  }
54
50
 
55
51
  .navbar-footer .navbar ul.nav a.navbar-brand {
@@ -21,7 +21,7 @@ body {
21
21
  a {
22
22
  color: #b1003e;
23
23
  }
24
- a:active, a:hover {
24
+ a:active, a:hover, a:focus {
25
25
  color: #4b001a;
26
26
  }
27
27
 
@@ -67,10 +67,15 @@ body {
67
67
  padding: 0 20px;
68
68
  }
69
69
 
70
- h3 {
70
+ h1, h2, h3 {
71
+ font-size: 24px;
71
72
  line-height: 45px;
72
73
  }
73
74
 
75
+ .header-with-subheader h2 {
76
+ margin-top: -18px;
77
+ }
78
+
74
79
  .centered {
75
80
  text-align: center;
76
81
  }
@@ -89,19 +94,18 @@ header.row .pagination {
89
94
  .summary_bar .summary {
90
95
  margin-top: 12px;
91
96
  background-color: #fff;
92
- box-shadow: 0 0 5px rgba(50, 50, 50, 0.25);
93
97
  border-radius: 4px;
98
+ border: 1px solid rgba(0, 0, 0, 0.1);
94
99
  padding: 8px;
95
100
  margin-bottom: 10px;
96
- border-width: 0;
97
101
  }
98
102
  .poll-wrapper {
99
103
  margin: 9px;
100
104
  }
101
- #live-poll.active {
105
+ .live-poll.active {
102
106
  background-color: #009300;
103
107
  }
104
- #live-poll.active:hover {
108
+ .live-poll.active:hover {
105
109
  background-color: #777;
106
110
  }
107
111
  .summary_bar ul {
@@ -203,6 +207,7 @@ table .table-checkbox label {
203
207
 
204
208
  .navbar .navbar-brand .status {
205
209
  color: #585454;
210
+ display: inline;
206
211
  }
207
212
 
208
213
 
@@ -266,18 +271,6 @@ table .table-checkbox label {
266
271
  .navbar .poll-wrapper {
267
272
  margin: 4px 4px 0 0;
268
273
  }
269
-
270
- .navbar .dropdown-menu {
271
- min-width: 120px;
272
- }
273
-
274
- .navbar .dropdown-menu a {
275
- text-align: left;
276
- }
277
- }
278
-
279
- .nav .dropdown {
280
- display: none;
281
274
  }
282
275
 
283
276
  .navbar-footer .navbar ul.nav {
@@ -362,6 +355,7 @@ img.smallogo {
362
355
  text-align: center;
363
356
  margin-right: 20px;
364
357
  border: 1px solid rgba(0, 0, 0, 0.1);
358
+ border-radius: 4px;
365
359
  padding: 5px;
366
360
  width: 150px;
367
361
  margin-bottom: 20px;
@@ -417,8 +411,6 @@ span.current-interval {
417
411
 
418
412
  div.interval-slider input {
419
413
  width: 160px;
420
- height: 3px;
421
- margin-top: 5px;
422
414
  border-radius: 2px;
423
415
  background: currentcolor;
424
416
  }
@@ -502,7 +494,7 @@ div.interval-slider input {
502
494
 
503
495
  @keyframes beacon-dot-pulse {
504
496
  from {
505
- background-color: #80002d;
497
+ background-color: #50002d;
506
498
  box-shadow: 0 0 9px #666;
507
499
  }
508
500
  50% {
@@ -510,7 +502,7 @@ div.interval-slider input {
510
502
  box-shadow: 0 0 18px #666;
511
503
  }
512
504
  to {
513
- background-color: #80002d;
505
+ background-color: #50002d;
514
506
  box-shadow: 0 0 9px #666;
515
507
  }
516
508
  }
@@ -549,7 +541,6 @@ div.interval-slider input {
549
541
  }
550
542
 
551
543
  .history-graph {
552
- font-size: 0.8em;
553
544
  padding: 3px;
554
545
  border-radius: 3px;
555
546
  }
@@ -633,7 +624,7 @@ div.interval-slider input {
633
624
  position: absolute;
634
625
  top: 0;
635
626
  z-index: 2;
636
- background: rgba(0, 0, 0, .1);
627
+ background: rgba(0, 0, 0, .9);
637
628
  bottom: 0;
638
629
  width: 1px;
639
630
  transition: opacity .25s linear;
@@ -739,96 +730,16 @@ div.interval-slider input {
739
730
  top: 0;
740
731
  bottom: 0;
741
732
  width: 0;
742
- border-left: 1px dotted rgba(0, 0, 0, .2);
733
+ border-left: 1px dotted rgba(0, 0, 0, .5);
743
734
  pointer-events: none
744
735
  }
745
736
  .rickshaw_graph .x_tick .title {
746
737
  position: absolute;
747
- font-size: 12px;
748
738
  font-family: Arial, sans-serif;
749
- opacity: .5;
750
739
  white-space: nowrap;
751
740
  margin-left: 3px;
752
741
  bottom: 1px
753
742
  }
754
- .rickshaw_annotation_timeline {
755
- height: 1px;
756
- border-top: 1px solid #e0e0e0;
757
- margin-top: 10px;
758
- position: relative
759
- }
760
- .rickshaw_annotation_timeline .annotation {
761
- position: absolute;
762
- height: 6px;
763
- width: 6px;
764
- margin-left: -2px;
765
- top: -3px;
766
- border-radius: 5px;
767
- background-color: rgba(0, 0, 0, .25)
768
- }
769
- .rickshaw_graph .annotation_line {
770
- position: absolute;
771
- top: 0;
772
- bottom: -6px;
773
- width: 0;
774
- border-left: 2px solid rgba(0, 0, 0, .3);
775
- display: none
776
- }
777
- .rickshaw_graph .annotation_line.active {
778
- display: block
779
- }
780
- .rickshaw_graph .annotation_range {
781
- background: rgba(0, 0, 0, .1);
782
- display: none;
783
- position: absolute;
784
- top: 0;
785
- bottom: -6px
786
- }
787
- .rickshaw_graph .annotation_range.active {
788
- display: block
789
- }
790
- .rickshaw_graph .annotation_range.active.offscreen {
791
- display: none
792
- }
793
- .rickshaw_annotation_timeline .annotation .content {
794
- background: #fff;
795
- color: #000;
796
- opacity: .9;
797
- padding: 5px;
798
- box-shadow: 0 0 2px rgba(0, 0, 0, .8);
799
- border-radius: 3px;
800
- position: relative;
801
- z-index: 20;
802
- font-size: 12px;
803
- padding: 6px 8px 8px;
804
- top: 18px;
805
- left: -11px;
806
- width: 160px;
807
- display: none;
808
- cursor: pointer
809
- }
810
- .rickshaw_annotation_timeline .annotation .content:before {
811
- content: "\25b2";
812
- position: absolute;
813
- top: -11px;
814
- color: #fff;
815
- text-shadow: 0 -1px 1px rgba(0, 0, 0, .8)
816
- }
817
- .rickshaw_annotation_timeline .annotation.active,
818
- .rickshaw_annotation_timeline .annotation:hover {
819
- background-color: rgba(0, 0, 0, .8);
820
- cursor: none
821
- }
822
- .rickshaw_annotation_timeline .annotation .content:hover {
823
- z-index: 50
824
- }
825
- .rickshaw_annotation_timeline .annotation.active .content {
826
- display: block
827
- }
828
- .rickshaw_annotation_timeline .annotation:hover .content {
829
- display: block;
830
- z-index: 50
831
- }
832
743
  .rickshaw_graph .y_axis,
833
744
  .rickshaw_graph .x_axis_d3 {
834
745
  fill: none
@@ -880,7 +791,6 @@ div.interval-slider input {
880
791
  }
881
792
  .rickshaw_legend {
882
793
  font-family: Arial;
883
- font-size: 12px;
884
794
  color: #fff;
885
795
  background: #404040;
886
796
  display: inline-block;
@@ -923,10 +833,8 @@ div.interval-slider input {
923
833
  }
924
834
  .rickshaw_legend .action {
925
835
  margin-right: .2em;
926
- font-size: 10px;
927
- opacity: .2;
836
+ opacity: .5;
928
837
  cursor: pointer;
929
- font-size: 14px
930
838
  }
931
839
  .rickshaw_legend .line.disabled {
932
840
  opacity: .4
@@ -1051,3 +959,41 @@ div.interval-slider input {
1051
959
  padding: 3px 7px;
1052
960
  margin-left: 5px;
1053
961
  }
962
+
963
+ .metrics-swatch-wrapper {
964
+ display: flex;
965
+ align-items: center;
966
+ gap: 6px;
967
+ }
968
+
969
+ .metrics-swatch[type=checkbox] {
970
+ display: inline-block;
971
+ width: 16px;
972
+ height: 16px;
973
+ margin: 0;
974
+ border-radius: 2px;
975
+ appearance: none;
976
+ -webkit-appearance: none;
977
+ -moz-appearance: none;
978
+ border: 1px solid #bbb;
979
+ color: white;
980
+ background-color: currentColor;
981
+ }
982
+
983
+ /* We need to add the checkmark since we've taken over the appearance */
984
+ .metrics-swatch[type=checkbox]:checked {
985
+ border-color: currentColor;
986
+ background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e");
987
+ background-size: 100% 100%;
988
+ background-position: center;
989
+ background-repeat: no-repeat;
990
+ }
991
+
992
+ .metrics-swatch[type=checkbox]:focus {
993
+ outline: 1px solid #888;
994
+ outline-offset: 2px;
995
+ }
996
+
997
+ canvas {
998
+ margin: 20px 0 30px;
999
+ }