diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 12:09:00 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-04 12:09:00 +0000 |
commit | 88a0824944720b6edaaef56376713541b9a02118 (patch) | |
tree | f5fcc4f9755f249779cda9a8f02902d734af6e7e /app | |
parent | 7d19df2d34a9803d9f077c16315ba919b7ae2aa2 (diff) | |
download | gitlab-ce-88a0824944720b6edaaef56376713541b9a02118.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
23 files changed, 345 insertions, 332 deletions
diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue index 3d389cf3db5..59c5586edcd 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/eks_cluster_configuration_form.vue @@ -306,9 +306,9 @@ export default { </script> <template> <form name="eks-cluster-configuration-form"> - <h2> + <h4> {{ s__('ClusterIntegration|Enter the details for your Amazon EKS Kubernetes cluster') }} - </h2> + </h4> <div class="mb-3" v-html="kubernetesIntegrationHelpText"></div> <div class="form-group"> <label class="label-bold" for="eks-cluster-name">{{ diff --git a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue index 49a5d4657af..0cfe47dafaf 100644 --- a/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue +++ b/app/assets/javascripts/create_cluster/eks_cluster/components/service_credentials_form.vue @@ -83,7 +83,7 @@ export default { </script> <template> <form name="service-credentials-form"> - <h2>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h2> + <h4>{{ s__('ClusterIntegration|Authenticate with Amazon Web Services') }}</h4> <p> {{ s__( diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index 809b3d5f57e..0ca13e897f3 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -8,7 +8,6 @@ import { polyfillSticky } from '~/lib/utils/sticky'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import Callout from '~/vue_shared/components/callout.vue'; import Icon from '~/vue_shared/components/icon.vue'; -import createStore from '../store'; import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; import ErasedBlock from './erased_block.vue'; @@ -22,7 +21,6 @@ import { isNewJobLogActive } from '../store/utils'; export default { name: 'JobPageApp', - store: createStore(), components: { CiHeader, Callout, @@ -60,27 +58,15 @@ export default { required: false, default: null, }, - endpoint: { - type: String, - required: true, - }, terminalPath: { type: String, required: false, default: null, }, - pagePath: { - type: String, - required: true, - }, projectPath: { type: String, required: true, }, - logState: { - type: String, - required: true, - }, subscriptionsMoreMinutesUrl: { type: String, required: false, @@ -161,37 +147,28 @@ export default { created() { this.throttled = _.throttle(this.toggleScrollButtons, 100); - this.setJobEndpoint(this.endpoint); - this.setTraceOptions({ - logState: this.logState, - pagePath: this.pagePath, - }); - - this.fetchJob(); - this.fetchTrace(); - window.addEventListener('resize', this.onResize); window.addEventListener('scroll', this.updateScroll); }, mounted() { this.updateSidebar(); }, - destroyed() { + beforeDestroy() { + this.stopPollingTrace(); + this.stopPolling(); window.removeEventListener('resize', this.onResize); window.removeEventListener('scroll', this.updateScroll); }, methods: { ...mapActions([ - 'setJobEndpoint', - 'setTraceOptions', - 'fetchJob', 'fetchJobsForStage', 'hideSidebar', 'showSidebar', 'toggleSidebar', - 'fetchTrace', 'scrollBottom', 'scrollTop', + 'stopPollingTrace', + 'stopPolling', 'toggleScrollButtons', 'toggleScrollAnimation', ]), @@ -223,7 +200,7 @@ export default { <div> <gl-loading-icon v-if="isLoading" - :size="2" + size="lg" class="js-job-loading qa-loading-animation prepend-top-20" /> diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js index 9c35534523e..024a13ce102 100644 --- a/app/assets/javascripts/jobs/index.js +++ b/app/assets/javascripts/jobs/index.js @@ -1,11 +1,18 @@ import Vue from 'vue'; import JobApp from './components/job_app.vue'; +import createStore from './store'; export default () => { const element = document.getElementById('js-job-vue-app'); + const store = createStore(); + + // Let's start initializing the store (i.e. fetching data) right away + store.dispatch('init', element.dataset); + return new Vue({ el: element, + store, components: { JobApp, }, diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index 41cc5a181dc..f4030939f2c 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -14,6 +14,16 @@ import { scrollUp, } from '~/lib/utils/scroll_utils'; +export const init = ({ dispatch }, { endpoint, logState, pagePath }) => { + dispatch('setJobEndpoint', endpoint); + dispatch('setTraceOptions', { + logState, + pagePath, + }); + + return Promise.all([dispatch('fetchJob'), dispatch('fetchTrace')]); +}; + export const setJobEndpoint = ({ commit }, endpoint) => commit(types.SET_JOB_ENDPOINT, endpoint); export const setTraceOptions = ({ commit }, options) => commit(types.SET_TRACE_OPTIONS, options); @@ -147,7 +157,6 @@ export const toggleScrollisInBottom = ({ commit }, toggle) => { export const requestTrace = ({ commit }) => commit(types.REQUEST_TRACE); -let traceTimeout; export const fetchTrace = ({ dispatch, state }) => axios .get(`${state.traceEndpoint}/trace.json`, { @@ -157,24 +166,32 @@ export const fetchTrace = ({ dispatch, state }) => dispatch('toggleScrollisInBottom', isScrolledToBottom()); dispatch('receiveTraceSuccess', data); - if (!data.complete) { - traceTimeout = setTimeout(() => { - dispatch('fetchTrace'); - }, 4000); - } else { + if (data.complete) { dispatch('stopPollingTrace'); + } else if (!state.traceTimeout) { + dispatch('startPollingTrace'); } }) .catch(() => dispatch('receiveTraceError')); -export const stopPollingTrace = ({ commit }) => { +export const startPollingTrace = ({ dispatch, commit }) => { + const traceTimeout = setTimeout(() => { + commit(types.SET_TRACE_TIMEOUT, 0); + dispatch('fetchTrace'); + }, 4000); + + commit(types.SET_TRACE_TIMEOUT, traceTimeout); +}; + +export const stopPollingTrace = ({ state, commit }) => { + clearTimeout(state.traceTimeout); + commit(types.SET_TRACE_TIMEOUT, 0); commit(types.STOP_POLLING_TRACE); - clearTimeout(traceTimeout); }; + export const receiveTraceSuccess = ({ commit }, log) => commit(types.RECEIVE_TRACE_SUCCESS, log); -export const receiveTraceError = ({ commit }) => { - commit(types.RECEIVE_TRACE_ERROR); - clearTimeout(traceTimeout); +export const receiveTraceError = ({ dispatch }) => { + dispatch('stopPollingTrace'); flash(__('An error occurred while fetching the job log.')); }; /** diff --git a/app/assets/javascripts/jobs/store/mutation_types.js b/app/assets/javascripts/jobs/store/mutation_types.js index 858fa3b73ab..6c4f1b5a191 100644 --- a/app/assets/javascripts/jobs/store/mutation_types.js +++ b/app/assets/javascripts/jobs/store/mutation_types.js @@ -10,7 +10,6 @@ export const DISABLE_SCROLL_BOTTOM = 'DISABLE_SCROLL_BOTTOM'; export const DISABLE_SCROLL_TOP = 'DISABLE_SCROLL_TOP'; export const ENABLE_SCROLL_BOTTOM = 'ENABLE_SCROLL_BOTTOM'; export const ENABLE_SCROLL_TOP = 'ENABLE_SCROLL_TOP'; -// TODO export const TOGGLE_SCROLL_ANIMATION = 'TOGGLE_SCROLL_ANIMATION'; export const TOGGLE_IS_SCROLL_IN_BOTTOM_BEFORE_UPDATING_TRACE = 'TOGGLE_IS_SCROLL_IN_BOTTOM'; @@ -20,6 +19,7 @@ export const RECEIVE_JOB_SUCCESS = 'RECEIVE_JOB_SUCCESS'; export const RECEIVE_JOB_ERROR = 'RECEIVE_JOB_ERROR'; export const REQUEST_TRACE = 'REQUEST_TRACE'; +export const SET_TRACE_TIMEOUT = 'SET_TRACE_TIMEOUT'; export const STOP_POLLING_TRACE = 'STOP_POLLING_TRACE'; export const RECEIVE_TRACE_SUCCESS = 'RECEIVE_TRACE_SUCCESS'; export const RECEIVE_TRACE_ERROR = 'RECEIVE_TRACE_ERROR'; diff --git a/app/assets/javascripts/jobs/store/mutations.js b/app/assets/javascripts/jobs/store/mutations.js index 77c68cac4a6..6193d8d34ab 100644 --- a/app/assets/javascripts/jobs/store/mutations.js +++ b/app/assets/javascripts/jobs/store/mutations.js @@ -53,17 +53,14 @@ export default { state.isTraceComplete = log.complete || state.isTraceComplete; }, - /** - * Will remove loading animation - */ - [types.STOP_POLLING_TRACE](state) { - state.isTraceComplete = true; + [types.SET_TRACE_TIMEOUT](state, id) { + state.traceTimeout = id; }, /** * Will remove loading animation */ - [types.RECEIVE_TRACE_ERROR](state) { + [types.STOP_POLLING_TRACE](state) { state.isTraceComplete = true; }, diff --git a/app/assets/javascripts/jobs/store/state.js b/app/assets/javascripts/jobs/store/state.js index cdc1780f3d6..5a61828ec6d 100644 --- a/app/assets/javascripts/jobs/store/state.js +++ b/app/assets/javascripts/jobs/store/state.js @@ -22,6 +22,7 @@ export default () => ({ isTraceComplete: false, traceSize: 0, isTraceSizeVisible: false, + traceTimeout: 0, // used as a query parameter to fetch the trace traceState: null, diff --git a/app/assets/javascripts/registry/settings/components/settings_form.vue b/app/assets/javascripts/registry/settings/components/settings_form.vue index ad2fdb4fd40..cab3c7fff85 100644 --- a/app/assets/javascripts/registry/settings/components/settings_form.vue +++ b/app/assets/javascripts/registry/settings/components/settings_form.vue @@ -1,16 +1,20 @@ <script> import { mapActions, mapState, mapGetters } from 'vuex'; +import { GlCard, GlButton, GlLoadingIcon } from '@gitlab/ui'; import Tracking from '~/tracking'; import { UPDATE_SETTINGS_ERROR_MESSAGE, UPDATE_SETTINGS_SUCCESS_MESSAGE, } from '../../shared/constants'; import { mapComputed } from '~/vuex_shared/bindings'; -import ExpirationPolicyForm from '../../shared/components/expiration_policy_form.vue'; +import ExpirationPolicyFields from '../../shared/components/expiration_policy_fields.vue'; export default { components: { - ExpirationPolicyForm, + GlCard, + GlButton, + GlLoadingIcon, + ExpirationPolicyFields, }, mixins: [Tracking.mixin()], labelsConfig: { @@ -22,12 +26,19 @@ export default { tracking: { label: 'docker_container_retention_and_expiration_policies', }, + formIsValid: true, }; }, computed: { ...mapState(['formOptions', 'isLoading']), ...mapGetters({ isEdited: 'getIsEdited' }), ...mapComputed([{ key: 'settings', getter: 'getSettings' }], 'updateSettings'), + isSubmitButtonDisabled() { + return !this.formIsValid || this.isLoading; + }, + isCancelButtonDisabled() { + return !this.isEdited || this.isLoading; + }, }, methods: { ...mapActions(['resetSettings', 'saveSettings']), @@ -46,12 +57,42 @@ export default { </script> <template> - <expiration-policy-form - v-model="settings" - :form-options="formOptions" - :is-loading="isLoading" - :disable-cancel-button="!isEdited" - @submit="submit" - @reset="reset" - /> + <form ref="form-element" @submit.prevent="submit" @reset.prevent="reset"> + <gl-card> + <template #header> + {{ s__('ContainerRegistry|Tag expiration policy') }} + </template> + <template #default> + <expiration-policy-fields + v-model="settings" + :form-options="formOptions" + :is-loading="isLoading" + @validated="formIsValid = true" + @invalidated="formIsValid = false" + /> + </template> + <template #footer> + <div class="d-flex justify-content-end"> + <gl-button + ref="cancel-button" + type="reset" + class="mr-2 d-block" + :disabled="isCancelButtonDisabled" + > + {{ __('Cancel') }} + </gl-button> + <gl-button + ref="save-button" + type="submit" + :disabled="isSubmitButtonDisabled" + variant="success" + class="d-flex justify-content-center align-items-center js-no-auto-disable" + > + {{ __('Save expiration policy') }} + <gl-loading-icon v-if="isLoading" class="ml-2" /> + </gl-button> + </div> + </template> + </gl-card> + </form> </template> diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue new file mode 100644 index 00000000000..84d1c5ccc6a --- /dev/null +++ b/app/assets/javascripts/registry/shared/components/expiration_policy_fields.vue @@ -0,0 +1,197 @@ +<script> +import { uniqueId } from 'lodash'; +import { GlFormGroup, GlToggle, GlFormSelect, GlFormTextarea } from '@gitlab/ui'; +import { s__, __, sprintf } from '~/locale'; +import { NAME_REGEX_LENGTH } from '../constants'; +import { mapComputedToEvent } from '../utils'; + +export default { + components: { + GlFormGroup, + GlToggle, + GlFormSelect, + GlFormTextarea, + }, + props: { + formOptions: { + type: Object, + required: false, + default: () => ({}), + }, + isLoading: { + type: Boolean, + required: false, + default: false, + }, + value: { + type: Object, + required: false, + default: () => ({}), + }, + labelCols: { + type: [Number, String], + required: false, + default: 3, + }, + labelAlign: { + type: String, + required: false, + default: 'right', + }, + }, + nameRegexPlaceholder: '.*', + selectList: [ + { + name: 'expiration-policy-interval', + label: s__('ContainerRegistry|Expiration interval:'), + model: 'older_than', + optionKey: 'olderThan', + }, + { + name: 'expiration-policy-schedule', + label: s__('ContainerRegistry|Expiration schedule:'), + model: 'cadence', + optionKey: 'cadence', + }, + { + name: 'expiration-policy-latest', + label: s__('ContainerRegistry|Number of tags to retain:'), + model: 'keep_n', + optionKey: 'keepN', + }, + ], + data() { + return { + uniqueId: uniqueId(), + }; + }, + computed: { + ...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'), + policyEnabledText() { + return this.enabled ? __('enabled') : __('disabled'); + }, + toggleDescriptionText() { + return sprintf( + s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'), + { + toggleStatus: `<strong>${this.policyEnabledText}</strong>`, + }, + false, + ); + }, + regexHelpText() { + return sprintf( + s__( + 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}', + ), + { + codeStart: '<code>', + codeEnd: '</code>', + }, + false, + ); + }, + nameRegexState() { + return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null; + }, + fieldsValidity() { + return this.nameRegexState !== false; + }, + isFormElementDisabled() { + return !this.enabled || this.isLoading; + }, + }, + watch: { + fieldsValidity: { + immediate: true, + handler(valid) { + if (valid) { + this.$emit('validated'); + } else { + this.$emit('invalidated'); + } + }, + }, + }, + methods: { + idGenerator(id) { + return `${id}_${this.uniqueId}`; + }, + updateModel(value, key) { + this[key] = value; + }, + }, +}; +</script> + +<template> + <div ref="form-elements" class="lh-2"> + <gl-form-group + :id="idGenerator('expiration-policy-toggle-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-toggle')" + :label="s__('ContainerRegistry|Expiration policy:')" + > + <div class="d-flex align-items-start"> + <gl-toggle + :id="idGenerator('expiration-policy-toggle')" + v-model="enabled" + :disabled="isLoading" + /> + <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> + </div> + </gl-form-group> + + <gl-form-group + v-for="select in $options.selectList" + :id="idGenerator(`${select.name}-group`)" + :key="select.name" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator(select.name)" + :label="select.label" + > + <gl-form-select + :id="idGenerator(select.name)" + :value="value[select.model]" + :disabled="isFormElementDisabled" + @input="updateModel($event, select.model)" + > + <option + v-for="option in formOptions[select.optionKey]" + :key="option.key" + :value="option.key" + > + {{ option.label }} + </option> + </gl-form-select> + </gl-form-group> + + <gl-form-group + :id="idGenerator('expiration-policy-name-matching-group')" + :label-cols="labelCols" + :label-align="labelAlign" + :label-for="idGenerator('expiration-policy-name-matching')" + :label=" + s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:') + " + :state="nameRegexState" + :invalid-feedback=" + s__('ContainerRegistry|The value of this input should be less than 255 characters') + " + > + <gl-form-textarea + :id="idGenerator('expiration-policy-name-matching')" + v-model="name_regex" + :placeholder="$options.nameRegexPlaceholder" + :state="nameRegexState" + :disabled="isFormElementDisabled" + trim + /> + <template #description> + <span ref="regex-description" v-html="regexHelpText"></span> + </template> + </gl-form-group> + </div> +</template> diff --git a/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue b/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue deleted file mode 100644 index c044add3759..00000000000 --- a/app/assets/javascripts/registry/shared/components/expiration_policy_form.vue +++ /dev/null @@ -1,247 +0,0 @@ -<script> -import { uniqueId } from 'lodash'; -import { - GlFormGroup, - GlToggle, - GlFormSelect, - GlFormTextarea, - GlButton, - GlCard, - GlLoadingIcon, -} from '@gitlab/ui'; -import { s__, __, sprintf } from '~/locale'; -import { NAME_REGEX_LENGTH } from '../constants'; -import { mapComputedToEvent } from '../utils'; - -export default { - components: { - GlFormGroup, - GlToggle, - GlFormSelect, - GlFormTextarea, - GlButton, - GlCard, - GlLoadingIcon, - }, - props: { - formOptions: { - type: Object, - required: false, - default: () => ({}), - }, - isLoading: { - type: Boolean, - required: false, - default: false, - }, - value: { - type: Object, - required: false, - default: () => ({}), - }, - labelCols: { - type: [Number, String], - required: false, - default: 3, - }, - labelAlign: { - type: String, - required: false, - default: 'right', - }, - disableCancelButton: { - type: Boolean, - required: false, - default: false, - }, - }, - nameRegexPlaceholder: '.*', - data() { - return { - uniqueId: uniqueId(), - }; - }, - computed: { - ...mapComputedToEvent(['enabled', 'cadence', 'older_than', 'keep_n', 'name_regex'], 'value'), - policyEnabledText() { - return this.enabled ? __('enabled') : __('disabled'); - }, - toggleDescriptionText() { - return sprintf( - s__('ContainerRegistry|Docker tag expiration policy is %{toggleStatus}'), - { - toggleStatus: `<strong>${this.policyEnabledText}</strong>`, - }, - false, - ); - }, - regexHelpText() { - return sprintf( - s__( - 'ContainerRegistry|Wildcards such as %{codeStart}*-stable%{codeEnd} or %{codeStart}production/*%{codeEnd} are supported. To select all tags, use %{codeStart}.*%{codeEnd}', - ), - { - codeStart: '<code>', - codeEnd: '</code>', - }, - false, - ); - }, - nameRegexState() { - return this.name_regex ? this.name_regex.length <= NAME_REGEX_LENGTH : null; - }, - formIsInvalid() { - return this.nameRegexState === false; - }, - isFormElementDisabled() { - return !this.enabled || this.isLoading; - }, - isSubmitButtonDisabled() { - return this.formIsInvalid || this.isLoading; - }, - isCancelButtonDisabled() { - return this.disableCancelButton || this.isLoading; - }, - }, - methods: { - idGenerator(id) { - return `${id}_${this.uniqueId}`; - }, - }, -}; -</script> - -<template> - <form - ref="form-element" - class="lh-2" - @submit.prevent="$emit('submit')" - @reset.prevent="$emit('reset')" - > - <gl-card> - <template #header> - {{ s__('ContainerRegistry|Tag expiration policy') }} - </template> - <template> - <gl-form-group - :id="idGenerator('expiration-policy-toggle-group')" - :label-cols="labelCols" - :label-align="labelAlign" - :label-for="idGenerator('expiration-policy-toggle')" - :label="s__('ContainerRegistry|Expiration policy:')" - > - <div class="d-flex align-items-start"> - <gl-toggle - :id="idGenerator('expiration-policy-toggle')" - v-model="enabled" - :disabled="isLoading" - /> - <span class="mb-2 ml-1 lh-2" v-html="toggleDescriptionText"></span> - </div> - </gl-form-group> - - <gl-form-group - :id="idGenerator('expiration-policy-interval-group')" - :label-cols="labelCols" - :label-align="labelAlign" - :label-for="idGenerator('expiration-policy-interval')" - :label="s__('ContainerRegistry|Expiration interval:')" - > - <gl-form-select - :id="idGenerator('expiration-policy-interval')" - v-model="older_than" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.olderThan" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - :id="idGenerator('expiration-policy-schedule-group')" - :label-cols="labelCols" - :label-align="labelAlign" - :label-for="idGenerator('expiration-policy-schedule')" - :label="s__('ContainerRegistry|Expiration schedule:')" - > - <gl-form-select - :id="idGenerator('expiration-policy-schedule')" - v-model="cadence" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.cadence" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - :id="idGenerator('expiration-policy-latest-group')" - :label-cols="labelCols" - :label-align="labelAlign" - :label-for="idGenerator('expiration-policy-latest')" - :label="s__('ContainerRegistry|Number of tags to retain:')" - > - <gl-form-select - :id="idGenerator('expiration-policy-latest')" - v-model="keep_n" - :disabled="isFormElementDisabled" - > - <option v-for="option in formOptions.keepN" :key="option.key" :value="option.key"> - {{ option.label }} - </option> - </gl-form-select> - </gl-form-group> - - <gl-form-group - :id="idGenerator('expiration-policy-name-matching-group')" - :label-cols="labelCols" - :label-align="labelAlign" - :label-for="idGenerator('expiration-policy-name-matching')" - :label=" - s__('ContainerRegistry|Docker tags with names matching this regex pattern will expire:') - " - :state="nameRegexState" - :invalid-feedback=" - s__('ContainerRegistry|The value of this input should be less than 255 characters') - " - > - <gl-form-textarea - :id="idGenerator('expiration-policy-name-matching')" - v-model="name_regex" - :placeholder="$options.nameRegexPlaceholder" - :state="nameRegexState" - :disabled="isFormElementDisabled" - trim - /> - <template #description> - <span ref="regex-description" v-html="regexHelpText"></span> - </template> - </gl-form-group> - </template> - <template #footer> - <div class="d-flex justify-content-end"> - <gl-button - ref="cancel-button" - type="reset" - class="mr-2 d-block" - :disabled="isCancelButtonDisabled" - > - {{ __('Cancel') }} - </gl-button> - <gl-button - ref="save-button" - type="submit" - :disabled="isSubmitButtonDisabled" - variant="success" - class="d-flex justify-content-center align-items-center js-no-auto-disable" - > - {{ __('Save expiration policy') }} - <gl-loading-icon v-if="isLoading" class="ml-2" /> - </gl-button> - </div> - </template> - </gl-card> - </form> -</template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 4f5f3ee5cf9..e30876813c2 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -79,6 +79,12 @@ export default { required: false, default: false, }, + // This prop is used as a fallback in case if textarea.elm is undefined + textareaValue: { + type: String, + required: false, + default: '', + }, }, data() { return { @@ -183,7 +189,7 @@ export default { Can't use `$refs` as the component is technically in the parent component so we access the VNode & then get the element */ - const text = this.$slots.textarea[0].elm.value; + const text = this.$slots.textarea[0]?.elm?.value || this.textareaValue; if (text) { this.markdownPreviewLoading = true; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index fa88ca91170..7cb629dee21 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -37,6 +37,7 @@ class ApplicationController < ActionController::Base around_action :set_current_context around_action :set_locale around_action :set_session_storage + around_action :set_current_admin after_action :set_page_title_header, if: :json_request? after_action :limit_session_time, if: -> { !current_user } @@ -473,6 +474,13 @@ class ApplicationController < ActionController::Base response.headers['Page-Title'] = URI.escape(page_title('GitLab')) end + def set_current_admin(&block) + return yield unless Feature.enabled?(:user_mode_in_session) + return yield unless current_user + + Gitlab::Auth::CurrentUserMode.with_current_admin(current_user, &block) + end + def html_request? request.format.html? end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 30589694e3f..b40264bfdf4 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -139,7 +139,6 @@ class RegistrationsController < Devise::RegistrationsController ensure_correct_params! return unless Feature.enabled?(:registrations_recaptcha, default_enabled: true) # reCAPTCHA on the UI will still display however - return if experiment_enabled?(:signup_flow) # when the experimental signup flow is enabled for the current user, disable the reCAPTCHA check return unless show_recaptcha_sign_up? return unless Gitlab::Recaptcha.load_configurations! diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index f55acad8517..80bf765f3a4 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -17,17 +17,6 @@ module ClustersHelper end end - def new_cluster_partial(provider: nil) - case provider - when 'aws' - 'clusters/clusters/aws/new' - when 'gcp' - 'clusters/clusters/gcp/new' - else - 'clusters/clusters/cloud_providers/cloud_provider_selector' - end - end - def render_gcp_signup_offer return if Gitlab::CurrentSettings.current_application_settings.hide_third_party_offers? return unless show_gcp_signup_offer? diff --git a/app/models/issue.rb b/app/models/issue.rb index 3823b5e0fba..fd4a8c90386 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -147,6 +147,20 @@ class Issue < ApplicationRecord 'project_id' end + def self.simple_sorts + super.merge( + { + 'closest_future_date' => -> { order_closest_future_date }, + 'closest_future_date_asc' => -> { order_closest_future_date }, + 'due_date' => -> { order_due_date_asc.with_order_id_desc }, + 'due_date_asc' => -> { order_due_date_asc.with_order_id_desc }, + 'due_date_desc' => -> { order_due_date_desc.with_order_id_desc }, + 'relative_position' => -> { order_relative_position_asc.with_order_id_desc }, + 'relative_position_asc' => -> { order_relative_position_asc.with_order_id_desc } + } + ) + end + def self.sort_by_attribute(method, excluded_labels: []) case method.to_s when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb index 25eab6e4e03..94992adfd1e 100644 --- a/app/models/pool_repository.rb +++ b/app/models/pool_repository.rb @@ -110,8 +110,8 @@ class PoolRepository < ApplicationRecord end def storage - Storage::HashedProject - .new(self, prefix: Storage::HashedProject::POOL_PATH_PREFIX) + Storage::Hashed + .new(self, prefix: Storage::Hashed::POOL_PATH_PREFIX) end end diff --git a/app/models/project.rb b/app/models/project.rb index 064c647ac59..54bed41e9e7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2288,7 +2288,7 @@ class Project < ApplicationRecord def storage @storage ||= if hashed_storage?(:repository) - Storage::HashedProject.new(self) + Storage::Hashed.new(self) else Storage::LegacyProject.new(self) end diff --git a/app/models/storage/hashed_project.rb b/app/models/storage/hashed.rb index 9a38b06b2f9..898e75194db 100644 --- a/app/models/storage/hashed_project.rb +++ b/app/models/storage/hashed.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module Storage - class HashedProject + class Hashed attr_accessor :project delegate :gitlab_shell, :repository_storage, to: :project diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index b326b266017..0fc71d2e3f3 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -36,7 +36,7 @@ class FileUploader < GitlabUploader def self.base_dir(model, store = Store::LOCAL) decorated_model = model - decorated_model = Storage::HashedProject.new(model) if store == Store::REMOTE + decorated_model = Storage::Hashed.new(model) if store == Store::REMOTE model_path_segment(decorated_model) end @@ -57,7 +57,7 @@ class FileUploader < GitlabUploader # Returns a String without a trailing slash def self.model_path_segment(model) case model - when Storage::HashedProject then model.disk_path + when Storage::Hashed then model.disk_path else model.hashed_storage?(:attachments) ? model.disk_path : model.full_path end diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml index 56d46580b9e..c10983a5405 100644 --- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml +++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_button.html.haml @@ -1,10 +1,12 @@ - provider = local_assigns.fetch(:provider) +- is_current_provider = provider == params[:provider] - logo_path = local_assigns.fetch(:logo_path) - label = local_assigns.fetch(:label) - last = local_assigns.fetch(:last, false) -- classes = ['btn btn-light btn-outline flex-fill d-inline-flex flex-column justify-content-center align-items-center', ('mr-3' unless last)] +- classes = ["btn btn-light btn-outline flex-fill d-inline-flex flex-column justify-content-center align-items-center w-50 js-create-#{provider}-cluster-button"] +- conditional_classes = [('mr-3' unless last), ('active' if is_current_provider)] -= link_to clusterable.new_path(provider: provider), class: classes do += link_to clusterable.new_path(provider: provider), class: classes + conditional_classes do .svg-content.p-2= image_tag logo_path, alt: label, class: 'gl-w-64 gl-h-64' %span = label diff --git a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml index 91925f5f96f..aee355bbf71 100644 --- a/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml +++ b/app/views/clusters/clusters/cloud_providers/_cloud_provider_selector.html.haml @@ -1,8 +1,8 @@ - gke_label = s_('ClusterIntegration|Google GKE') - eks_label = s_('ClusterIntegration|Amazon EKS') - create_cluster_label = s_('ClusterIntegration|Create cluster on') -.d-flex.flex-column - %h5.mb-3 +.d-flex.flex-column.p-3 + %h4.mb-3 = create_cluster_label .d-flex = render partial: 'clusters/clusters/cloud_providers/cloud_provider_button', diff --git a/app/views/clusters/clusters/new.html.haml b/app/views/clusters/clusters/new.html.haml index 629585d82cd..fae78fbb7f4 100644 --- a/app/views/clusters/clusters/new.html.haml +++ b/app/views/clusters/clusters/new.html.haml @@ -1,6 +1,7 @@ - breadcrumb_title _('Kubernetes') - page_title _('Kubernetes Cluster') - active_tab = local_assigns.fetch(:active_tab, 'create') +- provider = params[:provider] = javascript_include_tag 'https://apis.google.com/js/api.js' = render_gcp_signup_offer @@ -19,8 +20,12 @@ %span Add existing cluster .tab-content.gitlab-tab-content - .tab-pane{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } - = render new_cluster_partial(provider: params[:provider]) + .tab-pane.p-0{ id: 'create-cluster-pane', class: active_when(active_tab == 'create'), role: 'tabpanel' } + = render 'clusters/clusters/cloud_providers/cloud_provider_selector' + + - if ['aws', 'gcp'].include?(provider) + .p-3.border-top + = render "clusters/clusters/#{provider}/new" .tab-pane{ id: 'add-cluster-pane', class: active_when(active_tab == 'add'), role: 'tabpanel' } = render 'clusters/clusters/user/header' |