diff options
Diffstat (limited to 'app/assets/javascripts/pipeline_schedules/components')
4 files changed, 311 insertions, 0 deletions
diff --git a/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js new file mode 100644 index 00000000000..4d623763ca7 --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/interval_pattern_input.js @@ -0,0 +1,145 @@ +import Vue from 'vue'; + +const inputNameAttribute = 'schedule[cron]'; + +export default { + props: { + initialCronInterval: { + type: String, + required: false, + default: '', + }, + }, + data() { + return { + inputNameAttribute, + cronInterval: this.initialCronInterval, + cronIntervalPresets: { + everyDay: '0 4 * * *', + everyWeek: '0 4 * * 0', + everyMonth: '0 4 1 * *', + }, + cronSyntaxUrl: 'https://en.wikipedia.org/wiki/Cron', + customInputEnabled: false, + }; + }, + computed: { + intervalIsPreset() { + return _.contains(this.cronIntervalPresets, this.cronInterval); + }, + // The text input is editable when there's a custom interval, or when it's + // a preset interval and the user clicks the 'custom' radio button + isEditable() { + return !!(this.customInputEnabled || !this.intervalIsPreset); + }, + }, + methods: { + toggleCustomInput(shouldEnable) { + this.customInputEnabled = shouldEnable; + + if (shouldEnable) { + // We need to change the value so other radios don't remain selected + // because the model (cronInterval) hasn't changed. The server trims it. + this.cronInterval = `${this.cronInterval} `; + } + }, + }, + created() { + if (this.intervalIsPreset) { + this.enableCustomInput = false; + } + }, + watch: { + cronInterval() { + // updates field validation state when model changes, as + // glFieldError only updates on input. + Vue.nextTick(() => { + gl.pipelineScheduleFieldErrors.updateFormValidityState(); + }); + }, + }, + template: ` + <div class="interval-pattern-form-group"> + <div class="cron-preset-radio-input"> + <input + id="custom" + class="label-light" + type="radio" + :name="inputNameAttribute" + :value="cronInterval" + :checked="isEditable" + @click="toggleCustomInput(true)" + /> + + <label for="custom"> + Custom + </label> + + <span class="cron-syntax-link-wrap"> + (<a :href="cronSyntaxUrl" target="_blank">Cron syntax</a>) + </span> + </div> + + <div class="cron-preset-radio-input"> + <input + id="every-day" + class="label-light" + type="radio" + v-model="cronInterval" + :name="inputNameAttribute" + :value="cronIntervalPresets.everyDay" + @click="toggleCustomInput(false)" + /> + + <label class="label-light" for="every-day"> + Every day (at 4:00am) + </label> + </div> + + <div class="cron-preset-radio-input"> + <input + id="every-week" + class="label-light" + type="radio" + v-model="cronInterval" + :name="inputNameAttribute" + :value="cronIntervalPresets.everyWeek" + @click="toggleCustomInput(false)" + /> + + <label class="label-light" for="every-week"> + Every week (Sundays at 4:00am) + </label> + </div> + + <div class="cron-preset-radio-input"> + <input + id="every-month" + class="label-light" + type="radio" + v-model="cronInterval" + :name="inputNameAttribute" + :value="cronIntervalPresets.everyMonth" + @click="toggleCustomInput(false)" + /> + + <label class="label-light" for="every-month"> + Every month (on the 1st at 4:00am) + </label> + </div> + + <div class="cron-interval-input-wrapper"> + <input + id="schedule_cron" + class="form-control inline cron-interval-input" + type="text" + placeholder="Define a custom pattern with cron syntax" + required="true" + v-model="cronInterval" + :name="inputNameAttribute" + :disabled="!isEditable" + /> + </div> + </div> + `, +}; diff --git a/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js new file mode 100644 index 00000000000..5109b110b31 --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/pipeline_schedules_callout.js @@ -0,0 +1,48 @@ +import Cookies from 'js-cookie'; +import illustrationSvg from '../icons/intro_illustration.svg'; + +const cookieKey = 'pipeline_schedules_callout_dismissed'; + +export default { + name: 'PipelineSchedulesCallout', + data() { + return { + docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl, + illustrationSvg, + calloutDismissed: Cookies.get(cookieKey) === 'true', + }; + }, + methods: { + dismissCallout() { + this.calloutDismissed = true; + Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 }); + }, + }, + template: ` + <div v-if="!calloutDismissed" class="pipeline-schedules-user-callout user-callout"> + <div class="bordered-box landing content-block"> + <button + id="dismiss-callout-btn" + class="btn btn-default close" + @click="dismissCallout"> + <i class="fa fa-times"></i> + </button> + <div class="svg-container" v-html="illustrationSvg"></div> + <div class="user-callout-copy"> + <h4>Scheduling Pipelines</h4> + <p> + The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. + Those scheduled pipelines will inherit limited project access based on their associated user. + </p> + <p> Learn more in the + <a + :href="docsUrl" + target="_blank" + rel="nofollow">pipeline schedules documentation</a>. <!-- oneline to prevent extra space before period --> + </p> + </div> + </div> + </div> + `, +}; + diff --git a/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js b/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js new file mode 100644 index 00000000000..0c3926d76b5 --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/target_branch_dropdown.js @@ -0,0 +1,52 @@ +export default class TargetBranchDropdown { + constructor() { + this.$dropdown = $('.js-target-branch-dropdown'); + this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); + this.$input = $('#schedule_ref'); + this.initDefaultBranch(); + this.initDropdown(); + } + + initDropdown() { + this.$dropdown.glDropdown({ + data: this.formatBranchesList(), + filterable: true, + selectable: true, + toggleLabel: item => item.name, + search: { + fields: ['name'], + }, + clicked: cfg => this.updateInputValue(cfg), + text: item => item.name, + }); + + this.setDropdownToggle(); + } + + formatBranchesList() { + return this.$dropdown.data('data') + .map(val => ({ name: val })); + } + + setDropdownToggle() { + const initialValue = this.$input.val(); + + this.$dropdownToggle.text(initialValue); + } + + initDefaultBranch() { + const initialValue = this.$input.val(); + const defaultBranch = this.$dropdown.data('defaultBranch'); + + if (!initialValue) { + this.$input.val(defaultBranch); + } + } + + updateInputValue({ selectedObj, e }) { + e.preventDefault(); + + this.$input.val(selectedObj.name); + gl.pipelineScheduleFieldErrors.updateFormValidityState(); + } +} diff --git a/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js b/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js new file mode 100644 index 00000000000..95ed9c7dc21 --- /dev/null +++ b/app/assets/javascripts/pipeline_schedules/components/timezone_dropdown.js @@ -0,0 +1,66 @@ +/* eslint-disable class-methods-use-this */ + +const defaultTimezone = 'UTC'; + +export default class TimezoneDropdown { + constructor() { + this.$dropdown = $('.js-timezone-dropdown'); + this.$dropdownToggle = this.$dropdown.find('.dropdown-toggle-text'); + this.$input = $('#schedule_cron_timezone'); + this.timezoneData = this.$dropdown.data('data'); + this.initDefaultTimezone(); + this.initDropdown(); + } + + initDropdown() { + this.$dropdown.glDropdown({ + data: this.timezoneData, + filterable: true, + selectable: true, + toggleLabel: item => item.name, + search: { + fields: ['name'], + }, + clicked: cfg => this.updateInputValue(cfg), + text: item => this.formatTimezone(item), + }); + + this.setDropdownToggle(); + } + + formatUtcOffset(offset) { + let prefix = ''; + + if (offset > 0) { + prefix = '+'; + } else if (offset < 0) { + prefix = '-'; + } + + return `${prefix} ${Math.abs(offset / 3600)}`; + } + + formatTimezone(item) { + return `[UTC ${this.formatUtcOffset(item.offset)}] ${item.name}`; + } + + initDefaultTimezone() { + const initialValue = this.$input.val(); + + if (!initialValue) { + this.$input.val(defaultTimezone); + } + } + + setDropdownToggle() { + const initialValue = this.$input.val(); + + this.$dropdownToggle.text(initialValue); + } + + updateInputValue({ selectedObj, e }) { + e.preventDefault(); + this.$input.val(selectedObj.identifier); + gl.pipelineScheduleFieldErrors.updateFormValidityState(); + } +} |