summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-02-18 09:09:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-02-18 09:09:24 +0000
commit4720b569f0fcbb47e9f1a60e95172ae63b6f065a (patch)
tree5c6bcecbca227e608753a57a9aad19ccfe0567b6 /app
parentcefe554b7ce2d0b52f9de855be832a47c2bc24ab (diff)
downloadgitlab-ce-4720b569f0fcbb47e9f1a60e95172ae63b6f065a.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/due_date_select.js1
-rw-r--r--app/assets/javascripts/gl_dropdown.js2
-rw-r--r--app/assets/javascripts/jobs/components/environments_block.vue12
-rw-r--r--app/assets/javascripts/jobs/components/erased_block.vue4
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue6
-rw-r--r--app/assets/javascripts/jobs/components/manual_variables_form.vue8
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue6
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue4
-rw-r--r--app/assets/javascripts/jobs/store/getters.js14
-rw-r--r--app/assets/javascripts/monitoring/components/charts/anomaly.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue28
-rw-r--r--app/assets/javascripts/pages/groups/registry/repositories/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/registry/repositories/index.js4
-rw-r--r--app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue59
-rw-r--r--app/assets/javascripts/registry/explorer/index.js47
-rw-r--r--app/assets/javascripts/registry/explorer/pages/details.vue7
-rw-r--r--app/assets/javascripts/registry/explorer/pages/list.vue2
-rw-r--r--app/assets/javascripts/registry/explorer/router.js10
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js16
-rw-r--r--app/assets/javascripts/registry/explorer/utils.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue2
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss2
-rw-r--r--app/graphql/mutations/issues/update.rb25
-rw-r--r--app/graphql/types/mutation_type.rb1
-rw-r--r--app/views/projects/_zen.html.haml4
-rw-r--r--app/views/shared/snippets/_form.html.haml4
26 files changed, 204 insertions, 72 deletions
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index b973316b3b9..218bf41cd58 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -1,3 +1,4 @@
+/* eslint-disable max-classes-per-file */
import $ from 'jquery';
import Pikaday from 'pikaday';
import dateFormat from 'dateformat';
diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js
index 3d5c03440ea..918276ce329 100644
--- a/app/assets/javascripts/gl_dropdown.js
+++ b/app/assets/javascripts/gl_dropdown.js
@@ -1,4 +1,4 @@
-/* eslint-disable one-var, consistent-return */
+/* eslint-disable max-classes-per-file, one-var, consistent-return */
import $ from 'jquery';
import _ from 'underscore';
diff --git a/app/assets/javascripts/jobs/components/environments_block.vue b/app/assets/javascripts/jobs/components/environments_block.vue
index 797aa15a2b9..d9168f57cc7 100644
--- a/app/assets/javascripts/jobs/components/environments_block.vue
+++ b/app/assets/javascripts/jobs/components/environments_block.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { escape as esc, isEmpty } from 'lodash';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import { sprintf, __ } from '../../locale';
@@ -43,7 +43,7 @@ export default {
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${this.deploymentStatus.environment.environment_path}" class="js-environment-link">`,
- name: _.escape(this.deploymentStatus.environment.name),
+ name: esc(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false,
@@ -58,10 +58,10 @@ export default {
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
},
hasEnvironment() {
- return !_.isEmpty(this.deploymentStatus.environment);
+ return !isEmpty(this.deploymentStatus.environment);
},
lastDeploymentPath() {
- return !_.isEmpty(this.lastDeployment.deployable)
+ return !isEmpty(this.lastDeployment.deployable)
? this.lastDeployment.deployable.build_path
: '';
},
@@ -74,8 +74,8 @@ export default {
}
const { name, path } = this.deploymentCluster;
- const escapedName = _.escape(name);
- const escapedPath = _.escape(path);
+ const escapedName = esc(name);
+ const escapedPath = esc(path);
if (!escapedPath) {
return escapedName;
diff --git a/app/assets/javascripts/jobs/components/erased_block.vue b/app/assets/javascripts/jobs/components/erased_block.vue
index 8437ad89301..fc5e022f44a 100644
--- a/app/assets/javascripts/jobs/components/erased_block.vue
+++ b/app/assets/javascripts/jobs/components/erased_block.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import { GlLink } from '@gitlab/ui';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
@@ -21,7 +21,7 @@ export default {
},
computed: {
isErasedByUser() {
- return !_.isEmpty(this.user);
+ return !isEmpty(this.user);
},
},
};
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index bc310f77a58..0783d1157be 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { throttle, isEmpty } from 'lodash';
import { mapGetters, mapState, mapActions } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
@@ -125,7 +125,7 @@ export default {
// Once the job log is loaded,
// fetch the stages for the dropdown on the sidebar
job(newVal, oldVal) {
- if (_.isEmpty(oldVal) && !_.isEmpty(newVal.pipeline)) {
+ if (isEmpty(oldVal) && !isEmpty(newVal.pipeline)) {
const stages = this.job.pipeline.details.stages || [];
const defaultStage = stages.find(stage => stage && stage.name === this.selectedStage);
@@ -145,7 +145,7 @@ export default {
},
},
created() {
- this.throttled = _.throttle(this.toggleScrollButtons, 100);
+ this.throttled = throttle(this.toggleScrollButtons, 100);
window.addEventListener('resize', this.onResize);
window.addEventListener('scroll', this.updateScroll);
diff --git a/app/assets/javascripts/jobs/components/manual_variables_form.vue b/app/assets/javascripts/jobs/components/manual_variables_form.vue
index c32a3cac7be..a23f30d571a 100644
--- a/app/assets/javascripts/jobs/components/manual_variables_form.vue
+++ b/app/assets/javascripts/jobs/components/manual_variables_form.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { uniqueId } from 'lodash';
import { mapActions } from 'vuex';
import { GlButton } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
@@ -19,7 +19,9 @@ export default {
validator(value) {
return (
value === null ||
- (_.has(value, 'path') && _.has(value, 'method') && _.has(value, 'button_title'))
+ (Object.prototype.hasOwnProperty.call(value, 'path') &&
+ Object.prototype.hasOwnProperty.call(value, 'method') &&
+ Object.prototype.hasOwnProperty.call(value, 'button_title'))
);
},
},
@@ -78,7 +80,7 @@ export default {
const newVariable = {
key: this.key,
secret_value: this.secretValue,
- id: _.uniqueId(),
+ id: uniqueId(),
};
this.variables.push(newVariable);
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index a61acf2f6eb..f1683bc2195 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import { mapActions, mapState } from 'vuex';
import { GlLink, GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
@@ -84,10 +84,10 @@ export default {
);
},
hasArtifact() {
- return !_.isEmpty(this.job.artifact);
+ return !isEmpty(this.job.artifact);
},
hasTriggers() {
- return !_.isEmpty(this.job.trigger);
+ return !isEmpty(this.job.trigger);
},
hasStages() {
return (
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 09f9647a680..ddcfc3d6db6 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,5 +1,5 @@
<script>
-import _ from 'underscore';
+import { isEmpty } from 'lodash';
import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -24,7 +24,7 @@ export default {
},
computed: {
hasRef() {
- return !_.isEmpty(this.pipeline.ref);
+ return !isEmpty(this.pipeline.ref);
},
isTriggeredByMergeRequest() {
return Boolean(this.pipeline.merge_request);
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 406b1a2e375..3f02f924eed 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -1,4 +1,4 @@
-import _ from 'underscore';
+import { isEmpty, isString } from 'lodash';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
@@ -7,15 +7,15 @@ export const hasUnmetPrerequisitesFailure = state =>
state.job && state.job.failure_reason && state.job.failure_reason === 'unmet_prerequisites';
export const shouldRenderCalloutMessage = state =>
- !_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
+ !isEmpty(state.job.status) && !isEmpty(state.job.callout_message);
/**
* When job has not started the key will be null
* When job started the key will be a string with a date.
*/
-export const shouldRenderTriggeredLabel = state => _.isString(state.job.started);
+export const shouldRenderTriggeredLabel = state => isString(state.job.started);
-export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
+export const hasEnvironment = state => !isEmpty(state.job.deployment_status);
/**
* Checks if it the job has trace.
@@ -23,7 +23,7 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
* @returns {Boolean}
*/
export const hasTrace = state =>
- state.job.has_trace || (!_.isEmpty(state.job.status) && state.job.status.group === 'running');
+ state.job.has_trace || (!isEmpty(state.job.status) && state.job.status.group === 'running');
export const emptyStateIllustration = state =>
(state.job && state.job.status && state.job.status.illustration) || {};
@@ -38,8 +38,8 @@ export const emptyStateAction = state =>
* @returns {Boolean}
*/
export const shouldRenderSharedRunnerLimitWarning = state =>
- !_.isEmpty(state.job.runners) &&
- !_.isEmpty(state.job.runners.quota) &&
+ !isEmpty(state.job.runners) &&
+ !isEmpty(state.job.runners.quota) &&
state.job.runners.quota.used >= state.job.runners.quota.limit;
export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceComplete;
diff --git a/app/assets/javascripts/monitoring/components/charts/anomaly.vue b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
index bcbc1dad89d..447f8845506 100644
--- a/app/assets/javascripts/monitoring/components/charts/anomaly.vue
+++ b/app/assets/javascripts/monitoring/components/charts/anomaly.vue
@@ -127,7 +127,6 @@ export default {
});
const yAxisWithOffset = {
- name: this.yAxisLabel,
axisLabel: {
formatter: num => roundOffFloat(num - this.yOffset, 3).toString(),
},
@@ -162,6 +161,7 @@ export default {
}),
);
}
+
return { yAxis: yAxisWithOffset, series: boundarySeries };
},
},
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index e6dbc38402e..d2b1e4da3fd 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -162,7 +162,8 @@ export default {
);
},
chartOptions() {
- const option = omit(this.option, 'series');
+ const { yAxis, xAxis } = this.option;
+ const option = omit(this.option, ['series', 'yAxis', 'xAxis']);
const dataYAxis = {
name: this.yAxisLabel,
@@ -173,7 +174,9 @@ export default {
axisLabel: {
formatter: num => roundOffFloat(num, 3).toString(),
},
+ ...yAxis,
};
+
const deploymentsYAxis = {
show: false,
min: deploymentYAxisCoords.min,
@@ -184,18 +187,21 @@ export default {
},
};
+ const timeXAxis = {
+ name: __('Time'),
+ type: 'time',
+ axisLabel: {
+ formatter: date => dateFormat(date, dateFormats.timeOfDay),
+ },
+ axisPointer: {
+ snap: true,
+ },
+ ...xAxis,
+ };
+
return {
series: this.chartOptionSeries,
- xAxis: {
- name: __('Time'),
- type: 'time',
- axisLabel: {
- formatter: date => dateFormat(date, dateFormats.timeOfDay),
- },
- axisPointer: {
- snap: true,
- },
- },
+ xAxis: timeXAxis,
yAxis: [dataYAxis, deploymentsYAxis],
dataZoom: [this.dataZoomConfig],
...option,
diff --git a/app/assets/javascripts/pages/groups/registry/repositories/index.js b/app/assets/javascripts/pages/groups/registry/repositories/index.js
index 52fb839e3fd..47fea2be189 100644
--- a/app/assets/javascripts/pages/groups/registry/repositories/index.js
+++ b/app/assets/javascripts/pages/groups/registry/repositories/index.js
@@ -3,5 +3,7 @@ import registryExplorer from '~/registry/explorer/index';
document.addEventListener('DOMContentLoaded', () => {
initRegistryImages();
- registryExplorer();
+ const { attachMainComponent, attachBreadcrumb } = registryExplorer();
+ attachBreadcrumb();
+ attachMainComponent();
});
diff --git a/app/assets/javascripts/pages/projects/registry/repositories/index.js b/app/assets/javascripts/pages/projects/registry/repositories/index.js
index 52fb839e3fd..47fea2be189 100644
--- a/app/assets/javascripts/pages/projects/registry/repositories/index.js
+++ b/app/assets/javascripts/pages/projects/registry/repositories/index.js
@@ -3,5 +3,7 @@ import registryExplorer from '~/registry/explorer/index';
document.addEventListener('DOMContentLoaded', () => {
initRegistryImages();
- registryExplorer();
+ const { attachMainComponent, attachBreadcrumb } = registryExplorer();
+ attachBreadcrumb();
+ attachMainComponent();
});
diff --git a/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue
new file mode 100644
index 00000000000..f51948da8cc
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/components/registry_breadcrumb.vue
@@ -0,0 +1,59 @@
+<script>
+import { initial, first, last } from 'lodash';
+
+export default {
+ props: {
+ crumbs: {
+ type: Array,
+ required: true,
+ },
+ },
+ computed: {
+ rootRoute() {
+ return this.$router.options.routes.find(r => r.meta.root);
+ },
+ isRootRoute() {
+ return this.$route.name === this.rootRoute.name;
+ },
+ rootCrumbs() {
+ return initial(this.crumbs);
+ },
+ divider() {
+ const { classList, tagName, innerHTML } = first(this.crumbs).querySelector('svg');
+ return { classList: [...classList], tagName, innerHTML };
+ },
+ lastCrumb() {
+ const { children } = last(this.crumbs);
+ const { tagName, classList } = first(children);
+ return {
+ tagName,
+ classList: [...classList],
+ text: this.$route.meta.nameGenerator(this.$route),
+ path: { to: this.$route.name },
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <ul>
+ <li
+ v-for="(crumb, index) in rootCrumbs"
+ :key="index"
+ :class="crumb.classList"
+ v-html="crumb.innerHTML"
+ ></li>
+ <li v-if="!isRootRoute">
+ <router-link ref="rootRouteLink" :to="rootRoute.path">
+ {{ rootRoute.meta.nameGenerator(rootRoute) }}
+ </router-link>
+ <component :is="divider.tagName" :class="divider.classList" v-html="divider.innerHTML" />
+ </li>
+ <li>
+ <component :is="lastCrumb.tagName" ref="lastCrumb" :class="lastCrumb.classList">
+ <router-link ref="childRouteLink" :to="lastCrumb.path">{{ lastCrumb.text }}</router-link>
+ </component>
+ </li>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/registry/explorer/index.js b/app/assets/javascripts/registry/explorer/index.js
index daa2e4fb109..a36978303c6 100644
--- a/app/assets/javascripts/registry/explorer/index.js
+++ b/app/assets/javascripts/registry/explorer/index.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import RegistryExplorer from './pages/index.vue';
+import RegistryBreadcrumb from './components/registry_breadcrumb.vue';
import { createStore } from './stores';
import createRouter from './router';
@@ -19,15 +20,39 @@ export default () => {
const router = createRouter(endpoint, store);
store.dispatch('setInitialState', el.dataset);
- return new Vue({
- el,
- store,
- router,
- components: {
- RegistryExplorer,
- },
- render(createElement) {
- return createElement('registry-explorer');
- },
- });
+ const attachMainComponent = () =>
+ new Vue({
+ el,
+ store,
+ router,
+ components: {
+ RegistryExplorer,
+ },
+ render(createElement) {
+ return createElement('registry-explorer');
+ },
+ });
+
+ const attachBreadcrumb = () => {
+ const breadCrumbEl = document.querySelector('nav .js-breadcrumbs-list');
+ const crumbs = [...document.querySelectorAll('.js-breadcrumbs-list li')];
+ return new Vue({
+ el: breadCrumbEl,
+ store,
+ router,
+ components: {
+ RegistryBreadcrumb,
+ },
+ render(createElement) {
+ return createElement('registry-breadcrumb', {
+ class: breadCrumbEl.className,
+ props: {
+ crumbs,
+ },
+ });
+ },
+ });
+ };
+
+ return { attachBreadcrumb, attachMainComponent };
};
diff --git a/app/assets/javascripts/registry/explorer/pages/details.vue b/app/assets/javascripts/registry/explorer/pages/details.vue
index bff67bb8376..bc613db8672 100644
--- a/app/assets/javascripts/registry/explorer/pages/details.vue
+++ b/app/assets/javascripts/registry/explorer/pages/details.vue
@@ -19,6 +19,7 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import Tracking from '~/tracking';
+import { decodeAndParse } from '../utils';
import {
LIST_KEY_TAG,
LIST_KEY_IMAGE_ID,
@@ -62,7 +63,7 @@ export default {
computed: {
...mapState(['tags', 'tagsPagination', 'isLoading', 'config']),
imageName() {
- const { name } = JSON.parse(window.atob(this.$route.params.id));
+ const { name } = decodeAndParse(this.$route.params.id);
return name;
},
fields() {
@@ -169,7 +170,7 @@ export default {
},
handleSingleDelete(itemToDelete) {
this.itemsToBeDeleted = [];
- this.requestDeleteTag({ tag: itemToDelete, imageId: this.$route.params.id });
+ this.requestDeleteTag({ tag: itemToDelete, params: this.$route.params.id });
},
handleMultipleDelete() {
const { itemsToBeDeleted } = this;
@@ -178,7 +179,7 @@ export default {
this.requestDeleteTags({
ids: itemsToBeDeleted.map(x => this.tags[x].name),
- imageId: this.$route.params.id,
+ params: this.$route.params.id,
});
},
onDeletionConfirmed() {
diff --git a/app/assets/javascripts/registry/explorer/pages/list.vue b/app/assets/javascripts/registry/explorer/pages/list.vue
index dc730ac2828..1dbc7cc2242 100644
--- a/app/assets/javascripts/registry/explorer/pages/list.vue
+++ b/app/assets/javascripts/registry/explorer/pages/list.vue
@@ -70,7 +70,7 @@ export default {
this.itemToDelete = {};
},
encodeListItem(item) {
- const params = JSON.stringify({ name: item.path, tags_path: item.tags_path });
+ const params = JSON.stringify({ name: item.path, tags_path: item.tags_path, id: item.id });
return window.btoa(params);
},
},
diff --git a/app/assets/javascripts/registry/explorer/router.js b/app/assets/javascripts/registry/explorer/router.js
index 8cf35b8f245..7e4c3d28623 100644
--- a/app/assets/javascripts/registry/explorer/router.js
+++ b/app/assets/javascripts/registry/explorer/router.js
@@ -1,8 +1,9 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
-import { __ } from '~/locale';
+import { s__ } from '~/locale';
import List from './pages/list.vue';
import Details from './pages/details.vue';
+import { decodeAndParse } from './utils';
Vue.use(VueRouter);
@@ -16,7 +17,8 @@ export default function createRouter(base, store) {
path: '/',
component: List,
meta: {
- name: __('Container Registry'),
+ nameGenerator: () => s__('ContainerRegistry|Container Registry'),
+ root: true,
},
beforeEnter: (to, from, next) => {
store.dispatch('requestImagesList');
@@ -28,10 +30,10 @@ export default function createRouter(base, store) {
path: '/:id',
component: Details,
meta: {
- name: __('Tags'),
+ nameGenerator: route => decodeAndParse(route.params.id).name,
},
beforeEnter: (to, from, next) => {
- store.dispatch('requestTagsList', { id: to.params.id });
+ store.dispatch('requestTagsList', { params: to.params.id });
next();
},
},
diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js
index 25ff105ac53..86d00d4fca9 100644
--- a/app/assets/javascripts/registry/explorer/stores/actions.js
+++ b/app/assets/javascripts/registry/explorer/stores/actions.js
@@ -13,6 +13,7 @@ import {
DELETE_IMAGE_ERROR_MESSAGE,
DELETE_IMAGE_SUCCESS_MESSAGE,
} from '../constants';
+import { decodeAndParse } from '../utils';
export const setInitialState = ({ commit }, data) => commit(types.SET_INITIAL_STATE, data);
@@ -43,9 +44,9 @@ export const requestImagesList = ({ commit, dispatch, state }, pagination = {})
});
};
-export const requestTagsList = ({ commit, dispatch }, { pagination = {}, id }) => {
+export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params }) => {
commit(types.SET_MAIN_LOADING, true);
- const { tags_path } = JSON.parse(window.atob(id));
+ const { tags_path } = decodeAndParse(params);
const { page = DEFAULT_PAGE, perPage = DEFAULT_PAGE_SIZE } = pagination;
return axios
@@ -61,13 +62,13 @@ export const requestTagsList = ({ commit, dispatch }, { pagination = {}, id }) =
});
};
-export const requestDeleteTag = ({ commit, dispatch, state }, { tag, imageId }) => {
+export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => {
commit(types.SET_MAIN_LOADING, true);
return axios
.delete(tag.destroy_path)
.then(() => {
createFlash(DELETE_TAG_SUCCESS_MESSAGE, 'success');
- dispatch('requestTagsList', { pagination: state.tagsPagination, id: imageId });
+ dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAG_ERROR_MESSAGE);
@@ -77,15 +78,16 @@ export const requestDeleteTag = ({ commit, dispatch, state }, { tag, imageId })
});
};
-export const requestDeleteTags = ({ commit, dispatch, state }, { ids, imageId }) => {
+export const requestDeleteTags = ({ commit, dispatch, state }, { ids, params }) => {
commit(types.SET_MAIN_LOADING, true);
- const url = `/${state.config.projectPath}/registry/repository/${imageId}/tags/bulk_destroy`;
+ const { id } = decodeAndParse(params);
+ const url = `/${state.config.projectPath}/registry/repository/${id}/tags/bulk_destroy`;
return axios
.delete(url, { params: { ids } })
.then(() => {
createFlash(DELETE_TAGS_SUCCESS_MESSAGE, 'success');
- dispatch('requestTagsList', { pagination: state.tagsPagination, id: imageId });
+ dispatch('requestTagsList', { pagination: state.tagsPagination, params });
})
.catch(() => {
createFlash(DELETE_TAGS_ERROR_MESSAGE);
diff --git a/app/assets/javascripts/registry/explorer/utils.js b/app/assets/javascripts/registry/explorer/utils.js
new file mode 100644
index 00000000000..b1df87c6993
--- /dev/null
+++ b/app/assets/javascripts/registry/explorer/utils.js
@@ -0,0 +1,2 @@
+// eslint-disable-next-line import/prefer-default-export
+export const decodeAndParse = param => JSON.parse(window.atob(param));
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
index 75d1e5865b0..9df0c045fe4 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_failed_to_merge.vue
@@ -61,7 +61,7 @@ export default {
eventHub.$emit('EnablePolling');
},
updateTimer() {
- this.timer = this.timer - 1;
+ this.timer -= 1;
if (this.timer === 0) {
this.refresh();
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 21b23feb57f..89b673397a2 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -192,7 +192,7 @@
.stage-events {
width: 60%;
overflow: scroll;
- height: 467px;
+ min-height: 467px;
}
.stage-event-list {
diff --git a/app/graphql/mutations/issues/update.rb b/app/graphql/mutations/issues/update.rb
new file mode 100644
index 00000000000..119bc51e4a4
--- /dev/null
+++ b/app/graphql/mutations/issues/update.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Issues
+ class Update < Base
+ graphql_name 'UpdateIssue'
+
+ # Add arguments here instead of creating separate mutations
+
+ def resolve(project_path:, iid:, **args)
+ issue = authorized_find!(project_path: project_path, iid: iid)
+ project = issue.project
+
+ ::Issues::UpdateService.new(project, current_user, args).execute(issue)
+
+ {
+ issue: issue,
+ errors: issue.errors.full_messages
+ }
+ end
+ end
+ end
+end
+
+Mutations::Issues::Update.prepend_if_ee('::EE::Mutations::Issues::Update')
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index ee0f4dbb05f..90e9e1ec0b9 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -11,6 +11,7 @@ module Types
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::Issues::SetConfidential
mount_mutation Mutations::Issues::SetDueDate
+ mount_mutation Mutations::Issues::Update
mount_mutation Mutations::MergeRequests::SetLabels
mount_mutation Mutations::MergeRequests::SetLocked
mount_mutation Mutations::MergeRequests::SetMilestone
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index c502b392384..744aef3cad4 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -2,6 +2,7 @@
- current_text ||= nil
- supports_autocomplete = local_assigns.fetch(:supports_autocomplete, true)
- supports_quick_actions = local_assigns.fetch(:supports_quick_actions, false)
+- qa_selector = local_assigns.fetch(:qa_selector, '')
.zen-backdrop
- classes << ' js-gfm-input js-autosize markdown-area'
- if defined?(f) && f
@@ -10,7 +11,8 @@
placeholder: placeholder,
dir: 'auto',
data: { supports_quick_actions: supports_quick_actions,
- supports_autocomplete: supports_autocomplete }
+ supports_autocomplete: supports_autocomplete,
+ qa_selector: qa_selector }
- else
= text_area_tag attr, current_text, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave{ href: "#" }
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index f867fb2b6f7..3c2c751c579 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -16,10 +16,10 @@
= f.label :description, s_("Snippets|Description (optional)"), class: 'label-bold'
.js-collapsible-input
.js-collapsed{ class: ('d-none' if is_expanded) }
- = text_field_tag nil, nil, class: 'form-control', placeholder: description_placeholder
+ = text_field_tag nil, nil, class: 'form-control', placeholder: description_placeholder, data: { qa_selector: 'description_placeholder' }
.js-expanded{ class: ('d-none' if !is_expanded) }
= render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: description_placeholder
+ = render 'projects/zen', f: f, attr: :description, classes: 'note-textarea', placeholder: description_placeholder, qa_selector: 'description_field'
= render 'shared/notes/hints'
.form-group.file-editor