rails_observatory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +42 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/rails_observatory_manifest.js +2 -0
  5. data/app/assets/images/rails_observatory/logo.svg +8 -0
  6. data/app/assets/js/application.js +88 -0
  7. data/app/assets/js/controllers/chart_controller.js +176 -0
  8. data/app/assets/js/controllers/event_details_controller.js +15 -0
  9. data/app/assets/js/controllers/index.js +9 -0
  10. data/app/assets/js/controllers/sparkline_controller.js +72 -0
  11. data/app/assets/stylesheets/application/card.css +51 -0
  12. data/app/assets/stylesheets/application/chart.css +34 -0
  13. data/app/assets/stylesheets/application/dropdown.css +62 -0
  14. data/app/assets/stylesheets/application/global_modifiers.css +10 -0
  15. data/app/assets/stylesheets/application/query_table.css +68 -0
  16. data/app/assets/stylesheets/application/side_nav.css +62 -0
  17. data/app/assets/stylesheets/application/side_panel.css +35 -0
  18. data/app/assets/stylesheets/application/tab_nav.css +64 -0
  19. data/app/assets/stylesheets/application/table_chart.css +66 -0
  20. data/app/assets/stylesheets/application/tbd.css +70 -0
  21. data/app/assets/stylesheets/application/top_nav.css +33 -0
  22. data/app/assets/stylesheets/application.css +42 -0
  23. data/app/assets/stylesheets/elements/a.css +8 -0
  24. data/app/assets/stylesheets/elements/button.css +21 -0
  25. data/app/assets/stylesheets/elements/details.css +12 -0
  26. data/app/assets/stylesheets/elements/root.css +26 -0
  27. data/app/assets/stylesheets/elements/section.css +9 -0
  28. data/app/assets/stylesheets/errors/show/details.css +13 -0
  29. data/app/assets/stylesheets/layout/app.css +23 -0
  30. data/app/assets/stylesheets/layout/details-side-panel.css +15 -0
  31. data/app/assets/stylesheets/layout/requests.css +45 -0
  32. data/app/assets/stylesheets/layout/two-column.css +17 -0
  33. data/app/assets/stylesheets/mixins/nav_button.css +19 -0
  34. data/app/assets/stylesheets/requests/stats.css +35 -0
  35. data/app/controllers/rails_observatory/application_controller.rb +24 -0
  36. data/app/controllers/rails_observatory/errors_controller.rb +27 -0
  37. data/app/controllers/rails_observatory/jobs_controller.rb +25 -0
  38. data/app/controllers/rails_observatory/mailers_controller.rb +11 -0
  39. data/app/controllers/rails_observatory/requests_controller.rb +33 -0
  40. data/app/helpers/rails_observatory/application_helper.rb +110 -0
  41. data/app/jobs/rails_observatory/application_job.rb +4 -0
  42. data/app/mailers/rails_observatory/application_mailer.rb +6 -0
  43. data/app/views/layouts/rails_observatory/application.html.erb +93 -0
  44. data/app/views/new_user_mailer/greeting.html.erb +1 -0
  45. data/app/views/posts/index.html.erb +1 -0
  46. data/app/views/rails_observatory/application/_chart.html.erb +23 -0
  47. data/app/views/rails_observatory/application/_events_table.html.erb +24 -0
  48. data/app/views/rails_observatory/application/_sparkline.html.erb +17 -0
  49. data/app/views/rails_observatory/application/_trace.html.erb +122 -0
  50. data/app/views/rails_observatory/errors/index.html.erb +87 -0
  51. data/app/views/rails_observatory/errors/show.html.erb +193 -0
  52. data/app/views/rails_observatory/jobs/_table_chart.html.erb +29 -0
  53. data/app/views/rails_observatory/jobs/index.html.erb +20 -0
  54. data/app/views/rails_observatory/jobs/show.html.erb +8 -0
  55. data/app/views/rails_observatory/logs/index.html.erb +18 -0
  56. data/app/views/rails_observatory/mailers/index.html.erb +11 -0
  57. data/app/views/rails_observatory/mailers/show.html.erb +10 -0
  58. data/app/views/rails_observatory/requests/_text_gauge.html.erb +4 -0
  59. data/app/views/rails_observatory/requests/index.html.erb +56 -0
  60. data/app/views/rails_observatory/requests/show.html.erb +16 -0
  61. data/config/routes.rb +7 -0
  62. data/lib/rails_observatory/action_mailer_subscriber.rb +14 -0
  63. data/lib/rails_observatory/engine.rb +49 -0
  64. data/lib/rails_observatory/event_collector.rb +43 -0
  65. data/lib/rails_observatory/log_collector.rb +46 -0
  66. data/lib/rails_observatory/mailer_previews/delivered_mail_preview.rb +9 -0
  67. data/lib/rails_observatory/middleware.rb +77 -0
  68. data/lib/rails_observatory/models/error.rb +67 -0
  69. data/lib/rails_observatory/models/event_collection.rb +137 -0
  70. data/lib/rails_observatory/models/events.rb +22 -0
  71. data/lib/rails_observatory/models/job_trace.rb +28 -0
  72. data/lib/rails_observatory/models/logs.rb +9 -0
  73. data/lib/rails_observatory/models/mail_delivery.rb +33 -0
  74. data/lib/rails_observatory/models/redis_model.rb +112 -0
  75. data/lib/rails_observatory/models/request_trace.rb +29 -0
  76. data/lib/rails_observatory/railties/active_job_instrumentation.rb +48 -0
  77. data/lib/rails_observatory/railties/redis_runtime.rb +11 -0
  78. data/lib/rails_observatory/redis/logging_middleware.rb +22 -0
  79. data/lib/rails_observatory/redis/redis_client_instrumentation.rb +18 -0
  80. data/lib/rails_observatory/redis/time_series/increment_script.lua +67 -0
  81. data/lib/rails_observatory/redis/time_series/insertion.rb +73 -0
  82. data/lib/rails_observatory/redis/time_series/query_builder.rb +149 -0
  83. data/lib/rails_observatory/redis/time_series/timing_script.lua +89 -0
  84. data/lib/rails_observatory/redis/time_series.rb +91 -0
  85. data/lib/rails_observatory/serializers/event_serializer.rb +19 -0
  86. data/lib/rails_observatory/serializers/headers_serializer.rb +12 -0
  87. data/lib/rails_observatory/serializers/job_serializer.rb +11 -0
  88. data/lib/rails_observatory/serializers/mail_delivery_job_serializer.rb +14 -0
  89. data/lib/rails_observatory/serializers/request_serializer.rb +17 -0
  90. data/lib/rails_observatory/serializers/response_serializer.rb +14 -0
  91. data/lib/rails_observatory/serializers/serializer.rb +51 -0
  92. data/lib/rails_observatory/version.rb +3 -0
  93. data/lib/rails_observatory.rb +3 -0
  94. data/public/assets/js/application.js +11186 -0
  95. data/public/assets/logo_with_text.svg +21 -0
  96. data/public/assets/stylesheets/application.css +757 -0
  97. metadata +197 -0
@@ -0,0 +1,62 @@
1
+ .side-nav {
2
+ border-right: 1px solid var(--divider);
3
+ padding: 1rem;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 1rem;
7
+ align-items: stretch;
8
+ background: rgb(0, 144, 255);
9
+ background: radial-gradient(ellipse 100% 4rem at top, rgba(0, 144, 255, 0.4) 0%, rgba(22, 26, 29, 1) 100%);
10
+
11
+ ._active {
12
+ background-color: var(--surface-active);
13
+ color: var(--white);
14
+
15
+ &:hover {
16
+ background-color: var(--surface-active);
17
+ }
18
+ }
19
+
20
+ ._redis-stats {
21
+ align-self: center;
22
+ color: var(--black-secondary);
23
+ font-size: .85rem;
24
+
25
+ }
26
+
27
+ & > img {
28
+ align-self: start;
29
+ padding-inline: .75rem;
30
+ transition: all 500ms ease-in-out;
31
+
32
+ &:hover {
33
+ filter: drop-shadow(0px 0px 1px rgba(255, 255, 255, .8));
34
+ }
35
+ }
36
+
37
+
38
+ & > ul {
39
+ list-style: none;
40
+ padding: 0;
41
+ margin: 0;
42
+ display: flex;
43
+ flex-direction: column;
44
+ gap: .25rem;
45
+ flex-grow: 1;
46
+
47
+ & a {
48
+ display: flex;
49
+ align-items: center;
50
+ padding: .5rem .75rem;
51
+ gap: .5rem;
52
+ border-radius: .5rem;
53
+ text-decoration: none;
54
+ font-weight: 400;
55
+ color: color-mix(in oklab, var(--black) 20%, white);
56
+
57
+ &:is(:hover, :focus-visible) {
58
+ background-color: var(--surface-hover);
59
+ }
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,35 @@
1
+ .side-panel {
2
+ color: var(--white);
3
+ padding: 2rem;
4
+
5
+ ._occurrence_count {
6
+ font-size: 1.5rem;
7
+ font-weight: 400;
8
+ }
9
+
10
+ & dl {
11
+ margin: 0;
12
+
13
+ & dt {
14
+ font-weight: 400;
15
+ margin: 0;
16
+ color: var(--black-secondary);
17
+ font-size: 1.15rem;
18
+ }
19
+
20
+ & dd {
21
+ margin: 0;
22
+ padding-block: .5rem;
23
+
24
+ &[title] {
25
+ text-decoration: underline;
26
+ text-decoration-style: dotted;
27
+ text-decoration-color: var(--black-secondary);
28
+ }
29
+ }
30
+
31
+ & dd + dt {
32
+ margin-top: 2.5rem;
33
+ }
34
+ }
35
+ }
@@ -0,0 +1,64 @@
1
+ .tabs {
2
+
3
+ display: grid;
4
+ grid-template-rows: min-content auto;
5
+ grid-template-columns: minmax(0, 1fr);
6
+
7
+ .tabs-nav {
8
+ border-bottom: 1px solid var(--divider);
9
+ /*border-top: 1px solid var(--divider);*/
10
+ padding-block: .5rem;
11
+ padding-inline: 1.25rem;
12
+
13
+ display: flex;
14
+ gap: .25rem;
15
+
16
+ }
17
+
18
+ &:has(:nth-child(1 of .tabs-button) input:checked) { :nth-child(1 of .tab-content) {display: block;} }
19
+ &:has(:nth-child(2 of .tabs-button) input:checked) { :nth-child(2 of .tab-content) {display: block;} }
20
+ &:has(:nth-child(3 of .tabs-button) input:checked) { :nth-child(3 of .tab-content) {display: block;} }
21
+ &:has(:nth-child(4 of .tabs-button) input:checked) { :nth-child(4 of .tab-content) {display: block;} }
22
+ &:has(:nth-child(5 of .tabs-button) input:checked) { :nth-child(5 of .tab-content) {display: block;} }
23
+ &:has(:nth-child(6 of .tabs-button) input:checked) { :nth-child(6 of .tab-content) {display: block;} }
24
+ &:has(:nth-child(7 of .tabs-button) input:checked) { :nth-child(7 of .tab-content) {display: block;} }
25
+ &:has(:nth-child(8 of .tabs-button) input:checked) { :nth-child(8 of .tab-content) {display: block;} }
26
+ &:has(:nth-child(9 of .tabs-button) input:checked) { :nth-child(9 of .tab-content) {display: block;} }
27
+
28
+ .tabs-button {
29
+ /* Mixin .mixin-nav-button */
30
+
31
+ & input {
32
+ position: absolute;
33
+ /*z-index: -100;*/
34
+ opacity: 0;
35
+
36
+ }
37
+
38
+ &:has(:focus-visible) {
39
+ outline: -webkit-focus-ring-color auto 1px;
40
+ }
41
+
42
+ &:has(:checked) {
43
+ background-color: var(--surface-active);
44
+ }
45
+ }
46
+
47
+ /*& label:has(:focus) {*/
48
+ /* outline: -webkit-focus-ring-color auto 1px;*/
49
+ /*}*/
50
+
51
+ .tab-content {
52
+ display: none;
53
+ padding-bottom: 2rem;
54
+
55
+ & dd {
56
+ margin-left: 0;
57
+ }
58
+ & dt {
59
+ font-weight: 400;
60
+ color: var(--black-secondary);
61
+ margin-bottom: .75rem;
62
+ }
63
+ }
64
+ }
@@ -0,0 +1,66 @@
1
+ .table-chart {
2
+ .table-chart-title {
3
+ font-size: 1.5rem;
4
+ font-weight: 400;
5
+ margin: 0;
6
+ padding: 1rem;
7
+ border-bottom: 1px solid var(--divider);
8
+ }
9
+
10
+ & tr:has(a.table-chart-row-action:hover) {
11
+ background-color: var(--surface-hover);
12
+ }
13
+
14
+ & a.table-chart-row-action::before {
15
+ position: absolute;
16
+ content: '';
17
+ top: 0;
18
+ left: 0;
19
+ right:0;
20
+ bottom: 0;
21
+ }
22
+
23
+ & > table {
24
+ width: 100%;
25
+ border-spacing: 0;
26
+ white-space: nowrap;
27
+
28
+ & th {
29
+ text-align: left;
30
+ font-weight: 400;
31
+ padding: 0.5rem 1rem;
32
+ border-bottom: 1px solid var(--divider);
33
+ & + & {
34
+ border-left: 1px solid var(--divider);
35
+ }
36
+ &:first-child {
37
+ padding-left: 2rem;
38
+ }
39
+
40
+ &:last-child {
41
+ padding-right: 2rem;
42
+ }
43
+ }
44
+
45
+ & tr {
46
+ position: relative;
47
+ }
48
+
49
+ & td {
50
+ padding: .5rem 1rem;
51
+ border-bottom: 1px solid var(--divider);
52
+
53
+ & + & {
54
+ border-left: 1px solid var(--divider);
55
+ }
56
+
57
+ &:first-child {
58
+ padding-left: 2rem;
59
+ }
60
+
61
+ &:last-child {
62
+ padding-right: 2rem;
63
+ }
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,70 @@
1
+ .simple-list {
2
+ list-style: none;
3
+ padding: 0;
4
+ margin: 0;
5
+ display: flex;
6
+ flex-direction: column;
7
+ gap: .25rem;
8
+ flex-grow: 1;
9
+
10
+ .simple-list-title {
11
+ font-weight: var(--font-weight-bold);
12
+ }
13
+
14
+ & li {
15
+ padding: 2rem;
16
+ }
17
+
18
+ & li + li {
19
+ border-top: 1px solid var(--divider);
20
+ }
21
+
22
+ & a {
23
+ color: color-mix(in oklab, var(--black) 20%, white);
24
+
25
+ &:is(:hover, :focus-visible) {
26
+ background-color: var(--surface-hover);
27
+ }
28
+ }
29
+ }
30
+
31
+ .event-detail {
32
+
33
+ &:not([hidden]) {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 1rem;
37
+ }
38
+
39
+ max-width: 120ch;
40
+
41
+ .highlight {
42
+ border: 1px solid var(--divider);
43
+
44
+ .line {
45
+ padding: 0 1rem;
46
+ overflow: hidden;
47
+ text-overflow: ellipsis;
48
+
49
+ &:hover {
50
+ background-color: var(--surface-hover);
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ text-gauge {
57
+ display: flex;
58
+ align-items: center;
59
+ flex-direction: column;
60
+ padding-block: 1rem;
61
+ padding-inline: 2rem;
62
+ white-space: nowrap;
63
+
64
+ & text-gauge-title {}
65
+
66
+ & text-gauge-value {
67
+ font-size: 3rem;
68
+ font-weight: 400;
69
+ }
70
+ }
@@ -0,0 +1,33 @@
1
+ .top-nav {
2
+ grid-area: top-nav;
3
+ border-bottom: 1px solid var(--divider);
4
+ padding: 1rem 2rem;
5
+ display: flex;
6
+ justify-content: space-between;
7
+ align-items: center;
8
+
9
+ ._subtitle {
10
+ font-weight: 200;
11
+ color: var(--black-secondary);
12
+ }
13
+
14
+
15
+ & > span {
16
+ display: flex;
17
+ align-items: baseline;
18
+ gap: .5rem;
19
+
20
+ & h1 {
21
+ margin: 0;
22
+ align-items: baseline;
23
+ }
24
+ }
25
+
26
+ .status-success {
27
+ /*color: #3ad23a;*/
28
+ background-color: #38a238;
29
+ padding: .25rem .5rem;
30
+ border-radius: .5rem;
31
+ font-size:1.25rem;
32
+ }
33
+ }
@@ -0,0 +1,42 @@
1
+ /* Element override styling */
2
+ @import url('elements/root.css');
3
+ @import url('elements/a.css');
4
+ @import url('elements/button.css');
5
+ @import url('elements/details.css');
6
+ @import url('elements/section.css');
7
+
8
+ /* Layout */
9
+ @import url('layout/app.css');
10
+ @import url('layout/details-side-panel.css');
11
+ @import url('layout/requests.css');
12
+ @import url('layout/two-column.css');
13
+
14
+ /* Application styles */
15
+ @import url('application/global_modifiers.css');
16
+ @import url('application/card.css');
17
+ @import url('application/dropdown.css');
18
+ @import url('application/side_nav.css');
19
+ @import url('application/top_nav.css');
20
+ @import url('application/tab_nav.css');
21
+ @import url('application/side_panel.css');
22
+ @import url('application/chart.css');
23
+ @import url('application/table_chart.css');
24
+ @import url("application/query_table.css");
25
+ @import url("application/tbd.css");
26
+
27
+ /* Mixins */
28
+ @import url('mixins/nav_button.css');
29
+
30
+
31
+ /* page styles */
32
+ @import url('errors/show/details.css');
33
+
34
+ @import url('requests/stats.css');
35
+
36
+ @import url('https://fonts.googleapis.com/css2?family=Fira+Sans:wght@200;400&family=Roboto+Mono:wght@200;400&display=swap');
37
+
38
+
39
+ main {
40
+ grid-area: main;
41
+ overflow: auto;
42
+ }
@@ -0,0 +1,8 @@
1
+ a {
2
+ color: var(--white);
3
+ text-decoration: none;
4
+
5
+ &:hover {
6
+ text-decoration: underline;
7
+ }
8
+ }
@@ -0,0 +1,21 @@
1
+ button {
2
+ font-family: inherit;
3
+ display: flex;
4
+ font-size: 1rem;
5
+ align-items: center;
6
+ padding: .5rem .75rem;
7
+ border-radius: .5rem;
8
+ gap: .5rem;
9
+ font-weight: 400;
10
+
11
+ &.secondary {
12
+ background-color: transparent;
13
+ border: 1px solid var(--divider);
14
+ color: var(--white);
15
+
16
+ &:hover, &:focus-within {
17
+ border: 1px solid var(--divider-active);
18
+ background-color: var(--surface-hover);
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,12 @@
1
+ details {
2
+ .arrow-right {
3
+ transform: rotate(0deg);
4
+ transition: transform 100ms linear;
5
+ }
6
+
7
+ &[open] {
8
+ & >summary .arrow-right:first-child {
9
+ transform: rotate(90deg);
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,26 @@
1
+ :root {
2
+ --red: #ba181b;
3
+ --black: #161a1d;
4
+ --black-secondary: color-mix(in oklab, var(--black) 60%, white);
5
+ --blue: #003a61;
6
+ --gray: #b1a7a6;
7
+ --white: #eee;
8
+ --divider: color-mix(in oklab, var(--black) 88%, white);
9
+ --divider-active: color-mix(in oklab, var(--black) 75%, white);
10
+ --surface-active: color-mix(in oklab, var(--black) 92%, white);
11
+ --surface-hover: color-mix(in oklab, var(--black) 96%, white);
12
+ --surface-dropdown: color-mix(in oklab, var(--black) 88%, white);
13
+ --surface-card: color-mix(in oklab, var(--black) 96%, white);
14
+
15
+ --font-mono: 'Roboto Mono', monospace;
16
+ --font-weight-bold: 400;
17
+ }
18
+
19
+
20
+ html, body {
21
+ font-family: 'Fira Sans', sans-serif;
22
+ font-weight: 200;
23
+ color: var(--white);
24
+ height: 100%;
25
+ background-color: var(--black);
26
+ }
@@ -0,0 +1,9 @@
1
+ section {
2
+ h2 {
3
+ font-size: 1.5rem;
4
+ font-weight: 400;
5
+ margin: 0;
6
+ padding: 2rem;
7
+ border-bottom: 1px solid var(--divider);
8
+ }
9
+ }
@@ -0,0 +1,13 @@
1
+ .errors\/show\/details {
2
+ grid-area: details;
3
+ margin: 1rem;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: 1rem;
7
+
8
+ & > h2 {
9
+ margin-block: 1rem;
10
+ padding-left: 1rem;
11
+ font-size: 1.25rem;
12
+ }
13
+ }
@@ -0,0 +1,23 @@
1
+ @layer layout {
2
+ .layout-app {
3
+ display: grid;
4
+ grid-template-areas: 'side-nav top-nav'
5
+ 'side-nav main';
6
+ grid-template-columns: 16rem 1fr;
7
+ grid-template-rows: auto 1fr;
8
+ margin: 0;
9
+
10
+ & > .layout-app-side-nav {
11
+ grid-area: side-nav;
12
+ }
13
+
14
+ & > .layout-app-top-nav {
15
+ grid-area: top-nav;
16
+ }
17
+
18
+ & > .layout-app-main {
19
+ grid-area: main;
20
+ overflow: auto;
21
+ }
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ .layout\/details-side-panel {
2
+ padding: 0;
3
+ display: grid;
4
+ grid-template-areas:'details side-panel';
5
+ grid-template-columns: minmax(400px, 1fr) 300px;
6
+
7
+
8
+ & > .details {
9
+ grid-area: details;
10
+ }
11
+
12
+ & > .side-panel {
13
+ grid-area: side-panel;
14
+ }
15
+ }
@@ -0,0 +1,45 @@
1
+ @layer layout {
2
+ .layout-requests_index {
3
+ display: grid;
4
+
5
+ grid-template-areas:
6
+ 'glance glance'
7
+ 'chart chart'
8
+ /*'chart chart'*/
9
+ 'bycontroller bycontroller'
10
+ 'events events';
11
+ grid-template-columns: 1fr 1fr;
12
+ grid-template-rows: min-content min-content auto auto;
13
+ gap: 2rem;
14
+
15
+ & > .layout-requests_index-glance {
16
+ grid-area: glance;
17
+ }
18
+
19
+ & > .layout-requests_index-chart {
20
+ grid-area: chart;
21
+ display: grid;
22
+ grid-template-columns: subgrid;
23
+ padding-inline: 2rem;
24
+ }
25
+
26
+ & > .layout-requests_index-by_controller {
27
+ grid-area: bycontroller;
28
+ }
29
+
30
+ & > .layout-requests_index-events {
31
+ grid-area: events;
32
+ }
33
+ }
34
+
35
+ .layout-events-breakdown {
36
+ display: grid;
37
+ grid-template-rows: min-content auto;
38
+ grid-template-columns: 1fr;
39
+ gap: 1rem;
40
+
41
+ & > .layout-events-breakdown-details {
42
+ border-top: 1px solid var(--divider);
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,17 @@
1
+ @layer layout {
2
+ .layout-two-column {
3
+ display: grid;
4
+ grid-template-columns: minmax(0,1fr) 300px;
5
+ grid-template-areas: 'main side-panel';
6
+ gap: 2rem;
7
+
8
+ & > .layout-two-column-main {
9
+ grid-area: main;
10
+ padding: 1rem;
11
+ }
12
+
13
+ & > .layout-two-column-side-panel {
14
+ grid-area: side-panel;
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,19 @@
1
+ /* Mixin: Nav Button*/
2
+
3
+ @layer mixin {
4
+ .tabs .tabs-button,
5
+ .mixin-nav-button {
6
+ display: flex;
7
+ align-items: center;
8
+ padding: .5rem .75rem;
9
+ gap: .5rem;
10
+ border-radius: .5rem;
11
+ text-decoration: none;
12
+ font-weight: 400;
13
+ color: color-mix(in oklab, var(--black) 20%, white);
14
+
15
+ &:is(:hover, :focus-visible) {
16
+ background-color: var(--surface-hover);
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,35 @@
1
+ .table-bar-chart {
2
+ display: grid;
3
+ grid-template-columns: auto min-content min-content;
4
+ column-gap: 1rem;
5
+
6
+ .table-bar-row {
7
+ position: relative;
8
+ background-color: transparent;
9
+ display: grid;
10
+ grid-template-columns: subgrid;
11
+ grid-column: 1 / -1;
12
+ padding: .5rem;
13
+
14
+ &:hover {
15
+ background-color: var(--surface-hover);
16
+ }
17
+ }
18
+
19
+ .table-bar-header {
20
+ grid-column: 1 / -1;
21
+ border-bottom: 1px solid var(--divider);
22
+ font-weight: var(--font-weight-bold);
23
+ padding: .5rem 0;
24
+ }
25
+
26
+ .bar {
27
+ background-color: var(--blue);
28
+ position: absolute;
29
+ top: 0;
30
+ left:0;
31
+ bottom:0;
32
+ mix-blend-mode: lighten;
33
+ z-index: 0;
34
+ }
35
+ }
@@ -0,0 +1,24 @@
1
+ module RailsObservatory
2
+ class ApplicationController < ::ActionController::Base
3
+
4
+ before_action :set_duration
5
+ around_action :set_time_range
6
+ def set_time_range
7
+ TimeSeries.with_slice(duration.seconds.ago..) do
8
+ yield
9
+ end
10
+ end
11
+
12
+ def set_duration
13
+ if params[:duration].presence
14
+ session[:duration] = params[:duration].to_i
15
+ end
16
+ end
17
+
18
+ def duration
19
+ ActiveSupport::Duration.build((session[:duration] || 1.hour).to_i)
20
+ end
21
+ helper_method :duration
22
+
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module RailsObservatory
2
+ class ErrorsController < ApplicationController
3
+
4
+ before_action :set_duration
5
+
6
+ def index
7
+ Error.ensure_index
8
+ @errors = Error.all
9
+ @series_by_fingerprint = TimeSeries.where(name: "error.count", fingerprint: @errors.map(&:fingerprint))
10
+ .downsample(12, using: :sum)
11
+ .index_by { _1.labels[:fingerprint] }
12
+ @count_by_fingerprint = TimeSeries.where(name: "error.count", fingerprint: @errors.map(&:fingerprint)).group(:fingerprint).sum
13
+ end
14
+
15
+ def show
16
+ @time_range = (1.hour.ago..)
17
+ @error = Error.find(params[:id])
18
+ series = TimeSeries.where(name: "error.count", fingerprint: @error.fingerprint)
19
+ .downsample(24, using: :sum)
20
+ @count = TimeSeries.where(name: "error.count", fingerprint: @error.fingerprint).slice(2.years.ago..).downsample(1, using: :sum).first.value
21
+ # puts series.slice(1.day.ago..).to_a.size
22
+ @past_24_hours = series.slice(24.hours.ago..).first
23
+ @past_7_days = series.slice(7.days.ago..).first
24
+ @past_30_days = series.slice(30.days.ago..).first
25
+ end
26
+ end
27
+ end