code_sunset 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +242 -0
  4. data/Rakefile +6 -0
  5. data/app/assets/images/code_sunset/.keep +0 -0
  6. data/app/assets/images/code_sunset/logo-dark.png +0 -0
  7. data/app/assets/images/code_sunset/logo-light.png +0 -0
  8. data/app/assets/images/code_sunset/topography.svg +1 -0
  9. data/app/assets/javascripts/code_sunset/application.js +179 -0
  10. data/app/assets/javascripts/code_sunset/vendor/chart.umd.min.js +14 -0
  11. data/app/assets/stylesheets/code_sunset/application.css +1075 -0
  12. data/app/controllers/code_sunset/application_controller.rb +50 -0
  13. data/app/controllers/code_sunset/dashboard_controller.rb +12 -0
  14. data/app/controllers/code_sunset/features_controller.rb +62 -0
  15. data/app/controllers/code_sunset/removal_candidates_controller.rb +9 -0
  16. data/app/controllers/concerns/.keep +0 -0
  17. data/app/helpers/code_sunset/application_helper.rb +244 -0
  18. data/app/jobs/code_sunset/aggregate_daily_rollups_job.rb +48 -0
  19. data/app/jobs/code_sunset/application_job.rb +4 -0
  20. data/app/jobs/code_sunset/cleanup_events_job.rb +10 -0
  21. data/app/jobs/code_sunset/evaluate_alerts_job.rb +15 -0
  22. data/app/jobs/code_sunset/flush_buffered_events_job.rb +9 -0
  23. data/app/jobs/code_sunset/persist_event_job.rb +9 -0
  24. data/app/mailers/code_sunset/application_mailer.rb +6 -0
  25. data/app/models/code_sunset/alert_delivery.rb +14 -0
  26. data/app/models/code_sunset/application_record.rb +5 -0
  27. data/app/models/code_sunset/daily_rollup.rb +31 -0
  28. data/app/models/code_sunset/event.rb +190 -0
  29. data/app/models/code_sunset/feature.rb +33 -0
  30. data/app/models/concerns/.keep +0 -0
  31. data/app/views/code_sunset/dashboard/index.html.erb +123 -0
  32. data/app/views/code_sunset/features/index.html.erb +88 -0
  33. data/app/views/code_sunset/features/show.html.erb +266 -0
  34. data/app/views/code_sunset/removal_candidates/index.html.erb +118 -0
  35. data/app/views/code_sunset/shared/_filter_bar.html.erb +81 -0
  36. data/app/views/layouts/code_sunset/application.html.erb +57 -0
  37. data/bin/rails +26 -0
  38. data/code_sunset.gemspec +35 -0
  39. data/config/routes.rb +5 -0
  40. data/db/migrate/20260330000000_create_code_sunset_tables.rb +62 -0
  41. data/db/migrate/20260331000000_harden_code_sunset_reliability.rb +39 -0
  42. data/lib/code_sunset/alert_dispatcher.rb +103 -0
  43. data/lib/code_sunset/analytics_filters.rb +59 -0
  44. data/lib/code_sunset/configuration.rb +104 -0
  45. data/lib/code_sunset/context.rb +49 -0
  46. data/lib/code_sunset/controller_context.rb +23 -0
  47. data/lib/code_sunset/engine.rb +24 -0
  48. data/lib/code_sunset/event_buffer.rb +28 -0
  49. data/lib/code_sunset/event_ingestor.rb +57 -0
  50. data/lib/code_sunset/event_payload.rb +79 -0
  51. data/lib/code_sunset/feature_query.rb +69 -0
  52. data/lib/code_sunset/feature_snapshot.rb +14 -0
  53. data/lib/code_sunset/feature_usage_series.rb +59 -0
  54. data/lib/code_sunset/identity_hash.rb +9 -0
  55. data/lib/code_sunset/instrumentation.rb +17 -0
  56. data/lib/code_sunset/job_context.rb +17 -0
  57. data/lib/code_sunset/removal_candidate_query.rb +17 -0
  58. data/lib/code_sunset/removal_prompt_builder.rb +331 -0
  59. data/lib/code_sunset/score_calculator.rb +83 -0
  60. data/lib/code_sunset/status_engine.rb +38 -0
  61. data/lib/code_sunset/subscriber.rb +24 -0
  62. data/lib/code_sunset/version.rb +3 -0
  63. data/lib/code_sunset.rb +109 -0
  64. data/lib/generators/code_sunset/eject_ui/eject_ui_generator.rb +33 -0
  65. data/lib/generators/code_sunset/initializer/initializer_generator.rb +13 -0
  66. data/lib/generators/code_sunset/initializer/templates/code_sunset.rb +34 -0
  67. data/lib/generators/code_sunset/install/install_generator.rb +23 -0
  68. data/lib/generators/code_sunset/migration/migration_generator.rb +21 -0
  69. data/lib/generators/code_sunset/migration/templates/create_code_sunset_tables.rb +75 -0
  70. data/lib/tasks/code_sunset_tasks.rake +21 -0
  71. metadata +185 -0
@@ -0,0 +1,179 @@
1
+ (function() {
2
+ function parseJson(value) {
3
+ try {
4
+ return JSON.parse(value || "[]");
5
+ } catch (_error) {
6
+ return [];
7
+ }
8
+ }
9
+
10
+ function destroyChart(canvas) {
11
+ if (canvas.codeSunsetChart) {
12
+ canvas.codeSunsetChart.destroy();
13
+ canvas.codeSunsetChart = null;
14
+ }
15
+ }
16
+
17
+ function buildChart(canvas) {
18
+ if (!window.Chart) {
19
+ return;
20
+ }
21
+
22
+ destroyChart(canvas);
23
+
24
+ var labels = parseJson(canvas.dataset.labels);
25
+ var values = parseJson(canvas.dataset.values);
26
+
27
+ if (!labels.length || !values.length) {
28
+ return;
29
+ }
30
+
31
+ canvas.codeSunsetChart = new window.Chart(canvas.getContext("2d"), {
32
+ type: "line",
33
+ data: {
34
+ labels: labels,
35
+ datasets: [
36
+ {
37
+ data: values,
38
+ borderColor: "#635bff",
39
+ backgroundColor: "rgba(99, 91, 255, 0.10)",
40
+ borderWidth: 2,
41
+ fill: true,
42
+ pointRadius: 0,
43
+ pointHoverRadius: 4,
44
+ pointHoverBackgroundColor: "#635bff",
45
+ pointHoverBorderColor: "#ffffff",
46
+ pointHoverBorderWidth: 2,
47
+ tension: 0.35
48
+ }
49
+ ]
50
+ },
51
+ options: {
52
+ animation: false,
53
+ maintainAspectRatio: false,
54
+ plugins: {
55
+ legend: {
56
+ display: false
57
+ },
58
+ tooltip: {
59
+ backgroundColor: "#0f172a",
60
+ displayColors: false,
61
+ padding: 10,
62
+ titleColor: "#f8fafc",
63
+ bodyColor: "#e2e8f0",
64
+ callbacks: {
65
+ label: function(context) {
66
+ var value = context.parsed.y || 0;
67
+ return value + " hit" + (value === 1 ? "" : "s");
68
+ }
69
+ }
70
+ }
71
+ },
72
+ scales: {
73
+ x: {
74
+ grid: {
75
+ display: false
76
+ },
77
+ border: {
78
+ display: false
79
+ },
80
+ ticks: {
81
+ color: "#6b7c93",
82
+ maxRotation: 0,
83
+ autoSkipPadding: 18,
84
+ font: {
85
+ size: 11
86
+ }
87
+ }
88
+ },
89
+ y: {
90
+ beginAtZero: true,
91
+ grid: {
92
+ color: "rgba(15, 23, 42, 0.08)",
93
+ drawBorder: false
94
+ },
95
+ border: {
96
+ display: false
97
+ },
98
+ ticks: {
99
+ color: "#6b7c93",
100
+ precision: 0,
101
+ font: {
102
+ size: 11
103
+ }
104
+ }
105
+ }
106
+ }
107
+ }
108
+ });
109
+ }
110
+
111
+ function initializeCharts() {
112
+ document.querySelectorAll("[data-code-sunset-usage-chart]").forEach(buildChart);
113
+ }
114
+
115
+ function teardownCharts() {
116
+ document.querySelectorAll("[data-code-sunset-usage-chart]").forEach(destroyChart);
117
+ }
118
+
119
+ function copyText(value) {
120
+ if (navigator.clipboard && navigator.clipboard.writeText) {
121
+ return navigator.clipboard.writeText(value);
122
+ }
123
+
124
+ return new Promise(function(resolve, reject) {
125
+ var textarea = document.createElement("textarea");
126
+ textarea.value = value;
127
+ textarea.setAttribute("readonly", "readonly");
128
+ textarea.style.position = "absolute";
129
+ textarea.style.left = "-9999px";
130
+ document.body.appendChild(textarea);
131
+ textarea.select();
132
+
133
+ try {
134
+ document.execCommand("copy");
135
+ resolve();
136
+ } catch (error) {
137
+ reject(error);
138
+ } finally {
139
+ document.body.removeChild(textarea);
140
+ }
141
+ });
142
+ }
143
+
144
+ function bindCopyButtons() {
145
+ document.querySelectorAll("[data-copy-target]").forEach(function(button) {
146
+ if (button.dataset.copyBound === "true") {
147
+ return;
148
+ }
149
+
150
+ button.dataset.copyBound = "true";
151
+ button.addEventListener("click", function() {
152
+ var target = document.getElementById(button.dataset.copyTarget);
153
+ if (!target) {
154
+ return;
155
+ }
156
+
157
+ var defaultLabel = button.dataset.copyDefaultLabel || button.textContent;
158
+ var successLabel = button.dataset.copySuccessLabel || "Copied";
159
+
160
+ copyText(target.textContent || "")
161
+ .then(function() {
162
+ button.textContent = successLabel;
163
+ window.setTimeout(function() {
164
+ button.textContent = defaultLabel;
165
+ }, 1600);
166
+ })
167
+ .catch(function() {
168
+ button.textContent = defaultLabel;
169
+ });
170
+ });
171
+ });
172
+ }
173
+
174
+ document.addEventListener("DOMContentLoaded", initializeCharts);
175
+ document.addEventListener("turbo:load", initializeCharts);
176
+ document.addEventListener("DOMContentLoaded", bindCopyButtons);
177
+ document.addEventListener("turbo:load", bindCopyButtons);
178
+ document.addEventListener("turbo:before-cache", teardownCharts);
179
+ })();