Skip to content

Commit 59f757c

Browse files
Fix automation (#2700)
* Fix various problems found through various trials of the new automation system * Fix automation rules Change to ensure the following: - Save and exit: Only forced changes are applied. Non-forced proposals are discarded — the user never accepted them. - Save and continue: All changes are applied. Forced overrides get orange highlighting + warning. Non-forced fills get yellow highlighting for user review. Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Document final automation rules Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Revise automation The automation revision was wider-reaching than I'd realized. Fundamentally, having 2 badge sets (where someone might never see the other set) made it vitally important to ensure that only controlled sets were changes, and that all changes are shown to users. The result is much better for everyone. This documents the new rules, and tweaks the code to actually follow those rules. Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Simplify automation code Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Highlight justification changes Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Add test for _justification-only change External automations may change *only* justifications. Test to ensure we highlight criteria in those cases too. Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Fix typo Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Use URL to highlight save and continue automation In "Save and Continue" we re-run automation, filling in changes. However, the highlights weren't shown because of redirection. Add the info in the URL so the highlights are shown. Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Don't use hidden fields for highlighting Remove dead code. We can't use hidden fields in a redirect, and it's too confusing to have 2 mechanisms for the same thing, so just use URL query strings. Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Add license, implementation_languages, and CPE to all forms with automation Resolves issue where license and implementation_languages fields were marked for automation highlighting in baseline forms but weren't actually displayed, causing user confusion and unnecessary database updates. Changes: - Moved license, implementation_languages, and CPE fields into the shared _form_basics.html.erb partial so they appear in all badge level forms (passing, silver, gold, baseline-1, baseline-2, baseline-3) - Renamed 'Identification' subsection to 'General' with new translation key 'General_project_info' to reflect that it now contains general project information shown across all badge levels - Added automation highlighting support for all three fields (yellow background when auto-filled by Chief detectives or external tools) - Updated ALWAYS_AUTOMATABLE to include cpe field - Updated PROJECT_BASE_FIELDS to include implementation_languages and cpe so they're loaded for all badge levels - Removed duplicate fields from _form_0.html.erb (passing form) Benefits: - Automation highlighting now works correctly for baseline forms - No more hidden field updates - users see all fields that can be automated - More consistent forms across all badge levels - CPE field can now be automated by both built-in and external automation - Collects useful project information (license, languages, CPE) for all badge levels, enabling better project analysis All tests pass (188 controller tests, 69 integration tests). Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Add general_comments to General subsection in all forms Moves general_comments field from being only in the passing form to the shared _form_basics.html.erb partial, making it available in all badge level forms with automation highlighting support. Changes: - Added general_comments field to _form_basics.html.erb after CPE field - Added general_comments to ALWAYS_AUTOMATABLE constant - Added general_comments to PROJECT_BASE_FIELDS (loaded for all levels) - Removed general_comments from _form_0.html.erb (now in shared partial) - Removed LEVELS_WITH_EXTRA_METADATA constant (no longer needed) - Removed conditional logic for extra metadata fields Benefits: - Consistent forms across all badge levels (passing, silver, gold, baseline-1/2/3) - General comments can now be automated and highlighted when auto-filled - Removes inconsistency between passing and baseline forms - Simplifies controller code by eliminating special case logic All tests pass (188 controller tests, 69 integration tests). Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Add visual cues that "Save and continue" runs automation Makes it clearer to users that clicking "Save (and continue)" will run automation to fill in unknown values. Changes: - Added robot emoji (🤖) to "Save (and continue)" button text - Added tooltip on button hover: "Runs automation to fill unknown values" - Added info icon (ℹ️) next to button with same tooltip - Added new translation key: save_and_continue_tooltip Implementation: - Updated all "Save (and continue)" buttons across all forms: * _form_0.html.erb (passing) - 6 buttons * _form_1.html.erb (silver) - 7 buttons * _form_2.html.erb (gold) - 6 buttons * _form_baseline.html.erb (baseline-1/2/3) - 2 buttons - Used internationalization (I18n.t) for all user-facing text Benefits: - Users immediately see the robot icon indicating automation - Hover reveals detailed explanation of what happens - Info icon provides additional visual cue for discoverability - Consistent with existing automation highlighting (robot emoji on fields) - All text is internationalized for translation All tests pass (188 controller tests, 69 integration tests). Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Remove info icons from Save and continue buttons The info icons were not adding value beyond the robot emoji and tooltip. Keeping just the robot emoji in the button text and the tooltip on hover provides sufficient visual cues without cluttering the interface. Changes: - Removed glyphicon-info-sign icons from all Save and continue buttons - Kept robot emoji (🤖) in button text - Kept tooltip on button hover All tests pass (188 controller tests). Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> * Move robot emoji to follow Save and continue button text The primary action is "Save (and continue)" so that should be read first. The robot emoji now follows the text as a visual hint that automation runs. Changes: - Changed button text from: 🤖 Save (and continue) - To: Save (and continue) 🤖 This improves readability by emphasizing the action (save) before the indicator (automation). All tests pass (188 controller tests). Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com> --------- Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com>
1 parent 904bceb commit 59f757c

File tree

12 files changed

+876
-502
lines changed

12 files changed

+876
-502
lines changed

app/controllers/projects_controller.rb

Lines changed: 170 additions & 160 deletions
Large diffs are not rendered by default.

app/lib/github_basic_detective.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,12 @@ def analyze(_evidence, current)
146146
# but if this really is the "main" repo (as claimed) then this is met.
147147
results[:osps_qa_01_01_status] = {
148148
value: CriterionStatus::MET, confidence: 3,
149-
explanation: 'Repository is publcly available on GitHub.'
149+
explanation: 'Repository is publicly available on GitHub.'
150150
}
151151
# If the main repo is on GitHub, then git will store this
152152
results[:osps_qa_01_02_status] = {
153153
value: CriterionStatus::MET, confidence: 3,
154-
explanation: 'Repository git metadata is publcly available on GitHub.'
154+
explanation: 'Repository git metadata is publicly available on GitHub.'
155155
}
156156

157157
# Get basic evidence

app/lib/test_forced_detective.rb

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,46 @@
33
# Copyright the OpenSSF Best Practices badge contributors
44
# SPDX-License-Identifier: MIT
55

6-
# Test-only detective that forces an override when given specific input.
7-
# This detective is used to test override detection and warning paths
6+
# Test-only detective that produces automation results for specific URLs.
7+
# Used to test override detection, non-forced auto-fill, and warning paths
88
# in projects_controller without needing to mock Chief behavior.
99
#
10-
# Only active in test environment. Only forces override for specific test URL.
10+
# Only active in test environment. Only triggers for specific test URLs.
1111
class TestForcedDetective < Detective
1212
INPUTS = [:repo_url].freeze
1313
OUTPUTS = [:description_good_status].freeze
1414
OVERRIDABLE_OUTPUTS = [:description_good_status].freeze
1515

16-
# @return [Hash] Forced override for specific test URL, empty otherwise
16+
# Map of test URLs to their confidence levels.
17+
TEST_URLS = {
18+
'https://example.com/test/force-override' => 5, # Forced override
19+
'https://example.com/test/auto-fill' => 2 # Non-forced fill
20+
}.freeze
21+
22+
# @return [Hash] Proposed change for specific test URLs, empty otherwise
1723
def analyze(_evidence, current)
18-
# Only force override for specific test URL pattern (non-GitHub to avoid VCR)
1924
repo_url = current[:repo_url]
2025

2126
# Special URL to test Chief exception handling
2227
if repo_url == 'https://example.com/test/chief-failure'
2328
raise StandardError, 'Test chief failure for coverage'
2429
end
2530

26-
return {} unless repo_url == 'https://example.com/test/force-override'
31+
confidence = TEST_URLS[repo_url]
32+
return {} unless confidence
33+
34+
{ description_good_status: met_proposal(confidence) }
35+
end
36+
37+
private
2738

39+
# @param confidence [Integer] Confidence level for the proposal
40+
# @return [Hash] A proposal hash setting description_good_status to Met
41+
def met_proposal(confidence)
2842
{
29-
description_good_status: {
30-
value: 'Met',
31-
confidence: 5, # High confidence triggers forced override
32-
explanation: 'Test override for automated override detection coverage'
33-
}
43+
value: 'Met',
44+
confidence: confidence,
45+
explanation: "Test automation (confidence #{confidence})"
3446
}
3547
end
3648
end

app/views/projects/_form_0.html.erb

Lines changed: 19 additions & 255 deletions
Large diffs are not rendered by default.

app/views/projects/_form_1.html.erb

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
<%= bootstrap_form_for project, url: update_project_path(project, section: criteria_level) do |f| %>
2626
<%= render 'shared/error_messages', object: f.object %>
2727
<%= f.hidden_field :lock_version %>
28-
<%# Store automation state across save-and-continue (just field names) %>
29-
<%= hidden_field_tag :automated_fields, (@automated_fields || []).map { |f| f[:field] }.join(',') %>
30-
<%= hidden_field_tag :overridden_fields, (@overridden_fields || []).map { |f| f[:field] }.join(',') %>
3128

3229
<br>
3330

@@ -43,7 +40,7 @@
4340
}) %>
4441
<div class="panel-collapse collapse in">
4542
<ul class="list-group">
46-
<li class="list-group-item"><h3 id="section_identification"><%= t('headings.Identification') %></h3>
43+
<li class="list-group-item"><h3 id="section_general_project_info"><%= t('headings.General_project_info') %></h3>
4744
<%= render(partial: 'form_basics',
4845
locals:
4946
{
@@ -78,8 +75,9 @@
7875
<% unless view_only %>
7976
<div class="panel-footer text-center">
8077
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
81-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
82-
value: 'changecontrol', class:"btn btn-success btn-submit" %>
78+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
79+
value: 'changecontrol', class:"btn btn-success btn-submit",
80+
title: t('projects.edit.save_and_continue_tooltip') %>
8381
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
8482
</div>
8583
<% end %>
@@ -106,8 +104,9 @@
106104
<% unless view_only %>
107105
<div class="panel-footer text-center">
108106
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
109-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
110-
value: 'reporting', class:"btn btn-success btn-submit" %>
107+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
108+
value: 'reporting', class:"btn btn-success btn-submit",
109+
title: t('projects.edit.save_and_continue_tooltip') %>
111110
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
112111
</div>
113112
<% end %>
@@ -137,8 +136,9 @@
137136
<% unless view_only %>
138137
<div class="panel-footer text-center">
139138
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
140-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
141-
value: 'quality', class:"btn btn-success btn-submit" %>
139+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
140+
value: 'quality', class:"btn btn-success btn-submit",
141+
title: t('projects.edit.save_and_continue_tooltip') %>
142142
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
143143
</div>
144144
<% end %>
@@ -186,8 +186,9 @@
186186
<% unless view_only %>
187187
<div class="panel-footer text-center">
188188
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
189-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
190-
value: 'security', class:"btn btn-success btn-submit" %>
189+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
190+
value: 'security', class:"btn btn-success btn-submit",
191+
title: t('projects.edit.save_and_continue_tooltip') %>
191192
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
192193
</div>
193194
<% end %>
@@ -244,8 +245,9 @@
244245
<% unless view_only %>
245246
<div class="panel-footer text-center">
246247
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
247-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
248-
value: 'analysis', class:"btn btn-success btn-submit" %>
248+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
249+
value: 'analysis', class:"btn btn-success btn-submit",
250+
title: t('projects.edit.save_and_continue_tooltip') %>
249251
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
250252
</div>
251253
<% end %>
@@ -275,8 +277,9 @@
275277
<% unless view_only %>
276278
<div class="panel-footer text-center">
277279
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
278-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
279-
value: 'future', class:"btn btn-success btn-submit" %>
280+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
281+
value: 'future', class:"btn btn-success btn-submit",
282+
title: t('projects.edit.save_and_continue_tooltip') %>
280283
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
281284
</div>
282285
<% end %>
@@ -291,8 +294,9 @@
291294
user: project.user_display_name %>
292295
<% else %>
293296
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
294-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
295-
value: 'Save', class:"btn btn-success btn-submit" %>
297+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
298+
value: 'Save', class:"btn btn-success btn-submit",
299+
title: t('projects.edit.save_and_continue_tooltip') %>
296300
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
297301
<% end %>
298302
</div>

app/views/projects/_form_2.html.erb

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@
2525
<%= bootstrap_form_for project, url: update_project_path(project, section: criteria_level) do |f| %>
2626
<%= render 'shared/error_messages', object: f.object %>
2727
<%= f.hidden_field :lock_version %>
28-
<%# Store automation state across save-and-continue (just field names) %>
29-
<%= hidden_field_tag :automated_fields, (@automated_fields || []).map { |f| f[:field] }.join(',') %>
30-
<%= hidden_field_tag :overridden_fields, (@overridden_fields || []).map { |f| f[:field] }.join(',') %>
3128

3229
<br>
3330

@@ -43,7 +40,7 @@
4340
}) %>
4441
<div class="panel-collapse collapse in">
4542
<ul class="list-group">
46-
<li class="list-group-item"><h3 id="section_identification"><%= t('headings.Identification') %></h3>
43+
<li class="list-group-item"><h3 id="section_general_project_info"><%= t('headings.General_project_info') %></h3>
4744
<%= render(partial: 'form_basics',
4845
locals:
4946
{
@@ -67,8 +64,9 @@
6764
<% unless view_only %>
6865
<div class="panel-footer text-center">
6966
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
70-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
71-
value: 'changecontrol', class:"btn btn-success btn-submit" %>
67+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
68+
value: 'changecontrol', class:"btn btn-success btn-submit",
69+
title: t('projects.edit.save_and_continue_tooltip') %>
7270
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
7371
</div>
7472
<% end %>
@@ -95,8 +93,9 @@
9593
<% unless view_only %>
9694
<div class="panel-footer text-center">
9795
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
98-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
99-
value: 'reporting', class:"btn btn-success btn-submit" %>
96+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
97+
value: 'reporting', class:"btn btn-success btn-submit",
98+
title: t('projects.edit.save_and_continue_tooltip') %>
10099
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
101100
</div>
102101
<% end %>
@@ -127,8 +126,9 @@
127126
<% unless view_only %>
128127
<div class="panel-footer text-center">
129128
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
130-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
131-
value: 'security', class:"btn btn-success btn-submit" %>
129+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
130+
value: 'security', class:"btn btn-success btn-submit",
131+
title: t('projects.edit.save_and_continue_tooltip') %>
132132
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
133133
</div>
134134
<% end %>
@@ -181,8 +181,9 @@
181181
<% unless view_only %>
182182
<div class="panel-footer text-center">
183183
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
184-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
185-
value: 'analysis', class:"btn btn-success btn-submit" %>
184+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
185+
value: 'analysis', class:"btn btn-success btn-submit",
186+
title: t('projects.edit.save_and_continue_tooltip') %>
186187
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
187188
</div>
188189
<% end %>
@@ -208,8 +209,9 @@
208209
<% unless view_only %>
209210
<div class="panel-footer text-center">
210211
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
211-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
212-
value: 'future', class:"btn btn-success btn-submit" %>
212+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
213+
value: 'future', class:"btn btn-success btn-submit",
214+
title: t('projects.edit.save_and_continue_tooltip') %>
213215
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
214216
</div>
215217
<% end %>
@@ -224,8 +226,9 @@
224226
user: project.user_display_name %>
225227
<% else %>
226228
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
227-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
228-
value: 'Save', class:"btn btn-success btn-submit" %>
229+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
230+
value: 'Save', class:"btn btn-success btn-submit",
231+
title: t('projects.edit.save_and_continue_tooltip') %>
229232
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
230233
<% end %>
231234
</div>

app/views/projects/_form_baseline.html.erb

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,6 @@
3232
<%= bootstrap_form_for project, url: update_project_path(project, section: criteria_level) do |f| %>
3333
<%= render 'shared/error_messages', object: f.object %>
3434
<%= f.hidden_field :lock_version %>
35-
<%# Store automation state across save-and-continue (just field names) %>
36-
<%= hidden_field_tag :automated_fields, (@automated_fields || []).map { |f| f[:field] }.join(',') %>
37-
<%= hidden_field_tag :overridden_fields, (@overridden_fields || []).map { |f| f[:field] }.join(',') %>
3835

3936
<br>
4037

@@ -50,7 +47,7 @@
5047
}) %>
5148
<div class="panel-collapse collapse in">
5249
<ul class="list-group">
53-
<li class="list-group-item"><h3 id="section_identification"><%= t('headings.Identification') %></h3>
50+
<li class="list-group-item"><h3 id="section_general_project_info"><%= t('headings.General_project_info') %></h3>
5451
<%= render(partial: 'form_basics',
5552
locals:
5653
{
@@ -64,8 +61,9 @@
6461
<% unless view_only %>
6562
<div class="panel-footer text-center">
6663
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
67-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
68-
value: 'controls', class:"btn btn-success btn-submit" %>
64+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
65+
value: 'controls', class:"btn btn-success btn-submit",
66+
title: t('projects.edit.save_and_continue_tooltip') %>
6967
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
7068
</div>
7169
<% end %>
@@ -105,8 +103,9 @@
105103
user: project.user_display_name %>
106104
<% else %>
107105
<%= t 'projects.edit.submit_cdla_permissive_20_html' %>
108-
<%= f.button t('projects.edit.save_and_continue'), type: 'submit', name: 'continue',
109-
value: 'Save', class:"btn btn-success btn-submit" %>
106+
<%= f.button "#{t('projects.edit.save_and_continue')} #{ApplicationHelper::ROBOT_EMOJI_SAFE}".html_safe, type: 'submit', name: 'continue',
107+
value: 'Save', class:"btn btn-success btn-submit",
108+
title: t('projects.edit.save_and_continue_tooltip') %>
110109
<%= f.submit t('projects.edit.submit_and_exit'), class:"btn btn-success btn-submit" %>
111110
<% end %>
112111
</div>

0 commit comments

Comments
 (0)