feature_map 1.2.2 → 1.2.3
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.
- checksums.yaml +4 -4
- data/README.md +0 -1
- data/bin/featuremap +0 -1
- data/lib/feature_map/cli.rb +0 -2
- data/lib/feature_map/code_features/plugin.rb +2 -21
- data/lib/feature_map/code_features/plugins/identity.rb +1 -8
- data/lib/feature_map/code_features.rb +1 -31
- data/lib/feature_map/commit.rb +0 -19
- data/lib/feature_map/configuration.rb +40 -17
- data/lib/feature_map/constants.rb +3 -5
- data/lib/feature_map/mapper.rb +0 -26
- data/lib/feature_map/output_color.rb +0 -11
- data/lib/feature_map/private/additional_metrics_file.rb +9 -103
- data/lib/feature_map/private/assignment_applicator.rb +0 -12
- data/lib/feature_map/private/assignment_mappers/directory_assignment.rb +4 -26
- data/lib/feature_map/private/assignment_mappers/feature_definition_assignment.rb +1 -21
- data/lib/feature_map/private/assignment_mappers/feature_globs.rb +7 -40
- data/lib/feature_map/private/assignment_mappers/file_annotations.rb +20 -44
- data/lib/feature_map/private/assignments_file.rb +8 -54
- data/lib/feature_map/private/code_cov.rb +2 -29
- data/lib/feature_map/private/cyclomatic_complexity_calculator.rb +1 -7
- data/lib/feature_map/private/docs/index.html +2 -2
- data/lib/feature_map/private/documentation_site.rb +0 -16
- data/lib/feature_map/private/extension_loader.rb +0 -3
- data/lib/feature_map/private/feature_assigner.rb +0 -4
- data/lib/feature_map/private/feature_metrics_calculator.rb +2 -16
- data/lib/feature_map/private/feature_plugins/assignment.rb +0 -6
- data/lib/feature_map/private/glob_cache.rb +2 -29
- data/lib/feature_map/private/health_calculator.rb +122 -0
- data/lib/feature_map/private/lines_of_code_calculator.rb +10 -21
- data/lib/feature_map/private/metrics_file.rb +1 -25
- data/lib/feature_map/private/percentile_metrics_calculator.rb +117 -0
- data/lib/feature_map/private/release_notification_builder.rb +1 -13
- data/lib/feature_map/private/test_coverage_file.rb +12 -39
- data/lib/feature_map/private/test_pyramid_file.rb +0 -41
- data/lib/feature_map/private/todo_inspector.rb +16 -30
- data/lib/feature_map/private/validations/features_up_to_date.rb +1 -6
- data/lib/feature_map/private/validations/files_have_features.rb +2 -7
- data/lib/feature_map/private/validations/files_have_unique_features.rb +1 -6
- data/lib/feature_map/private.rb +7 -44
- data/lib/feature_map/validator.rb +0 -13
- data/lib/feature_map.rb +8 -49
- metadata +4 -44
data/lib/feature_map.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# typed: strict
|
4
|
-
|
5
3
|
require 'set'
|
6
|
-
|
4
|
+
|
7
5
|
require 'json'
|
8
6
|
require 'yaml'
|
9
7
|
require 'feature_map/commit'
|
@@ -20,23 +18,11 @@ module FeatureMap
|
|
20
18
|
|
21
19
|
module_function
|
22
20
|
|
23
|
-
extend T::Sig
|
24
|
-
extend T::Helpers
|
25
|
-
|
26
|
-
requires_ancestor { Kernel }
|
27
|
-
GlobsToAssignedFeatureMap = T.type_alias { T::Hash[String, CodeFeatures::Feature] }
|
28
|
-
|
29
|
-
UpdatedFeaturesByTeam = T.type_alias { T::Hash[String, CommitsByFeature] }
|
30
|
-
CommitsByFeature = T.type_alias { T::Hash[String, T::Array[Commit]] }
|
31
|
-
|
32
|
-
sig { params(assignments_file_path: String).void }
|
33
21
|
def apply_assignments!(assignments_file_path)
|
34
22
|
Private.apply_assignments!(assignments_file_path)
|
35
23
|
end
|
36
24
|
|
37
|
-
sig { params(file: String).returns(T.nilable(CodeFeatures::Feature)) }
|
38
25
|
def for_file(file)
|
39
|
-
@for_file ||= T.let(@for_file, T.nilable(T::Hash[String, T.nilable(CodeFeatures::Feature)]))
|
40
26
|
@for_file ||= {}
|
41
27
|
|
42
28
|
return nil if file.start_with?('./')
|
@@ -44,8 +30,7 @@ module FeatureMap
|
|
44
30
|
|
45
31
|
Private.load_configuration!
|
46
32
|
|
47
|
-
feature =
|
48
|
-
|
33
|
+
feature = nil
|
49
34
|
Mapper.all.each do |mapper|
|
50
35
|
feature = mapper.map_file_to_feature(file)
|
51
36
|
break if feature # TODO: what if there are multiple features? Should we respond with an error instead of the first match?
|
@@ -54,10 +39,9 @@ module FeatureMap
|
|
54
39
|
@for_file[file] = feature
|
55
40
|
end
|
56
41
|
|
57
|
-
sig { params(feature: T.any(CodeFeatures::Feature, String)).returns(String) }
|
58
42
|
def for_feature(feature)
|
59
|
-
feature =
|
60
|
-
feature_report =
|
43
|
+
feature = CodeFeatures.find(feature)
|
44
|
+
feature_report = []
|
61
45
|
|
62
46
|
feature_report << "# Report for `#{feature.name}` Feature"
|
63
47
|
|
@@ -85,18 +69,10 @@ module FeatureMap
|
|
85
69
|
class InvalidFeatureMapConfigurationError < StandardError
|
86
70
|
end
|
87
71
|
|
88
|
-
sig { params(filename: String).void }
|
89
72
|
def self.remove_file_annotation!(filename)
|
90
73
|
Private::AssignmentMappers::FileAnnotations.new.remove_file_annotation!(filename)
|
91
74
|
end
|
92
75
|
|
93
|
-
sig do
|
94
|
-
params(
|
95
|
-
autocorrect: T::Boolean,
|
96
|
-
stage_changes: T::Boolean,
|
97
|
-
files: T.nilable(T::Array[String])
|
98
|
-
).void
|
99
|
-
end
|
100
76
|
def validate!(
|
101
77
|
autocorrect: true,
|
102
78
|
stage_changes: true,
|
@@ -113,43 +89,30 @@ module FeatureMap
|
|
113
89
|
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
|
114
90
|
end
|
115
91
|
|
116
|
-
sig { params(git_ref: T.nilable(String)).void }
|
117
92
|
def generate_docs!(git_ref)
|
118
93
|
Private.generate_docs!(git_ref)
|
119
94
|
end
|
120
95
|
|
121
|
-
sig do
|
122
|
-
params(
|
123
|
-
unit_path: String,
|
124
|
-
integration_path: String,
|
125
|
-
regression_path: T.nilable(String),
|
126
|
-
regression_assignments_path: T.nilable(String)
|
127
|
-
).void
|
128
|
-
end
|
129
96
|
def generate_test_pyramid!(unit_path, integration_path, regression_path, regression_assignments_path)
|
130
97
|
Private.generate_test_pyramid!(unit_path, integration_path, regression_path, regression_assignments_path)
|
131
98
|
end
|
132
99
|
|
133
|
-
sig { params(commit_sha: String, code_cov_token: String).void }
|
134
100
|
def gather_test_coverage!(commit_sha, code_cov_token)
|
135
101
|
Private.gather_test_coverage!(commit_sha, code_cov_token)
|
136
102
|
end
|
137
103
|
|
138
|
-
sig { void }
|
139
104
|
def generate_additional_metrics!
|
140
105
|
Private.generate_additional_metrics!
|
141
106
|
end
|
142
107
|
|
143
108
|
# Given a backtrace from either `Exception#backtrace` or `caller`, find the
|
144
109
|
# first line that corresponds to a file with an assigned feature
|
145
|
-
sig { params(backtrace: T.nilable(T::Array[String]), excluded_features: T::Array[CodeFeatures::Feature]).returns(T.nilable(CodeFeatures::Feature)) }
|
146
110
|
def for_backtrace(backtrace, excluded_features: [])
|
147
111
|
first_assigned_file_for_backtrace(backtrace, excluded_features: excluded_features)&.first
|
148
112
|
end
|
149
113
|
|
150
114
|
# Given a backtrace from either `Exception#backtrace` or `caller`, find the
|
151
115
|
# first assigned file in it, useful for figuring out which file is being blamed.
|
152
|
-
sig { params(backtrace: T.nilable(T::Array[String]), excluded_features: T::Array[CodeFeatures::Feature]).returns(T.nilable([CodeFeatures::Feature, String])) }
|
153
116
|
def first_assigned_file_for_backtrace(backtrace, excluded_features: [])
|
154
117
|
backtrace_with_feature_assignments(backtrace).each do |(feature, file)|
|
155
118
|
if feature && !excluded_features.include?(feature)
|
@@ -160,7 +123,6 @@ module FeatureMap
|
|
160
123
|
nil
|
161
124
|
end
|
162
125
|
|
163
|
-
sig { params(backtrace: T.nilable(T::Array[String])).returns(T::Enumerable[[T.nilable(CodeFeatures::Feature), String]]) }
|
164
126
|
def backtrace_with_feature_assignments(backtrace)
|
165
127
|
return [] unless backtrace
|
166
128
|
|
@@ -183,7 +145,7 @@ module FeatureMap
|
|
183
145
|
match = line.match(backtrace_line)
|
184
146
|
next unless match
|
185
147
|
|
186
|
-
file =
|
148
|
+
file = match[:file]
|
187
149
|
|
188
150
|
[
|
189
151
|
FeatureMap.for_file(file),
|
@@ -193,9 +155,7 @@ module FeatureMap
|
|
193
155
|
end
|
194
156
|
private_class_method(:backtrace_with_feature_assignments)
|
195
157
|
|
196
|
-
sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(CodeFeatures::Feature)) }
|
197
158
|
def for_class(klass)
|
198
|
-
@memoized_values ||= T.let(@memoized_values, T.nilable(T::Hash[String, T.nilable(CodeFeatures::Feature)]))
|
199
159
|
@memoized_values ||= {}
|
200
160
|
# We use key because the memoized value could be `nil`
|
201
161
|
if @memoized_values.key?(klass.to_s)
|
@@ -213,7 +173,7 @@ module FeatureMap
|
|
213
173
|
# Groups the provided list of commits (e.g. the changes being deployed in a release) by both the feature they impact
|
214
174
|
# and the teams responsible for these features. Returns a hash with keys for each team with features modified within
|
215
175
|
# these commits and values that are a hash of features to the set of commits that impact each feature.
|
216
|
-
|
176
|
+
|
217
177
|
def group_commits(commits)
|
218
178
|
commits.each_with_object({}) do |commit, hash|
|
219
179
|
commit_features = commit.files.map do |file|
|
@@ -244,7 +204,7 @@ module FeatureMap
|
|
244
204
|
|
245
205
|
# Generates a block kit message grouping the provided commits into sections for each feature impacted by the
|
246
206
|
# cheanges.
|
247
|
-
|
207
|
+
|
248
208
|
def generate_release_notification(commits_by_feature)
|
249
209
|
Private.generate_release_notification(commits_by_feature)
|
250
210
|
end
|
@@ -253,7 +213,7 @@ module FeatureMap
|
|
253
213
|
# Namely, the set of files, and directories which are tracked for feature assignment should not change.
|
254
214
|
# The primary reason this is helpful is for clients of FeatureMap who want to test their code, and each test context
|
255
215
|
# has different feature assignments and tracked files.
|
256
|
-
|
216
|
+
|
257
217
|
def self.bust_caches!
|
258
218
|
@for_file = nil
|
259
219
|
@memoized_values = nil
|
@@ -261,7 +221,6 @@ module FeatureMap
|
|
261
221
|
Mapper.all.each(&:bust_caches!)
|
262
222
|
end
|
263
223
|
|
264
|
-
sig { returns(Configuration) }
|
265
224
|
def self.configuration
|
266
225
|
Private.configuration
|
267
226
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: feature_map
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Beyond Finance
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-03-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_ownership
|
@@ -66,20 +66,6 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
|
-
- !ruby/object:Gem::Dependency
|
70
|
-
name: sorbet-runtime
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
72
|
-
requirements:
|
73
|
-
- - "~>"
|
74
|
-
- !ruby/object:Gem::Version
|
75
|
-
version: '0.5'
|
76
|
-
type: :runtime
|
77
|
-
prerelease: false
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
79
|
-
requirements:
|
80
|
-
- - "~>"
|
81
|
-
- !ruby/object:Gem::Version
|
82
|
-
version: '0.5'
|
83
69
|
- !ruby/object:Gem::Dependency
|
84
70
|
name: uri
|
85
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -164,34 +150,6 @@ dependencies:
|
|
164
150
|
- - "~>"
|
165
151
|
- !ruby/object:Gem::Version
|
166
152
|
version: '3.0'
|
167
|
-
- !ruby/object:Gem::Dependency
|
168
|
-
name: sorbet
|
169
|
-
requirement: !ruby/object:Gem::Requirement
|
170
|
-
requirements:
|
171
|
-
- - "~>"
|
172
|
-
- !ruby/object:Gem::Version
|
173
|
-
version: '0.5'
|
174
|
-
type: :development
|
175
|
-
prerelease: false
|
176
|
-
version_requirements: !ruby/object:Gem::Requirement
|
177
|
-
requirements:
|
178
|
-
- - "~>"
|
179
|
-
- !ruby/object:Gem::Version
|
180
|
-
version: '0.5'
|
181
|
-
- !ruby/object:Gem::Dependency
|
182
|
-
name: tapioca
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
184
|
-
requirements:
|
185
|
-
- - "~>"
|
186
|
-
- !ruby/object:Gem::Version
|
187
|
-
version: '0.16'
|
188
|
-
type: :development
|
189
|
-
prerelease: false
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
191
|
-
requirements:
|
192
|
-
- - "~>"
|
193
|
-
- !ruby/object:Gem::Version
|
194
|
-
version: '0.16'
|
195
153
|
- !ruby/object:Gem::Dependency
|
196
154
|
name: webmock
|
197
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -247,8 +205,10 @@ files:
|
|
247
205
|
- lib/feature_map/private/feature_metrics_calculator.rb
|
248
206
|
- lib/feature_map/private/feature_plugins/assignment.rb
|
249
207
|
- lib/feature_map/private/glob_cache.rb
|
208
|
+
- lib/feature_map/private/health_calculator.rb
|
250
209
|
- lib/feature_map/private/lines_of_code_calculator.rb
|
251
210
|
- lib/feature_map/private/metrics_file.rb
|
211
|
+
- lib/feature_map/private/percentile_metrics_calculator.rb
|
252
212
|
- lib/feature_map/private/release_notification_builder.rb
|
253
213
|
- lib/feature_map/private/test_coverage_file.rb
|
254
214
|
- lib/feature_map/private/test_pyramid_file.rb
|