diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-08-20 21:42:06 +0300 |
commit | 6e4e1050d9dba2b7b2523fdd1768823ab85feef4 (patch) | |
tree | 78be5963ec075d80116a932011d695dd33910b4e /app/assets/javascripts/deploy_freeze | |
parent | 1ce776de4ae122aba3f349c02c17cebeaa8ecf07 (diff) |
Add latest changes from gitlab-org/gitlab@13-3-stable-ee
Diffstat (limited to 'app/assets/javascripts/deploy_freeze')
9 files changed, 432 insertions, 0 deletions
diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue new file mode 100644 index 00000000000..afc1c2cda8e --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_modal.vue @@ -0,0 +1,149 @@ +<script> +import { GlFormGroup, GlFormInput, GlModal, GlSprintf, GlLink } from '@gitlab/ui'; +import { mapActions, mapState } from 'vuex'; +import { isValidCron } from 'cron-validator'; +import { mapComputed } from '~/vuex_shared/bindings'; +import { __ } from '~/locale'; +import TimezoneDropdown from '~/vue_shared/components/timezone_dropdown.vue'; + +export default { + components: { + GlFormGroup, + GlFormInput, + GlModal, + GlSprintf, + GlLink, + TimezoneDropdown, + }, + modalOptions: { + ref: 'modal', + modalId: 'deploy-freeze-modal', + title: __('Add deploy freeze'), + actionCancel: { + text: __('Cancel'), + }, + static: true, + lazy: true, + }, + translations: { + cronPlaceholder: __('* * * * *'), + cronSyntaxInstructions: __( + 'Define a custom deploy freeze pattern with %{cronSyntaxStart}cron syntax%{cronSyntaxEnd}', + ), + }, + computed: { + ...mapState([ + 'projectId', + 'selectedTimezone', + 'timezoneData', + 'freezeStartCron', + 'freezeEndCron', + ]), + ...mapComputed([ + { key: 'freezeStartCron', updateFn: 'setFreezeStartCron' }, + { key: 'freezeEndCron', updateFn: 'setFreezeEndCron' }, + ]), + addDeployFreezeButton() { + return { + text: __('Add deploy freeze'), + attributes: [ + { variant: 'success' }, + { + disabled: + !isValidCron(this.freezeStartCron) || + !isValidCron(this.freezeEndCron) || + !this.selectedTimezone, + }, + ], + }; + }, + invalidFreezeStartCron() { + return this.invalidCronMessage(this.freezeStartCronState); + }, + freezeStartCronState() { + return Boolean(!this.freezeStartCron || isValidCron(this.freezeStartCron)); + }, + invalidFreezeEndCron() { + return this.invalidCronMessage(this.freezeEndCronState); + }, + freezeEndCronState() { + return Boolean(!this.freezeEndCron || isValidCron(this.freezeEndCron)); + }, + timezone: { + get() { + return this.selectedTimezone; + }, + set(selectedTimezone) { + this.setSelectedTimezone(selectedTimezone); + }, + }, + }, + methods: { + ...mapActions(['addFreezePeriod', 'setSelectedTimezone', 'resetModal']), + resetModalHandler() { + this.resetModal(); + }, + invalidCronMessage(validCronState) { + if (!validCronState) { + return __('This Cron pattern is invalid'); + } + return ''; + }, + }, +}; +</script> + +<template> + <gl-modal + v-bind="$options.modalOptions" + :action-primary="addDeployFreezeButton" + @primary="addFreezePeriod" + @canceled="resetModalHandler" + > + <p> + <gl-sprintf :message="$options.translations.cronSyntaxInstructions"> + <template #cronSyntax="{ content }"> + <gl-link href="https://crontab.guru/" target="_blank">{{ content }}</gl-link> + </template> + </gl-sprintf> + </p> + + <gl-form-group + :label="__('Freeze start')" + label-for="deploy-freeze-start" + :invalid-feedback="invalidFreezeStartCron" + :state="freezeStartCronState" + > + <gl-form-input + id="deploy-freeze-start" + v-model="freezeStartCron" + class="gl-font-monospace!" + data-qa-selector="deploy_freeze_start_field" + :placeholder="this.$options.translations.cronPlaceholder" + :state="freezeStartCronState" + trim + /> + </gl-form-group> + + <gl-form-group + :label="__('Freeze end')" + label-for="deploy-freeze-end" + :invalid-feedback="invalidFreezeEndCron" + :state="freezeEndCronState" + > + <gl-form-input + id="deploy-freeze-end" + v-model="freezeEndCron" + class="gl-font-monospace!" + data-qa-selector="deploy_freeze_end_field" + :placeholder="this.$options.translations.cronPlaceholder" + :state="freezeEndCronState" + trim + /> + </gl-form-group> + + <gl-form-group :label="__('Cron time zone')" label-for="cron-time-zone-dropdown"> + <timezone-dropdown v-model="timezone" :timezone-data="timezoneData" /> + </gl-form-group> + </gl-modal> +</template> diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_settings.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_settings.vue new file mode 100644 index 00000000000..fc2ed10f3ca --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_settings.vue @@ -0,0 +1,18 @@ +<script> +import DeployFreezeTable from './deploy_freeze_table.vue'; +import DeployFreezeModal from './deploy_freeze_modal.vue'; + +export default { + components: { + DeployFreezeTable, + DeployFreezeModal, + }, +}; +</script> + +<template> + <div> + <deploy-freeze-table /> + <deploy-freeze-modal /> + </div> +</template> diff --git a/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue new file mode 100644 index 00000000000..159f5ddd755 --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/components/deploy_freeze_table.vue @@ -0,0 +1,83 @@ +<script> +import { GlTable, GlButton, GlModalDirective, GlSprintf } from '@gitlab/ui'; +import { mapState, mapActions } from 'vuex'; +import { s__, __ } from '~/locale'; + +export default { + fields: [ + { + key: 'freezeStart', + label: s__('DeployFreeze|Freeze start'), + }, + { + key: 'freezeEnd', + label: s__('DeployFreeze|Freeze end'), + }, + { + key: 'cronTimezone', + label: s__('DeployFreeze|Time zone'), + }, + ], + translations: { + addDeployFreeze: __('Add deploy freeze'), + }, + components: { + GlTable, + GlButton, + GlSprintf, + }, + directives: { + GlModal: GlModalDirective, + }, + computed: { + ...mapState(['freezePeriods']), + tableIsNotEmpty() { + return this.freezePeriods?.length > 0; + }, + }, + mounted() { + this.fetchFreezePeriods(); + }, + methods: { + ...mapActions(['fetchFreezePeriods']), + }, +}; +</script> + +<template> + <div class="deploy-freeze-table"> + <gl-table + data-testid="deploy-freeze-table" + :items="freezePeriods" + :fields="$options.fields" + show-empty + stacked="lg" + > + <template #empty> + <p data-testid="empty-freeze-periods" class="gl-text-center text-plain"> + <gl-sprintf + :message=" + s__( + 'DeployFreeze|No deploy freezes exist for this project. To add one, click %{strongStart}Add deploy freeze%{strongEnd}', + ) + " + > + <template #strong="{ content }"> + <strong>{{ content }}</strong> + </template> + </gl-sprintf> + </p> + </template> + </gl-table> + <div class="gl-display-flex gl-justify-content-center"> + <gl-button + v-gl-modal.deploy-freeze-modal + data-testid="add-deploy-freeze" + category="primary" + variant="success" + > + {{ $options.translations.addDeployFreeze }} + </gl-button> + </div> + </div> +</template> diff --git a/app/assets/javascripts/deploy_freeze/index.js b/app/assets/javascripts/deploy_freeze/index.js new file mode 100644 index 00000000000..fd3f52b6da1 --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/index.js @@ -0,0 +1,22 @@ +import Vue from 'vue'; +import DeployFreezeSettings from './components/deploy_freeze_settings.vue'; +import createStore from './store'; + +export default () => { + const el = document.getElementById('js-deploy-freeze-table'); + + const { projectId, timezoneData } = el.dataset; + + const store = createStore({ + projectId, + timezoneData: JSON.parse(timezoneData), + }); + + return new Vue({ + el, + store, + render(createElement) { + return createElement(DeployFreezeSettings); + }, + }); +}; diff --git a/app/assets/javascripts/deploy_freeze/store/actions.js b/app/assets/javascripts/deploy_freeze/store/actions.js new file mode 100644 index 00000000000..2fbbba5a128 --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/store/actions.js @@ -0,0 +1,63 @@ +import * as types from './mutation_types'; +import Api from '~/api'; +import { deprecatedCreateFlash as createFlash } from '~/flash'; +import { __ } from '~/locale'; + +export const requestAddFreezePeriod = ({ commit }) => { + commit(types.REQUEST_ADD_FREEZE_PERIOD); +}; + +export const receiveAddFreezePeriodSuccess = ({ commit }) => { + commit(types.RECEIVE_ADD_FREEZE_PERIOD_SUCCESS); +}; + +export const receiveAddFreezePeriodError = ({ commit }, error) => { + commit(types.RECEIVE_ADD_FREEZE_PERIOD_ERROR, error); +}; + +export const addFreezePeriod = ({ state, dispatch, commit }) => { + dispatch('requestAddFreezePeriod'); + + return Api.createFreezePeriod(state.projectId, { + freeze_start: state.freezeStartCron, + freeze_end: state.freezeEndCron, + cron_timezone: state.selectedTimezoneIdentifier, + }) + .then(() => { + dispatch('receiveAddFreezePeriodSuccess'); + commit(types.RESET_MODAL); + dispatch('fetchFreezePeriods'); + }) + .catch(error => { + createFlash(__('Error: Unable to create deploy freeze')); + dispatch('receiveAddFreezePeriodError', error); + }); +}; + +export const fetchFreezePeriods = ({ commit, state }) => { + commit(types.REQUEST_FREEZE_PERIODS); + + return Api.freezePeriods(state.projectId) + .then(({ data }) => { + commit(types.RECEIVE_FREEZE_PERIODS_SUCCESS, data); + }) + .catch(() => { + createFlash(__('There was an error fetching the deploy freezes.')); + }); +}; + +export const setSelectedTimezone = ({ commit }, timezone) => { + commit(types.SET_SELECTED_TIMEZONE, timezone); +}; + +export const setFreezeStartCron = ({ commit }, { freezeStartCron }) => { + commit(types.SET_FREEZE_START_CRON, freezeStartCron); +}; + +export const setFreezeEndCron = ({ commit }, { freezeEndCron }) => { + commit(types.SET_FREEZE_END_CRON, freezeEndCron); +}; + +export const resetModal = ({ commit }) => { + commit(types.RESET_MODAL); +}; diff --git a/app/assets/javascripts/deploy_freeze/store/index.js b/app/assets/javascripts/deploy_freeze/store/index.js new file mode 100644 index 00000000000..ca7ea8c783c --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/store/index.js @@ -0,0 +1,14 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; +import * as actions from './actions'; +import mutations from './mutations'; +import createState from './state'; + +Vue.use(Vuex); + +export default initialState => + new Vuex.Store({ + actions, + mutations, + state: createState(initialState), + }); diff --git a/app/assets/javascripts/deploy_freeze/store/mutation_types.js b/app/assets/javascripts/deploy_freeze/store/mutation_types.js new file mode 100644 index 00000000000..47a4874a5cf --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/store/mutation_types.js @@ -0,0 +1,12 @@ +export const REQUEST_FREEZE_PERIODS = 'REQUEST_FREEZE_PERIODS'; +export const RECEIVE_FREEZE_PERIODS_SUCCESS = 'RECEIVE_FREEZE_PERIODS_SUCCESS'; + +export const REQUEST_ADD_FREEZE_PERIOD = 'REQUEST_ADD_FREEZE_PERIOD'; +export const RECEIVE_ADD_FREEZE_PERIOD_SUCCESS = 'RECEIVE_ADD_FREEZE_PERIOD_SUCCESS'; +export const RECEIVE_ADD_FREEZE_PERIOD_ERROR = 'RECEIVE_ADD_FREEZE_PERIOD_ERROR'; + +export const SET_SELECTED_TIMEZONE = 'SET_SELECTED_TIMEZONE'; +export const SET_FREEZE_START_CRON = 'SET_FREEZE_START_CRON'; +export const SET_FREEZE_END_CRON = 'SET_FREEZE_END_CRON'; + +export const RESET_MODAL = 'RESET_MODAL'; diff --git a/app/assets/javascripts/deploy_freeze/store/mutations.js b/app/assets/javascripts/deploy_freeze/store/mutations.js new file mode 100644 index 00000000000..89ce1dc5428 --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/store/mutations.js @@ -0,0 +1,54 @@ +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import * as types from './mutation_types'; + +const formatTimezoneName = (freezePeriod, timezoneList) => + convertObjectPropsToCamelCase({ + ...freezePeriod, + cron_timezone: timezoneList.find(tz => tz.identifier === freezePeriod.cron_timezone)?.name, + }); + +export default { + [types.REQUEST_FREEZE_PERIODS](state) { + state.isLoading = true; + }, + + [types.RECEIVE_FREEZE_PERIODS_SUCCESS](state, freezePeriods) { + state.isLoading = false; + state.freezePeriods = freezePeriods.map(freezePeriod => + formatTimezoneName(freezePeriod, state.timezoneData), + ); + }, + + [types.REQUEST_ADD_FREEZE_PERIOD](state) { + state.isLoading = true; + }, + + [types.RECEIVE_ADD_FREEZE_PERIOD_SUCCESS](state) { + state.isLoading = false; + }, + + [types.RECEIVE_ADD_FREEZE_PERIOD_ERROR](state, error) { + state.isLoading = false; + state.error = error; + }, + + [types.SET_SELECTED_TIMEZONE](state, timezone) { + state.selectedTimezone = timezone.formattedTimezone; + state.selectedTimezoneIdentifier = timezone.identifier; + }, + + [types.SET_FREEZE_START_CRON](state, freezeStartCron) { + state.freezeStartCron = freezeStartCron; + }, + + [types.SET_FREEZE_END_CRON](state, freezeEndCron) { + state.freezeEndCron = freezeEndCron; + }, + + [types.RESET_MODAL](state) { + state.freezeStartCron = ''; + state.freezeEndCron = ''; + state.selectedTimezone = ''; + state.selectedTimezoneIdentifier = ''; + }, +}; diff --git a/app/assets/javascripts/deploy_freeze/store/state.js b/app/assets/javascripts/deploy_freeze/store/state.js new file mode 100644 index 00000000000..4cc38c097b6 --- /dev/null +++ b/app/assets/javascripts/deploy_freeze/store/state.js @@ -0,0 +1,17 @@ +export default ({ + projectId, + freezePeriods = [], + timezoneData = [], + selectedTimezone = '', + selectedTimezoneIdentifier = '', + freezeStartCron = '', + freezeEndCron = '', +}) => ({ + projectId, + freezePeriods, + timezoneData, + selectedTimezone, + selectedTimezoneIdentifier, + freezeStartCron, + freezeEndCron, +}); |