summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/pages/projects/pipeline_schedules/shared/components')
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue160
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue67
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js52
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js66
4 files changed, 345 insertions, 0 deletions
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
new file mode 100644
index 00000000000..2d18fa2044b
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -0,0 +1,160 @@
+<script>
+ import _ from 'underscore';
+
+ export default {
+ props: {
+ initialCronInterval: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ data() {
+ return {
+ inputNameAttribute: 'schedule[cron]',
+ 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);
+ },
+ },
+ watch: {
+ cronInterval() {
+ // updates field validation state when model changes, as
+ // glFieldError only updates on input.
+ this.$nextTick(() => {
+ gl.pipelineScheduleFieldErrors.updateFormValidityState();
+ });
+ },
+ },
+ created() {
+ if (this.intervalIsPreset) {
+ this.enableCustomInput = false;
+ }
+ },
+ 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} `;
+ }
+ },
+ },
+ };
+</script>
+
+<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">
+ {{ s__('PipelineSheduleIntervalPattern|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>
+</template>
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
new file mode 100644
index 00000000000..77508e62cef
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/pipeline_schedules_callout.vue
@@ -0,0 +1,67 @@
+<script>
+ import Vue from 'vue';
+ import Cookies from 'js-cookie';
+ import Translate from '../../../../../vue_shared/translate';
+ import illustrationSvg from '../icons/intro_illustration.svg';
+
+ Vue.use(Translate);
+
+ const cookieKey = 'pipeline_schedules_callout_dismissed';
+
+ export default {
+ name: 'PipelineSchedulesCallout',
+ data() {
+ return {
+ docsUrl: document.getElementById('pipeline-schedules-callout').dataset.docsUrl,
+ calloutDismissed: Cookies.get(cookieKey) === 'true',
+ };
+ },
+ created() {
+ this.illustrationSvg = illustrationSvg;
+ },
+ methods: {
+ dismissCallout() {
+ this.calloutDismissed = true;
+ Cookies.set(cookieKey, this.calloutDismissed, { expires: 365 });
+ },
+ },
+ };
+</script>
+<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
+ aria-hidden="true"
+ 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"
+ >
+ {{ s__('Learn more in the|pipeline schedules documentation') }}</a>.
+ <!-- oneline to prevent extra space before period -->
+ </p>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/target_branch_dropdown.js
new file mode 100644
index 00000000000..0c3926d76b5
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/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/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/timezone_dropdown.js
new file mode 100644
index 00000000000..95ed9c7dc21
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/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();
+ }
+}