Skip to content

Commit 9480e60

Browse files
feat: Support tags for tasks and task just run (#599)
## 📝 Description #### `front` - Small improvement: search dropdown on `run_now` page if parameter has more than 10 possible values - Support for tags on schedulers/tasks - protobuf refresh - DB stub update fixes flaky browser tests in promotions: - [test when deployment targets are enabled when target has deployment target that blocks promotion](https://semaphore.semaphoreci.com/projects/semaphore/flaky_tests/028249c9-90ad-38c3-9fa3-55ddf43195c1) - [test when deployment targets are enabled when target has deployment target that allows promotion](https://semaphore.semaphoreci.com/projects/semaphore/flaky_tests/3c1225ae-7037-3a06-be84-47fe841887fc) #### `periodic_scheduler` - Adds support for scheduling periodic workflows on Git tags in addition to branches - Introduces a new git_reference utility module to handle both branch and tag references - Updates the periodic scheduler to accept and process tag-based schedules - **gRPC/Protocol buffer updates:** Regenerated protocol buffer definitions to rename `branch` field into `reference`, will still work if caller service uses `branch` field name ([since type and order in api is preserved](https://stackoverflow.com/questions/45431685/protocol-buffer-does-changing-field-name-break-the-message)) #### `projecthub` - Use persist instead of apply for periodic schedulers and tasks - refresh proto: update field name to reference instead of branch - support in `projecthub-rest-api` for reference object (if present, if not fallback to branch field) #### `public-api` - Added tag support to task/periodic scheduler endpoints: Create, update, replace, and trigger operations now accept an optional tags parameter - Extended internal gRPC clients: Updated request/response formatters to handle tags when communicating with the periodic scheduler service - Updated OpenAPI schemas: Added tag field definitions to task specifications and trigger specifications ## ✅ Checklist - [x] I have tested this change - [ ] This change requires documentation update
1 parent af02802 commit 9480e60

File tree

108 files changed

+3457
-1220
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

108 files changed

+3457
-1220
lines changed

front/assets/css/app-semaphore.css

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,133 @@ input[type="radio"][disabled] {
135135
opacity: 1;
136136
color: #96A0A6;
137137
}
138+
/* TomSelect styling to match form-control exactly */
139+
.ts-control {
140+
-moz-appearance: none;
141+
-webkit-appearance: none;
142+
outline: none;
143+
font-size: 16px;
144+
font-size: 1rem;
145+
line-height: 1.5;
146+
padding: 4px 10px;
147+
padding-right: 24px;
148+
border: 0;
149+
border-radius: 6px;
150+
color: #202b30;
151+
background-color: #fff;
152+
font-family: "Fakt Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
153+
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
154+
transition: background 0.1s ease, border-color 0.1s ease;
155+
position: relative;
156+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2), inset 0 -1px 1px 0 #e5e8ea;
157+
background-position: right 8px center;
158+
background-repeat: no-repeat;
159+
background-image: url('data:image/svg+xml;utf8,<svg width="6px" height="11px" viewBox="0 0 6 11" xmlns="http://www.w3.org/2000/svg"><path d="M3 0l3 4H0l3-4zm0 11L0 7h6l-3 4z" fill="%233B4148" fill-rule="evenodd"/></svg>');
160+
}
161+
162+
.ts-control:focus,
163+
.ts-control.focus {
164+
outline: none;
165+
box-shadow: 0 0 0 2px #00359f !important;
166+
z-index: 4;
167+
}
168+
169+
.ts-control .ts-placeholder {
170+
opacity: 1;
171+
color: #96a0a6;
172+
}
173+
174+
/* Match disabled state */
175+
.ts-control.disabled,
176+
.ts-control[disabled] {
177+
color: #96a0a6;
178+
background-color: #f7fafb;
179+
cursor: not-allowed;
180+
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
181+
}
182+
183+
/* Hide TomSelect's default dropdown arrow since we're using the background image */
184+
.ts-control .ts-dropdown-trigger {
185+
display: none;
186+
}
187+
188+
/* Ensure TomSelect dropdown matches form styling */
189+
.ts-dropdown {
190+
border-radius: 6px;
191+
border: 0;
192+
box-shadow: rgba(0, 0, 0, 0.35) 0px 6px 30px 3px,
193+
rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;
194+
font-family: "Fakt Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
195+
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
196+
}
197+
198+
/* Style TomSelect options to match */
199+
.ts-dropdown .ts-option {
200+
padding: 4px 10px;
201+
font-size: 16px;
202+
font-size: 1rem;
203+
}
204+
205+
.ts-dropdown .ts-option.selected {
206+
background-color: #00359f;
207+
color: white;
208+
}
209+
210+
.ts-dropdown .ts-option.active {
211+
background-color: #cedcff;
212+
color: #00359f;
213+
}
214+
215+
/* Ensure single value display matches */
216+
.ts-control.single .ts-control-input {
217+
padding: 0;
218+
margin: 0;
219+
background: none;
220+
border: none;
221+
box-shadow: none;
222+
font-size: 16px;
223+
font-size: 1rem;
224+
color: #202b30;
225+
font-family: "Fakt Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
226+
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
227+
}
228+
229+
/* Ensure input in editing mode maintains proper styling */
230+
.ts-control.single .ts-control-input input {
231+
border: 1px solid #E5E8EA !important;
232+
outline: none !important;
233+
box-shadow: none !important;
234+
background: none !important;
235+
font-size: 16px;
236+
font-size: 1rem;
237+
color: #202B30;
238+
font-family: 'Fakt Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
239+
}
240+
241+
/* When TomSelect wrapper is in active states, ensure the .ts-control maintains its border */
242+
.ts-wrapper.dropdown-active .ts-control,
243+
.ts-wrapper.input-active .ts-control,
244+
.ts-wrapper.focus .ts-control {
245+
box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), inset 0 -1px 1px 0 #E5E8EA !important;
246+
border: 0 !important;
247+
background-color: #FFF !important;
248+
border-radius: 6px !important;
249+
}
250+
251+
/* Override focus state when dropdown is active to maintain blue focus border */
252+
.ts-wrapper.focus .ts-control {
253+
box-shadow: 0 0 0 2px #00359f !important;
254+
}
255+
256+
/* Ensure TomSelect control always maintains form-control styling in all states */
257+
.ts-control,
258+
.ts-wrapper.dropdown-active .ts-control,
259+
.ts-wrapper.input-active .ts-control {
260+
box-shadow: 0 0 0 1px rgba(0, 0, 0, .2), inset 0 -1px 1px 0 #E5E8EA !important;
261+
background-color: #FFF !important;
262+
border: 0 !important;
263+
border-radius: 6px !important;
264+
}
138265
textarea {
139266
display: block;
140267
min-height: 50px;

front/assets/js/tasks/components/target.js

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
export default {
22
init(params) {
3-
return new TargetComponent(params.branch, params.pipeline_file)
3+
return new TargetComponent(params.branch || params.reference_name, params.pipeline_file, params.reference_type || 'branch')
44
}
55
}
66

77
import { Validator, Rule } from './validation'
88

99
class TargetComponent {
10-
constructor(branch, pipelineFile) {
10+
constructor(referenceName, pipelineFile, referenceType = 'branch') {
11+
this.currentReferenceType = referenceType
12+
1113
this.validators = {
12-
branch: new Validator('branch', branch, [
14+
branch: new Validator('branch', referenceName, [
1315
new Rule((n) => n.length < 1, 'cannot be empty')
1416
]),
1517
pipelineFile: new Validator('pipelineFile', pipelineFile, [
@@ -19,6 +21,10 @@ class TargetComponent {
1921

2022
this.handleFieldChange('branch')
2123
this.handleFieldChange('pipelineFile')
24+
this.handleReferenceTypeChange()
25+
this.updateReferenceLabel()
26+
this.updatePlaceholder()
27+
this.updateReferenceTypeText()
2228
}
2329

2430
isValid() {
@@ -35,10 +41,75 @@ class TargetComponent {
3541
}
3642

3743
handleFieldChange(fieldName) {
38-
document
39-
.querySelector(`[data-validation-input="${fieldName}"]`)
40-
.addEventListener('input', (event) => {
44+
const element = document.querySelector(`[data-validation-input="${fieldName}"]`)
45+
if (element) {
46+
element.addEventListener('input', (event) => {
4147
this.changeFieldValue(fieldName, event.target.value)
4248
})
49+
}
50+
}
51+
52+
handleReferenceTypeChange() {
53+
const referenceTypeInputs = document.querySelectorAll('[data-validation-input="referenceType"]')
54+
referenceTypeInputs.forEach(input => {
55+
input.addEventListener('change', (event) => {
56+
this.changeReferenceType(event.target.value)
57+
})
58+
})
59+
}
60+
61+
changeReferenceType(referenceType) {
62+
this.currentReferenceType = referenceType
63+
this.updateReferenceLabel()
64+
this.updatePlaceholder()
65+
this.updateReferenceTypeText()
66+
}
67+
68+
updateReferenceLabel() {
69+
const labelElement = document.querySelector('[data-reference-label]')
70+
if (labelElement) {
71+
switch (this.currentReferenceType) {
72+
case 'tag':
73+
labelElement.textContent = 'Tag'
74+
break
75+
case 'pr':
76+
labelElement.textContent = 'Pull Request'
77+
break
78+
default:
79+
labelElement.textContent = 'Branch'
80+
}
81+
}
82+
}
83+
84+
updateReferenceTypeText() {
85+
const textElement = document.querySelector('[data-reference-type-text]')
86+
if (textElement) {
87+
switch (this.currentReferenceType) {
88+
case 'tag':
89+
textElement.textContent = 'tag'
90+
break
91+
case 'pr':
92+
textElement.textContent = 'pull request'
93+
break
94+
default:
95+
textElement.textContent = 'branch'
96+
}
97+
}
98+
}
99+
100+
updatePlaceholder() {
101+
const inputElement = document.querySelector('[data-validation-input="branch"]')
102+
if (inputElement) {
103+
switch (this.currentReferenceType) {
104+
case 'tag':
105+
inputElement.placeholder = 'e.g. v1.0.0'
106+
break
107+
case 'pr':
108+
inputElement.placeholder = 'e.g. 123'
109+
break
110+
default:
111+
inputElement.placeholder = 'e.g. master'
112+
}
113+
}
43114
}
44115
}

front/assets/js/tasks/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ export class Tasks {
5656

5757
static run() {
5858
return JustRunForm.init({
59-
branch: window.InjectedDataByBackend.Tasks.Branch,
59+
referenceType: window.InjectedDataByBackend.Tasks.ReferenceType,
60+
referenceName: window.InjectedDataByBackend.Tasks.ReferenceName,
6061
pipelineFile: window.InjectedDataByBackend.Tasks.PipelineFile,
6162
parameters: window.InjectedDataByBackend.Tasks.Parameters,
6263
})

front/assets/js/tasks/just_run_form.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11

22
import { Validator, Rule } from "./components/validation"
3+
import { TargetParams } from '../workflow_view/target_params'
34

45
export default class JustRunForm {
56
static init(params) {
67
return new JustRunForm(params)
78
}
89

910
constructor(params) {
10-
this.branchValidator = new Validator('branch', params.branch, [
11+
const referenceName = params.referenceName || 'Enter a branch or tag name…';
12+
const referenceType = params.referenceType || 'branch';
13+
14+
this.referenceNameValidator = new Validator('referenceName', referenceName, [
1115
new Rule((v) => v.length < 1, 'cannot be empty')
1216
])
1317
this.pipelineFileValidator = new Validator('pipelineFile', params.pipelineFile, [
@@ -17,30 +21,55 @@ export default class JustRunForm {
1721
new Rule((v) => parameter.required && (!v || v.length < 1), 'cannot be empty')
1822
])]))
1923

20-
this.handleBranchChange()
24+
this.currentReferenceType = referenceType
25+
this.handleReferenceTypeChange()
26+
this.handleReferenceNameChange()
2127
this.handlePipelineFileChange()
2228
this.handleParameterChanges()
2329
this.handleSubmitButton()
30+
this.updateReferenceLabel()
31+
this.initializeParameterSelects()
2432
}
2533

2634
renderAll() {
27-
this.branchValidator.render()
35+
this.referenceNameValidator.render()
2836
this.pipelineFileValidator.render()
2937
this.parameterValidators.forEach(
3038
pV => pV.render()
3139
)
3240
}
3341

34-
handleBranchChange() {
35-
const inputField = document.querySelector('[data-validation-input="branch"]')
42+
handleReferenceTypeChange() {
43+
const referenceTypeInputs = document.querySelectorAll('[data-validation-input="referenceType"]')
44+
referenceTypeInputs.forEach(input => {
45+
input.addEventListener('change', (event) => {
46+
this.changeReferenceType(event.target.value)
47+
})
48+
})
49+
}
50+
51+
changeReferenceType(referenceType) {
52+
this.currentReferenceType = referenceType
53+
this.updateReferenceLabel()
54+
}
55+
56+
updateReferenceLabel() {
57+
const labelElement = document.querySelector('[data-reference-label]')
58+
if (labelElement) {
59+
labelElement.textContent = this.currentReferenceType === 'tag' ? 'Tag' : 'Branch'
60+
}
61+
}
62+
63+
handleReferenceNameChange() {
64+
const inputField = document.querySelector('[data-validation-input="referenceName"]')
3665
if (inputField) {
37-
inputField.addEventListener('input', (event) => { this.changeBranchValue(event.target.value) })
66+
inputField.addEventListener('input', (event) => { this.changeReferenceNameValue(event.target.value) })
3867
}
3968
}
4069

41-
changeBranchValue(branchValue) {
42-
this.branchValidator.setValue(branchValue)
43-
this.branchValidator.render()
70+
changeReferenceNameValue(referenceNameValue) {
71+
this.referenceNameValidator.setValue(referenceNameValue)
72+
this.referenceNameValidator.render()
4473
}
4574

4675
handlePipelineFileChange() {
@@ -75,7 +104,7 @@ export default class JustRunForm {
75104
validateForm() {
76105
const parameterValidators = Array.from(this.parameterValidators.values())
77106

78-
return this.branchValidator.isValid() &&
107+
return this.referenceNameValidator.isValid() &&
79108
this.pipelineFileValidator.isValid() &&
80109
parameterValidators.every(parameterValidator => parameterValidator.isValid())
81110
}
@@ -84,12 +113,16 @@ export default class JustRunForm {
84113
const submitButton = document.querySelector('[data-action="submit-form"]')
85114
if (!submitButton) { return; }
86115

87-
submitButton.addEventListener('click', (event) => {
116+
submitButton.addEventListener('click', () => {
88117
if (this.validateForm()) {
89118
document.forms[0].submit()
90119
} else {
91120
this.renderAll()
92121
}
93122
})
94123
}
124+
125+
initializeParameterSelects() {
126+
TargetParams.init('[data-parameter-select]')
127+
}
95128
}

front/assets/js/workflow_view/switch.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export var Switch = {
3333
$("body").on("click", "[promote-button]", function(event) {
3434
Pollman.stop();
3535

36-
TargetParams.init();
36+
TargetParams.init('[data-promotion-param-name]');
3737
let button = $(event.currentTarget);
3838
let switchId = Switch.parentSwitch(button);
3939
let promotionTarget = Switch.parentPromotionTarget(button);

front/assets/js/workflow_view/target_params.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import TomSelect from 'tom-select'
22

33
export var TargetParams = {
4-
init: function () {
5-
document.querySelectorAll('[data-promotion-param-name]').forEach((element) => {
4+
init: function (selector = null) {
5+
document.querySelectorAll(selector).forEach((element) => {
66
if (!element.tomselect) {
77
new TomSelect(element, {
88
hidePlaceholder: true,
9-
plugins: ['no_backspace_delete', 'dropdown_input'],
9+
plugins: ['no_backspace_delete'],
1010
})
1111
}
1212
})

0 commit comments

Comments
 (0)