diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 18:09:27 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-07-20 18:09:27 +0000 |
commit | bf213f07c8146b7121240af90a07cb4b2ecc41fa (patch) | |
tree | 184537ad2334124c5090f2a729a9fd58bccf6b51 | |
parent | dd2da214c9644ef1064061d706dfeec50f9fad8f (diff) | |
download | gitlab-ce-bf213f07c8146b7121240af90a07cb4b2ecc41fa.tar.gz |
Add latest changes from gitlab-org/gitlab@master
131 files changed, 1023 insertions, 802 deletions
diff --git a/app/assets/javascripts/admin/statistics_panel/store/actions.js b/app/assets/javascripts/admin/statistics_panel/store/actions.js index 537025f524c..ac10aa07c11 100644 --- a/app/assets/javascripts/admin/statistics_panel/store/actions.js +++ b/app/assets/javascripts/admin/statistics_panel/store/actions.js @@ -23,6 +23,3 @@ export const receiveStatisticsError = ({ commit }, error) => { commit(types.RECEIVE_STATISTICS_ERROR, error); createFlash(s__('AdminDashboard|Error loading the statistics. Please try again')); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/admin/statistics_panel/store/getters.js b/app/assets/javascripts/admin/statistics_panel/store/getters.js index 24437bc76bf..2aa34b8f38e 100644 --- a/app/assets/javascripts/admin/statistics_panel/store/getters.js +++ b/app/assets/javascripts/admin/statistics_panel/store/getters.js @@ -3,6 +3,7 @@ * and returns an array of the following form: * [{ key: "forks", label: "Forks", value: 50 }] */ +// eslint-disable-next-line import/prefer-default-export export const getStatistics = state => labels => Object.keys(labels).map(key => { const result = { @@ -12,6 +13,3 @@ export const getStatistics = state => labels => }; return result; }); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js index 1ef012696c5..d06efaa25de 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js @@ -146,6 +146,3 @@ export const expandAllDiscussions = ({ dispatch, state }) => export const toggleResolveDiscussion = ({ commit }, draftId) => { commit(types.TOGGLE_RESOLVE_DISCUSSION, draftId); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/getters.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/getters.js index 43f43c983aa..22ae6c2e970 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/getters.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/getters.js @@ -82,6 +82,3 @@ export const isPublishingDraft = state => draftId => state.currentlyPublishingDrafts.indexOf(draftId) !== -1; export const sortedDrafts = state => [...state.drafts].sort((a, b) => a.id > b.id); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/clusters_list/store/actions.js b/app/assets/javascripts/clusters_list/store/actions.js index dddcfb3d975..011d7391e7d 100644 --- a/app/assets/javascripts/clusters_list/store/actions.js +++ b/app/assets/javascripts/clusters_list/store/actions.js @@ -76,6 +76,3 @@ export const fetchClusters = ({ state, commit, dispatch }) => { export const setPage = ({ commit }, page) => { commit(types.SET_PAGE, page); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/contributors/stores/actions.js b/app/assets/javascripts/contributors/stores/actions.js index 4138ff24f1d..aefb132efc3 100644 --- a/app/assets/javascripts/contributors/stores/actions.js +++ b/app/assets/javascripts/contributors/stores/actions.js @@ -3,6 +3,7 @@ import { __ } from '~/locale'; import service from '../services/contributors_service'; import * as types from './mutation_types'; +// eslint-disable-next-line import/prefer-default-export export const fetchChartData = ({ commit }, endpoint) => { commit(types.SET_LOADING_STATE, true); @@ -15,6 +16,3 @@ export const fetchChartData = ({ commit }, endpoint) => { }) .catch(() => flash(__('An error occurred while loading chart data'))); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/contributors/stores/getters.js b/app/assets/javascripts/contributors/stores/getters.js index 9b0def9b3ca..9022179d6c7 100644 --- a/app/assets/javascripts/contributors/stores/getters.js +++ b/app/assets/javascripts/contributors/stores/getters.js @@ -28,6 +28,3 @@ export const parsedData = state => { byAuthorEmail, }; }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js b/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js index f05ad7773a2..f0c41d1d230 100644 --- a/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js +++ b/app/assets/javascripts/create_cluster/gke_cluster/store/actions.js @@ -90,6 +90,3 @@ export const fetchMachineTypes = ({ commit, state }) => mutation: types.SET_MACHINE_TYPES, payloadKey: 'items', }); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/design_management_new/pages/index.vue b/app/assets/javascripts/design_management_new/pages/index.vue index 2a100fae280..700fa903a9c 100644 --- a/app/assets/javascripts/design_management_new/pages/index.vue +++ b/app/assets/javascripts/design_management_new/pages/index.vue @@ -246,28 +246,28 @@ export default { this.onUploadDesign([newFile]); } }, - toggleOnPasteListener(route) { - if (route === DESIGNS_ROUTE_NAME) { - document.addEventListener('paste', this.onDesignPaste); - } else { - document.removeEventListener('paste', this.onDesignPaste); - } + toggleOnPasteListener() { + document.addEventListener('paste', this.onDesignPaste); + }, + toggleOffPasteListener() { + document.removeEventListener('paste', this.onDesignPaste); }, }, beforeRouteUpdate(to, from, next) { - this.toggleOnPasteListener(to.name); this.selectedDesigns = []; next(); }, - beforeRouteLeave(to, from, next) { - this.toggleOnPasteListener(to.name); - next(); - }, }; </script> <template> - <div data-testid="designs-root" class="gl-mt-5"> + <div + data-testid="designs-root" + class="gl-mt-5" + :class="{ 'designs-root': !isDesignListEmpty }" + @mouseenter="toggleOnPasteListener" + @mouseleave="toggleOffPasteListener" + > <header v-if="showToolbar" class="row-content-block border-top-0 p-2 d-flex"> <div class="gl-display-flex gl-justify-content-space-between gl-align-items-center gl-w-full"> <div> diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index d469ed8ee82..cdc17bddc6b 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -759,6 +759,3 @@ export const navigateToDiffFileIndex = ({ commit, state }, index) => { commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/diffs/store/getters.js b/app/assets/javascripts/diffs/store/getters.js index 047caed1e63..a24894b8d6b 100644 --- a/app/assets/javascripts/diffs/store/getters.js +++ b/app/assets/javascripts/diffs/store/getters.js @@ -74,7 +74,6 @@ export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) = discussion => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash, ) || []; -// prevent babel-plugin-rewire from generating an invalid default during karma∂ tests export const getDiffFileByHash = state => fileHash => state.diffFiles.find(file => file.file_hash === fileHash); @@ -130,6 +129,3 @@ export const fileLineCoverage = state => (file, line) => { */ export const currentDiffIndex = state => Math.max(0, state.diffFiles.findIndex(diff => diff.file_hash === state.currentDiffFileId)); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js index 05554b2b566..ce7276602e3 100644 --- a/app/assets/javascripts/error_tracking/store/actions.js +++ b/app/assets/javascripts/error_tracking/store/actions.js @@ -34,5 +34,3 @@ export const updateIgnoreStatus = ({ commit, dispatch }, params) => { commit(types.SET_UPDATING_IGNORE_STATUS, false); }); }; - -export default () => {}; diff --git a/app/assets/javascripts/error_tracking/store/details/actions.js b/app/assets/javascripts/error_tracking/store/details/actions.js index 5914a79f092..9d3c710d51d 100644 --- a/app/assets/javascripts/error_tracking/store/details/actions.js +++ b/app/assets/javascripts/error_tracking/store/details/actions.js @@ -10,6 +10,7 @@ const stopPolling = poll => { if (poll) poll.stop(); }; +// eslint-disable-next-line import/prefer-default-export export function startPollingStacktrace({ commit }, endpoint) { stackTracePoll = new Poll({ resource: service, @@ -32,5 +33,3 @@ export function startPollingStacktrace({ commit }, endpoint) { stackTracePoll.makeRequest(); } - -export default () => {}; diff --git a/app/assets/javascripts/error_tracking/store/details/getters.js b/app/assets/javascripts/error_tracking/store/details/getters.js index a36c84dc28c..f2778fbb2c7 100644 --- a/app/assets/javascripts/error_tracking/store/details/getters.js +++ b/app/assets/javascripts/error_tracking/store/details/getters.js @@ -1,6 +1,5 @@ +// eslint-disable-next-line import/prefer-default-export export const stacktrace = state => state.stacktraceData.stack_trace_entries ? state.stacktraceData.stack_trace_entries.reverse() : []; - -export default () => {}; diff --git a/app/assets/javascripts/error_tracking/store/list/actions.js b/app/assets/javascripts/error_tracking/store/list/actions.js index 94cf444d2e4..c55420f46ac 100644 --- a/app/assets/javascripts/error_tracking/store/list/actions.js +++ b/app/assets/javascripts/error_tracking/store/list/actions.js @@ -102,5 +102,3 @@ export const fetchPaginatedResults = ({ commit, dispatch }, cursor) => { export const removeIgnoredResolvedErrors = ({ commit }, error) => { commit(types.REMOVE_IGNORED_RESOLVED_ERRORS, error); }; - -export default () => {}; diff --git a/app/assets/javascripts/error_tracking_settings/store/actions.js b/app/assets/javascripts/error_tracking_settings/store/actions.js index 3f1ac426278..7a5e449dd98 100644 --- a/app/assets/javascripts/error_tracking_settings/store/actions.js +++ b/app/assets/javascripts/error_tracking_settings/store/actions.js @@ -89,6 +89,3 @@ export const updateSelectedProject = ({ commit }, selectedProject) => { export const setInitialState = ({ commit }, data) => { commit(types.SET_INITIAL_STATE, data); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/error_tracking_settings/store/getters.js b/app/assets/javascripts/error_tracking_settings/store/getters.js index e27fe9c079e..a02a4310ab9 100644 --- a/app/assets/javascripts/error_tracking_settings/store/getters.js +++ b/app/assets/javascripts/error_tracking_settings/store/getters.js @@ -39,6 +39,3 @@ export const projectSelectionLabel = state => { } return s__('ErrorTracking|To enable project selection, enter a valid Auth Token'); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/error_tracking_settings/utils.js b/app/assets/javascripts/error_tracking_settings/utils.js index 450e8728121..9a09702a030 100644 --- a/app/assets/javascripts/error_tracking_settings/utils.js +++ b/app/assets/javascripts/error_tracking_settings/utils.js @@ -14,5 +14,3 @@ export const transformFrontendSettings = ({ apiHost, enabled, token, selectedPro }; export const getDisplayName = project => `${project.organizationName} | ${project.slug}`; - -export default () => {}; diff --git a/app/assets/javascripts/frequent_items/store/actions.js b/app/assets/javascripts/frequent_items/store/actions.js index ba62ab67e50..d4756e2ea6a 100644 --- a/app/assets/javascripts/frequent_items/store/actions.js +++ b/app/assets/javascripts/frequent_items/store/actions.js @@ -76,6 +76,3 @@ export const setSearchQuery = ({ commit, dispatch }, query) => { dispatch('fetchFrequentItems'); } }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/frequent_items/store/getters.js b/app/assets/javascripts/frequent_items/store/getters.js index 00165db6684..73e66643f06 100644 --- a/app/assets/javascripts/frequent_items/store/getters.js +++ b/app/assets/javascripts/frequent_items/store/getters.js @@ -1,4 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export export const hasSearchQuery = state => state.searchQuery !== ''; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/helpers/monitor_helper.js b/app/assets/javascripts/helpers/monitor_helper.js index 5f85ee58779..5e345321013 100644 --- a/app/assets/javascripts/helpers/monitor_helper.js +++ b/app/assets/javascripts/helpers/monitor_helper.js @@ -49,7 +49,7 @@ const multiMetricLabel = metricAttributes => { * @param {Object} metricAttributes - Default metric attribute values (e.g. method, instance) * @returns {String} The formatted query label */ -const getSeriesLabel = (queryLabel, metricAttributes) => { +export const getSeriesLabel = (queryLabel, metricAttributes) => { return ( singleAttributeLabel(queryLabel, metricAttributes) || templatedLabel(queryLabel, metricAttributes) || @@ -63,7 +63,6 @@ const getSeriesLabel = (queryLabel, metricAttributes) => { * @param {Object} defaultConfig - Default chart config values (e.g. lineStyle, name) * @returns {Array} The formatted values */ -// eslint-disable-next-line import/prefer-default-export export const makeDataSeries = (queryResults, defaultConfig) => queryResults.map(result => { return { diff --git a/app/assets/javascripts/ide/stores/modules/branches/actions.js b/app/assets/javascripts/ide/stores/modules/branches/actions.js index f90c2d77f2b..c46289f77e2 100644 --- a/app/assets/javascripts/ide/stores/modules/branches/actions.js +++ b/app/assets/javascripts/ide/stores/modules/branches/actions.js @@ -32,5 +32,3 @@ export const fetchBranches = ({ dispatch, rootGetters }, { search = '' }) => { }; export const resetBranches = ({ commit }) => commit(types.RESET_BRANCHES); - -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js index 453df8d7e0c..e8a72f7bc52 100644 --- a/app/assets/javascripts/ide/stores/modules/file_templates/getters.js +++ b/app/assets/javascripts/ide/stores/modules/file_templates/getters.js @@ -23,5 +23,3 @@ export const templateTypes = () => [ export const showFileTemplatesBar = (_, getters, rootState) => name => getters.templateTypes.find(t => t.name === name) && rootState.currentActivityView === leftSidebarViews.edit.name; - -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js index 8b5f7558654..6a1a0de033e 100644 --- a/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js +++ b/app/assets/javascripts/ide/stores/modules/merge_requests/actions.js @@ -41,5 +41,3 @@ export const fetchMergeRequests = ( }; export const resetMergeRequests = ({ commit }) => commit(types.RESET_MERGE_REQUESTS); - -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js index 9862c556c2e..86b889546b0 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/actions.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/actions.js @@ -149,5 +149,3 @@ export const resetLatestPipeline = ({ commit }) => { commit(types.RECEIVE_LASTEST_PIPELINE_SUCCESS, null); commit(types.SET_DETAIL_JOB, null); }; - -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js index 1d127d915d7..eb3cc027494 100644 --- a/app/assets/javascripts/ide/stores/modules/pipelines/getters.js +++ b/app/assets/javascripts/ide/stores/modules/pipelines/getters.js @@ -20,5 +20,3 @@ export const failedJobsCount = state => ); export const jobsCount = state => state.stages.reduce((acc, stage) => acc + stage.jobs.length, 0); - -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js index 112b3794114..5c13b5d74f2 100644 --- a/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/index.js @@ -2,4 +2,3 @@ export * from './setup'; export * from './checks'; export * from './session_controls'; export * from './session_status'; -export default () => {}; diff --git a/app/assets/javascripts/ide/stores/modules/terminal/getters.js b/app/assets/javascripts/ide/stores/modules/terminal/getters.js index 6d64ee4ab6e..ef98547ccc4 100644 --- a/app/assets/javascripts/ide/stores/modules/terminal/getters.js +++ b/app/assets/javascripts/ide/stores/modules/terminal/getters.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/prefer-default-export export const allCheck = state => { const checks = Object.values(state.checks); @@ -15,5 +16,3 @@ export const allCheck = state => { message, }; }; - -export default () => {}; diff --git a/app/assets/javascripts/import_projects/store/actions.js b/app/assets/javascripts/import_projects/store/actions.js index 8d8d33f5972..fedae173af7 100644 --- a/app/assets/javascripts/import_projects/store/actions.js +++ b/app/assets/javascripts/import_projects/store/actions.js @@ -128,6 +128,3 @@ export const fetchJobs = ({ state, commit, dispatch }) => { } }); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/issuables_list/components/issuable.vue b/app/assets/javascripts/issuables_list/components/issuable.vue index b7f4292a126..f01925ca27b 100644 --- a/app/assets/javascripts/issuables_list/components/issuable.vue +++ b/app/assets/javascripts/issuables_list/components/issuable.vue @@ -85,9 +85,6 @@ export default { dueDateWords() { return this.dueDate ? dateInWords(this.dueDate, true) : undefined; }, - hasNoComments() { - return !this.userNotesCount; - }, isOverdue() { return this.dueDate ? this.dueDate < new Date() : false; }, @@ -148,32 +145,51 @@ export default { time_ago: escape(getTimeago().format(this.issuable.updated_at)), }); }, - userNotesCount() { - return this.issuable.user_notes_count; - }, issuableMeta() { return [ { key: 'merge-requests', + visible: this.issuable.merge_requests_count > 0, value: this.issuable.merge_requests_count, title: __('Related merge requests'), - class: 'js-merge-requests', + dataTestId: 'merge-requests', icon: 'merge-request', }, { key: 'upvotes', + visible: this.issuable.upvotes > 0, value: this.issuable.upvotes, title: __('Upvotes'), - class: 'js-upvotes', + dataTestId: 'upvotes', icon: 'thumb-up', }, { key: 'downvotes', + visible: this.issuable.downvotes > 0, value: this.issuable.downvotes, title: __('Downvotes'), - class: 'js-downvotes', + dataTestId: 'downvotes', icon: 'thumb-down', }, + { + key: 'blocking-issues', + visible: this.issuable.blocking_issues_count > 0, + value: this.issuable.blocking_issues_count, + title: __('Blocking issues'), + dataTestId: 'blocking-issues', + href: `${this.issuable.web_url}#related-issues`, + icon: 'issue-block', + }, + { + key: 'comments-count', + visible: !this.isJiraIssue, + value: this.issuable.user_notes_count, + title: __('Comments'), + dataTestId: 'notes-count', + href: `${this.issuable.web_url}#notes`, + class: !this.issuable.user_notes_count ? 'no-comments' : '', + icon: 'comments', + }, ]; }, }, @@ -202,6 +218,9 @@ export default { selected: ev.target.checked, }); }, + issuableMetaComponent(href) { + return href ? 'gl-link' : 'span'; + }, }, confidentialTooltipText: __('Confidential'), @@ -216,9 +235,9 @@ export default { :data-labels="labelIdsString" :data-url="issuable.web_url" > - <div class="d-flex"> + <div class="gl-display-flex"> <!-- Bulk edit checkbox --> - <div v-if="isBulkEditing" class="mr-2"> + <div v-if="isBulkEditing" class="gl-mr-3"> <input :checked="selected" class="selected-issuable" @@ -230,7 +249,7 @@ export default { <!-- Issuable info container --> <!-- Issuable main info --> - <div class="flex-grow-1"> + <div class="gl-flex-grow-1"> <div class="title"> <span class="issue-title-text"> <gl-icon @@ -251,7 +270,10 @@ export default { /> </gl-link> </span> - <span v-if="issuable.has_tasks" class="ml-1 task-status d-none d-sm-inline-block"> + <span + v-if="issuable.has_tasks" + class="gl-ml-2 task-status gl-display-none d-sm-inline-block" + > {{ issuable.task_status }} </span> </div> @@ -267,7 +289,7 @@ export default { {{ referencePath }} </span> - <span data-testid="openedByMessage" class="d-none d-sm-inline-block mr-1"> + <span data-testid="openedByMessage" class="gl-display-none d-sm-inline-block gl-mr-2"> · <gl-sprintf :message="isJiraIssue ? $options.i18n.openedAgoJira : $options.i18n.openedAgo" @@ -291,7 +313,7 @@ export default { <gl-link v-if="issuable.milestone" v-gl-tooltip - class="d-none d-sm-inline-block mr-1 js-milestone" + class="gl-display-none d-sm-inline-block gl-mr-2 js-milestone" :href="milestoneLink" :title="milestoneTooltipText" > @@ -302,7 +324,7 @@ export default { <span v-if="dueDate" v-gl-tooltip - class="d-none d-sm-inline-block mr-1 js-due-date" + class="gl-display-none d-sm-inline-block gl-mr-2 js-due-date" :class="{ cred: isOverdue }" :title="__('Due date')" > @@ -321,7 +343,7 @@ export default { :title="label.name" :scoped="isScoped(label)" size="sm" - class="mr-1" + class="gl-mr-2" >{{ label.name }}</gl-label > @@ -329,7 +351,7 @@ export default { v-if="hasWeight" v-gl-tooltip :title="__('Weight')" - class="d-none d-sm-inline-block js-weight" + class="gl-display-none d-sm-inline-block" data-testid="weight" > <gl-icon name="weight" class="align-text-bottom" /> @@ -339,43 +361,37 @@ export default { </div> <!-- Issuable meta --> - <div class="flex-shrink-0 d-flex flex-column align-items-end justify-content-center"> - <div class="controls d-flex"> + <div + class="gl-flex-shrink-0 gl-display-flex gl-flex-direction-column align-items-end gl-justify-content-center" + > + <div class="controls gl-display-flex"> <span v-if="isJiraIssue" data-testid="issuable-status">{{ issuable.status }}</span> <span v-else-if="isClosed" class="issuable-status">{{ __('CLOSED') }}</span> <issue-assignees :assignees="issuable.assignees" - class="align-items-center d-flex ml-2" + class="gl-align-items-center gl-display-flex gl-ml-3" :icon-size="16" - img-css-classes="mr-1" + img-css-classes="gl-mr-2!" :max-visible="4" /> <template v-for="meta in issuableMeta"> <span - v-if="meta.value" + v-if="meta.visible" :key="meta.key" v-gl-tooltip - :class="['d-none d-sm-inline-block ml-2 vertical-align-middle', meta.class]" + class="gl-display-none gl-display-sm-flex gl-align-items-center gl-ml-3" + :class="meta.class" + :data-testid="meta.dataTestId" :title="meta.title" > - <gl-icon v-if="meta.icon" :name="meta.icon" /> - {{ meta.value }} + <component :is="issuableMetaComponent(meta.href)" :href="meta.href"> + <gl-icon v-if="meta.icon" :name="meta.icon" /> + {{ meta.value }} + </component> </span> </template> - - <gl-link - v-if="!isJiraIssue" - v-gl-tooltip - class="ml-2 js-notes" - :href="`${issuable.web_url}#notes`" - :title="__('Comments')" - :class="{ 'no-comments': hasNoComments }" - > - <gl-icon name="comments" class="gl-vertical-align-text-bottom" /> - {{ userNotesCount }} - </gl-link> </div> <div v-gl-tooltip class="issuable-updated-at" :title="updatedDateString"> {{ updatedDateAgo }} diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js index 4bd8d6f58a6..6b664ed05cc 100644 --- a/app/assets/javascripts/jobs/store/actions.js +++ b/app/assets/javascripts/jobs/store/actions.js @@ -247,6 +247,3 @@ export const triggerManualJob = ({ state }, variables) => { }) .catch(() => flash(__('An error occurred while triggering the job.'))); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 3f02f924eed..dc4a3578a86 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -46,6 +46,3 @@ export const isScrollingDown = state => isScrolledToBottom() && !state.isTraceCo export const hasRunnersForProject = state => state.job.runners.available && !state.job.runners.online; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/logs/stores/actions.js b/app/assets/javascripts/logs/stores/actions.js index 0edd825b6e9..623516f349d 100644 --- a/app/assets/javascripts/logs/stores/actions.js +++ b/app/assets/javascripts/logs/stores/actions.js @@ -200,6 +200,3 @@ export const dismissRequestLogsError = ({ commit }) => { export const dismissInvalidTimeRangeWarning = ({ commit }) => { commit(types.HIDE_TIME_RANGE_INVALID_WARNING); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/monitoring/components/charts/heatmap.vue b/app/assets/javascripts/monitoring/components/charts/heatmap.vue index ddb44f7b1be..7003e2d37cf 100644 --- a/app/assets/javascripts/monitoring/components/charts/heatmap.vue +++ b/app/assets/javascripts/monitoring/components/charts/heatmap.vue @@ -36,7 +36,7 @@ export default { ); }, xAxisName() { - return this.graphData.x_label || ''; + return this.graphData.xLabel || ''; }, yAxisName() { return this.graphData.y_label || ''; diff --git a/app/assets/javascripts/monitoring/components/dashboard_panel.vue b/app/assets/javascripts/monitoring/components/dashboard_panel.vue index 3e3c8408de3..610bef37fdb 100644 --- a/app/assets/javascripts/monitoring/components/dashboard_panel.vue +++ b/app/assets/javascripts/monitoring/components/dashboard_panel.vue @@ -30,6 +30,7 @@ import MonitorStackedColumnChart from './charts/stacked_column.vue'; import TrackEventDirective from '~/vue_shared/directives/track_event'; import AlertWidget from './alert_widget.vue'; import { timeRangeToUrl, downloadCSVOptions, generateLinkToChartOptions } from '../utils'; +import { graphDataToCsv } from '../csv_export'; const events = { timeRangeZoom: 'timerangezoom', @@ -148,13 +149,10 @@ export default { return null; }, csvText() { - const chartData = this.graphData?.metrics[0].result[0].values || []; - const yLabel = this.graphData.y_label; - const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/require-i18n-strings - return chartData.reduce((csv, data) => { - const row = data.join(','); - return `${csv}${row}\r\n`; - }, header); + if (this.graphData) { + return graphDataToCsv(this.graphData); + } + return null; }, downloadCsv() { const data = new Blob([this.csvText], { type: 'text/plain' }); diff --git a/app/assets/javascripts/monitoring/csv_export.js b/app/assets/javascripts/monitoring/csv_export.js new file mode 100644 index 00000000000..734e8dc07a7 --- /dev/null +++ b/app/assets/javascripts/monitoring/csv_export.js @@ -0,0 +1,147 @@ +import { getSeriesLabel } from '~/helpers/monitor_helper'; + +/** + * Returns a label for a header of the csv. + * + * Includes double quotes ("") in case the header includes commas or other separator. + * + * @param {String} axisLabel + * @param {String} metricLabel + * @param {Object} metricAttributes + */ +const csvHeader = (axisLabel, metricLabel, metricAttributes = {}) => + `${axisLabel} > ${getSeriesLabel(metricLabel, metricAttributes)}`; + +/** + * Returns an array with the header labels given a list of metrics + * + * ``` + * metrics = [ + * { + * label: "..." // user-defined label + * result: [ + * { + * metric: { ... } // metricAttributes + * }, + * ... + * ] + * }, + * ... + * ] + * ``` + * + * When metrics have a `label` or `metricAttributes`, they are + * used to generate the column name. + * + * @param {String} axisLabel - Main label + * @param {Array} metrics - Metrics with results + */ +const csvMetricHeaders = (axisLabel, metrics) => + metrics.flatMap(({ label, result }) => + // The `metric` in a `result` is a map of `metricAttributes` + // contains key-values to identify the series, rename it + // here for clarity. + result.map(({ metric: metricAttributes }) => { + return csvHeader(axisLabel, label, metricAttributes); + }), + ); + +/** + * Returns a (flat) array with all the values arrays in each + * metric and series + * + * ``` + * metrics = [ + * { + * result: [ + * { + * values: [ ... ] // `values` + * }, + * ... + * ] + * }, + * ... + * ] + * ``` + * + * @param {Array} metrics - Metrics with results + */ +const csvMetricValues = metrics => + metrics.flatMap(({ result }) => result.map(res => res.values || [])); + +/** + * Returns headers and rows for csv, sorted by their timestamp. + * + * { + * headers: ["timestamp", "<col_1_name>", "col_2_name"], + * rows: [ + * [ <timestamp>, <col_1_value>, <col_2_value> ], + * [ <timestamp>, <col_1_value>, <col_2_value> ] + * ... + * ] + * } + * + * @param {Array} metricHeaders + * @param {Array} metricValues + */ +const csvData = (metricHeaders, metricValues) => { + const rowsByTimestamp = {}; + + metricValues.forEach((values, colIndex) => { + values.forEach(([timestamp, value]) => { + if (!rowsByTimestamp[timestamp]) { + rowsByTimestamp[timestamp] = []; + } + // `value` should be in the right column + rowsByTimestamp[timestamp][colIndex] = value; + }); + }); + + const rows = Object.keys(rowsByTimestamp) + .sort() + .map(timestamp => { + // force each row to have the same number of entries + rowsByTimestamp[timestamp].length = metricHeaders.length; + // add timestamp as the first entry + return [timestamp, ...rowsByTimestamp[timestamp]]; + }); + + // Escape double quotes and enclose headers: + // "If double-quotes are used to enclose fields, then a double-quote + // appearing inside a field must be escaped by preceding it with + // another double quote." + // https://tools.ietf.org/html/rfc4180#page-2 + const headers = metricHeaders.map(header => `"${header.replace(/"/g, '""')}"`); + + return { + headers: ['timestamp', ...headers], + rows, + }; +}; + +/** + * Returns dashboard panel's data in a string in CSV format + * + * @param {Object} graphData - Panel contents + * @returns {String} + */ +// eslint-disable-next-line import/prefer-default-export +export const graphDataToCsv = graphData => { + const delimiter = ','; + const br = '\r\n'; + const { metrics = [], y_label: axisLabel } = graphData; + + const metricsWithResults = metrics.filter(metric => metric.result); + const metricHeaders = csvMetricHeaders(axisLabel, metricsWithResults); + const metricValues = csvMetricValues(metricsWithResults); + const { headers, rows } = csvData(metricHeaders, metricValues); + + if (rows.length === 0) { + return ''; + } + + const headerLine = headers.join(delimiter) + br; + const lines = rows.map(row => row.join(delimiter)); + + return headerLine + lines.join(br) + br; +}; diff --git a/app/assets/javascripts/monitoring/stores/embed_group/actions.js b/app/assets/javascripts/monitoring/stores/embed_group/actions.js index cbe0950d954..4a7572bdbd9 100644 --- a/app/assets/javascripts/monitoring/stores/embed_group/actions.js +++ b/app/assets/javascripts/monitoring/stores/embed_group/actions.js @@ -1,5 +1,4 @@ import * as types from './mutation_types'; +// eslint-disable-next-line import/prefer-default-export export const addModule = ({ commit }, data) => commit(types.ADD_MODULE, data); - -export default () => {}; diff --git a/app/assets/javascripts/monitoring/stores/embed_group/getters.js b/app/assets/javascripts/monitoring/stores/embed_group/getters.js index 9b08cf762c1..096d8d03096 100644 --- a/app/assets/javascripts/monitoring/stores/embed_group/getters.js +++ b/app/assets/javascripts/monitoring/stores/embed_group/getters.js @@ -1,4 +1,3 @@ +// eslint-disable-next-line import/prefer-default-export export const metricsWithData = (state, getters, rootState, rootGetters) => state.modules.map(module => rootGetters[`${module}/metricsWithData`]().length); - -export default () => {}; diff --git a/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js b/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js index e7a425d3623..7fd3f0f8647 100644 --- a/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js +++ b/app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js @@ -1,3 +1,2 @@ +// eslint-disable-next-line import/prefer-default-export export const ADD_MODULE = 'ADD_MODULE'; - -export default () => {}; diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js index 5b2ab255557..a5c60e9c767 100644 --- a/app/assets/javascripts/notes/stores/actions.js +++ b/app/assets/javascripts/notes/stores/actions.js @@ -682,6 +682,3 @@ export const receiveDeleteDescriptionVersionError = ({ commit }, error) => { export const updateAssignees = ({ commit }, assignees) => { commit(types.UPDATE_ASSIGNEES, assignees); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index 85997b44bcc..ed0e956533f 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -229,6 +229,3 @@ export const getDiscussion = state => discussionId => state.discussions.find(discussion => discussion.id === discussionId); export const commentsDisabled = state => state.commentsDisabled; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/operation_settings/store/actions.js b/app/assets/javascripts/operation_settings/store/actions.js index 122acb6bdcf..fff4af3ec10 100644 --- a/app/assets/javascripts/operation_settings/store/actions.js +++ b/app/assets/javascripts/operation_settings/store/actions.js @@ -37,6 +37,3 @@ export const receiveSaveChangesError = (_, error) => { createFlash(`${__('There was an error saving your changes.')} ${message}`, 'alert'); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/actions.js b/app/assets/javascripts/pipelines/stores/test_reports/actions.js index ccacb9f7e97..cb79a1a6ca5 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/actions.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/actions.js @@ -52,6 +52,3 @@ export const setSelectedSuiteIndex = ({ commit }, data) => export const removeSelectedSuiteIndex = ({ commit }) => commit(types.SET_SELECTED_SUITE_INDEX, null); export const toggleLoading = ({ commit }) => commit(types.TOGGLE_LOADING); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/pipelines/stores/test_reports/getters.js b/app/assets/javascripts/pipelines/stores/test_reports/getters.js index 877762b77c9..6c670806cc4 100644 --- a/app/assets/javascripts/pipelines/stores/test_reports/getters.js +++ b/app/assets/javascripts/pipelines/stores/test_reports/getters.js @@ -16,6 +16,3 @@ export const getSuiteTests = state => { const { test_cases: testCases = [] } = getSelectedSuite(state); return testCases.sort(sortTestCases).map(addIconStatus); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/pipelines/utils.js b/app/assets/javascripts/pipelines/utils.js index 9dbc8073d3a..2e08001f6b3 100644 --- a/app/assets/javascripts/pipelines/utils.js +++ b/app/assets/javascripts/pipelines/utils.js @@ -1,8 +1,7 @@ import { pickBy } from 'lodash'; import { SUPPORTED_FILTER_PARAMETERS } from './constants'; +// eslint-disable-next-line import/prefer-default-export export const validateParams = params => { return pickBy(params, (val, key) => SUPPORTED_FILTER_PARAMETERS.includes(key) && val); }; - -export default () => {}; diff --git a/app/assets/javascripts/registry/explorer/stores/actions.js b/app/assets/javascripts/registry/explorer/stores/actions.js index 3d73ffbd23f..f3294d8ea9c 100644 --- a/app/assets/javascripts/registry/explorer/stores/actions.js +++ b/app/assets/javascripts/registry/explorer/stores/actions.js @@ -102,5 +102,3 @@ export const requestDeleteImage = ({ commit }, image) => { commit(types.SET_MAIN_LOADING, false); }); }; - -export default () => {}; diff --git a/app/assets/javascripts/registry/settings/store/actions.js b/app/assets/javascripts/registry/settings/store/actions.js index be1f62334fa..0530a870ecc 100644 --- a/app/assets/javascripts/registry/settings/store/actions.js +++ b/app/assets/javascripts/registry/settings/store/actions.js @@ -28,6 +28,3 @@ export const saveSettings = ({ dispatch, state }) => { ) .finally(() => dispatch('toggleLoading')); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/related_merge_requests/store/actions.js b/app/assets/javascripts/related_merge_requests/store/actions.js index 69abeaaf7db..0d3d8d253ec 100644 --- a/app/assets/javascripts/related_merge_requests/store/actions.js +++ b/app/assets/javascripts/related_merge_requests/store/actions.js @@ -32,6 +32,3 @@ export const fetchMergeRequests = ({ state, dispatch }) => { createFlash(s__('Something went wrong while fetching related merge requests.')); }); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/releases/stores/modules/list/actions.js b/app/assets/javascripts/releases/stores/modules/list/actions.js index 06d13890a9d..e5296299ed5 100644 --- a/app/assets/javascripts/releases/stores/modules/list/actions.js +++ b/app/assets/javascripts/releases/stores/modules/list/actions.js @@ -43,6 +43,3 @@ export const receiveReleasesError = ({ commit }) => { commit(types.RECEIVE_RELEASES_ERROR); createFlash(__('An error occurred while fetching the releases. Please try again.')); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/reports/accessibility_report/store/actions.js b/app/assets/javascripts/reports/accessibility_report/store/actions.js index 446cfd79984..bb502020a06 100644 --- a/app/assets/javascripts/reports/accessibility_report/store/actions.js +++ b/app/assets/javascripts/reports/accessibility_report/store/actions.js @@ -74,6 +74,3 @@ export const receiveReportError = ({ commit, dispatch }) => { commit(types.RECEIVE_REPORT_ERROR); dispatch('stopPolling'); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/reports/accessibility_report/store/getters.js b/app/assets/javascripts/reports/accessibility_report/store/getters.js index 9aff427e644..312b333a771 100644 --- a/app/assets/javascripts/reports/accessibility_report/store/getters.js +++ b/app/assets/javascripts/reports/accessibility_report/store/getters.js @@ -43,6 +43,3 @@ export const shouldRenderIssuesList = state => export const unresolvedIssues = state => state.report.existing_errors; export const resolvedIssues = state => state.report.resolved_errors; export const newIssues = state => state.report.new_errors; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/reports/store/actions.js b/app/assets/javascripts/reports/store/actions.js index db8ab5ccb80..c5860db6601 100644 --- a/app/assets/javascripts/reports/store/actions.js +++ b/app/assets/javascripts/reports/store/actions.js @@ -85,6 +85,3 @@ export const openModal = ({ dispatch }, payload) => { }; export const setModalData = ({ commit }, payload) => commit(types.SET_ISSUE_MODAL_DATA, payload); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/reports/store/getters.js b/app/assets/javascripts/reports/store/getters.js index 95266194acb..6345be69f6f 100644 --- a/app/assets/javascripts/reports/store/getters.js +++ b/app/assets/javascripts/reports/store/getters.js @@ -1,5 +1,6 @@ import { LOADING, ERROR, SUCCESS, STATUS_FAILED } from '../constants'; +// eslint-disable-next-line import/prefer-default-export export const summaryStatus = state => { if (state.isLoading) { return LOADING; @@ -11,6 +12,3 @@ export const summaryStatus = state => { return SUCCESS; }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/serverless/store/actions.js b/app/assets/javascripts/serverless/store/actions.js index a0a9fdf7ace..2e07e57d4d5 100644 --- a/app/assets/javascripts/serverless/store/actions.js +++ b/app/assets/javascripts/serverless/store/actions.js @@ -123,6 +123,3 @@ export const fetchMetrics = ({ dispatch }, { metricsPath, hasPrometheus }) => { createFlash(error); }); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/serverless/store/getters.js b/app/assets/javascripts/serverless/store/getters.js index 071f663d9d2..c9b1d22799a 100644 --- a/app/assets/javascripts/serverless/store/getters.js +++ b/app/assets/javascripts/serverless/store/getters.js @@ -5,6 +5,3 @@ export const hasPrometheusMissingData = state => state.hasPrometheus && !state.h // Convert the function list into a k/v grouping based on the environment scope export const getFunctions = state => translate(state.functions); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/serverless/utils.js b/app/assets/javascripts/serverless/utils.js index 8b9e96ce9aa..1bf03ea8d42 100644 --- a/app/assets/javascripts/serverless/utils.js +++ b/app/assets/javascripts/serverless/utils.js @@ -18,6 +18,3 @@ export const translate = functions => }), {}, ); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/snippets/index.js b/app/assets/javascripts/snippets/index.js index 1c79492957d..99bf1ed0350 100644 --- a/app/assets/javascripts/snippets/index.js +++ b/app/assets/javascripts/snippets/index.js @@ -38,5 +38,3 @@ export const SnippetShowInit = () => { export const SnippetEditInit = () => { appFactory(document.getElementById('js-snippet-edit'), SnippetsEdit); }; - -export default () => {}; diff --git a/app/assets/javascripts/snippets/mixins/snippets.js b/app/assets/javascripts/snippets/mixins/snippets.js index 91331cdf339..3f5d64a768f 100644 --- a/app/assets/javascripts/snippets/mixins/snippets.js +++ b/app/assets/javascripts/snippets/mixins/snippets.js @@ -2,6 +2,7 @@ import GetSnippetQuery from '../queries/snippet.query.graphql'; const blobsDefault = []; +// eslint-disable-next-line import/prefer-default-export export const getSnippetMixin = { apollo: { snippet: { @@ -39,5 +40,3 @@ export const getSnippetMixin = { }, }, }; - -export default () => {}; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js index 3648db795f5..419793977f0 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js @@ -69,6 +69,3 @@ export const receiveArtifactsSuccess = ({ commit }, response) => { }; export const receiveArtifactsError = ({ commit }) => commit(types.RECEIVE_ARTIFACTS_ERROR); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js index 8921637b93b..5215d210e1c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js @@ -1,5 +1,6 @@ import { s__, n__ } from '~/locale'; +// eslint-disable-next-line import/prefer-default-export export const title = state => { if (state.isLoading) { return s__('BuildArtifacts|Loading artifacts'); @@ -11,6 +12,3 @@ export const title = state => { return n__('View exposed artifact', 'View %d exposed artifacts', state.artifacts.length); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js index e6053628eca..52be558cf7f 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js @@ -56,6 +56,3 @@ export const createLabel = ({ state, dispatch }, label) => { export const updateSelectedLabels = ({ commit }, labels) => commit(types.UPDATE_SELECTED_LABELS, { labels }); - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js index e035a866048..5a30e29cad3 100644 --- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js @@ -50,6 +50,3 @@ export const isDropdownVariantStandalone = state => state.variant === DropdownVa * @param {object} state */ export const isDropdownVariantEmbedded = state => state.variant === DropdownVariant.Embedded; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/javascripts/vuex_shared/bindings.js b/app/assets/javascripts/vuex_shared/bindings.js index 817a90f8149..edc31cfa69e 100644 --- a/app/assets/javascripts/vuex_shared/bindings.js +++ b/app/assets/javascripts/vuex_shared/bindings.js @@ -9,6 +9,7 @@ * @param {string} root - the key of the state where to search fo they keys described in list * @returns {Object} a dictionary with all the computed properties generated */ +// eslint-disable-next-line import/prefer-default-export export const mapComputed = (list, defaultUpdateFn, root) => { const result = {}; list.forEach(item => { @@ -32,5 +33,3 @@ export const mapComputed = (list, defaultUpdateFn, root) => { }); return result; }; - -export default () => {}; diff --git a/app/assets/javascripts/vuex_shared/modules/modal/actions.js b/app/assets/javascripts/vuex_shared/modules/modal/actions.js index 7b209909f69..552237e05c5 100644 --- a/app/assets/javascripts/vuex_shared/modules/modal/actions.js +++ b/app/assets/javascripts/vuex_shared/modules/modal/actions.js @@ -15,6 +15,3 @@ export const show = ({ commit }) => { export const hide = ({ commit }) => { commit(types.HIDE); }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/app/assets/stylesheets/components/design_management/design_list_item.scss b/app/assets/stylesheets/components/design_management/design_list_item.scss index b7f6b2026fe..3a6781b666e 100644 --- a/app/assets/stylesheets/components/design_management/design_list_item.scss +++ b/app/assets/stylesheets/components/design_management/design_list_item.scss @@ -1,3 +1,12 @@ +.designs-root { + border: 2px dashed transparent; + transition: border $gl-transition-duration-medium $general-hover-transition-curve; + + &:hover { + border-color: $gray-100; + } +} + .design-list-item { height: 280px; text-decoration: none; diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index 5b12bd000d8..781b850ddfe 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -45,7 +45,7 @@ module Projects if result[:status] == :success pagerduty_token = project.incident_management_setting&.pagerduty_token - webhook_url = project_incidents_pagerduty_url(project, token: pagerduty_token) + webhook_url = project_incidents_integrations_pagerduty_url(project, token: pagerduty_token) render json: { pagerduty_webhook_url: webhook_url, pagerduty_token: pagerduty_token } else diff --git a/app/helpers/operations_helper.rb b/app/helpers/operations_helper.rb index 3444773fe88..37e91153710 100644 --- a/app/helpers/operations_helper.rb +++ b/app/helpers/operations_helper.rb @@ -45,7 +45,7 @@ module OperationsHelper send_email: setting.send_email.to_s, pagerduty_active: setting.pagerduty_active.to_s, pagerduty_token: setting.pagerduty_token.to_s, - pagerduty_webhook_url: project_incidents_pagerduty_url(@project, token: setting.pagerduty_token), + pagerduty_webhook_url: project_incidents_integrations_pagerduty_url(@project, token: setting.pagerduty_token), pagerduty_reset_key_path: reset_pagerduty_token_project_settings_operations_path(@project) } end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 4607f291a26..03d06117e83 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -9,7 +9,7 @@ class ApplicationSetting < ApplicationRecord GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \ 'Admin Area > Settings > Metrics and profiling > Metrics - Grafana' - add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required } + add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required } add_authentication_token_field :health_check_access_token add_authentication_token_field :static_objects_external_storage_auth_token diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6c90645e997..733f28a2944 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -351,7 +351,7 @@ module Ci after_transition any => [:failed] do |build| next unless build.project - if build.retry_failure? + if build.auto_retry_allowed? begin Ci::Build.retry(build, build.user) rescue Gitlab::Access::AccessDeniedError => ex @@ -373,6 +373,10 @@ module Ci end end + def auto_retry_allowed? + auto_retry.allowed? + end + def detailed_status(current_user) Gitlab::Ci::Status::Build::Factory .new(self, current_user) @@ -439,27 +443,6 @@ module Ci pipeline.builds.retried.where(name: self.name).count end - def retry_failure? - max_allowed_retries = nil - max_allowed_retries ||= options_retry_max if retry_on_reason_or_always? - max_allowed_retries ||= DEFAULT_RETRIES.fetch(failure_reason.to_sym, 0) - - max_allowed_retries > 0 && retries_count < max_allowed_retries - end - - def options_retry_max - options_retry[:max] - end - - def options_retry_when - options_retry.fetch(:when, ['always']) - end - - def retry_on_reason_or_always? - options_retry_when.include?(failure_reason.to_s) || - options_retry_when.include?('always') - end - def any_unmet_prerequisites? prerequisites.present? end @@ -962,6 +945,12 @@ module Ci private + def auto_retry + strong_memoize(:auto_retry) do + Gitlab::Ci::Build::AutoRetry.new(self) + end + end + def dependencies strong_memoize(:dependencies) do Ci::BuildDependencies.new(self) @@ -1017,19 +1006,6 @@ module Ci end end - # The format of the retry option changed in GitLab 11.5: Before it was - # integer only, after it is a hash. New builds are created with the new - # format, but builds created before GitLab 11.5 and saved in database still - # have the old integer only format. This method returns the retry option - # normalized as a hash in 11.5+ format. - def options_retry - strong_memoize(:options_retry) do - value = options&.dig(:retry) - value = value.is_a?(Integer) ? { max: value } : value.to_h - value.with_indifferent_access - end - end - def has_expiring_artifacts? artifacts_expire_at.present? && artifacts_expire_at > Time.current end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 1cd6c64841b..00ee45740bd 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -10,7 +10,7 @@ module Ci include TokenAuthenticatable include IgnorableColumns - add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption, default_enabled: true) ? :optional : :required } + add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption) ? :optional : :required } enum access_level: { not_protected: 0, diff --git a/app/models/concerns/approvable_base.rb b/app/models/concerns/approvable_base.rb index 6323bd01c58..d07c4ec43ac 100644 --- a/app/models/concerns/approvable_base.rb +++ b/app/models/concerns/approvable_base.rb @@ -13,4 +13,8 @@ module ApprovableBase approved_by_users.include?(user) end + + def can_be_approved_by?(user) + user && !approved_by?(user) && user.can?(:approve_merge_request, self) + end end diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb index 94f3a140098..c027a0d140e 100644 --- a/app/models/suggestion.rb +++ b/app/models/suggestion.rb @@ -43,12 +43,12 @@ class Suggestion < ApplicationRecord def inapplicable_reason(cached: true) strong_memoize("inapplicable_reason_#{cached}") do - next :applied if applied? - next :merge_request_merged if noteable.merged? - next :merge_request_closed if noteable.closed? - next :source_branch_deleted unless noteable.source_branch_exists? - next :outdated if outdated?(cached: cached) || !note.active? - next :same_content unless different_content? + next _("Can't apply this suggestion.") if applied? + next _("This merge request was merged. To apply this suggestion, edit this file directly.") if noteable.merged? + next _("This merge request is closed. To apply this suggestion, edit this file directly.") if noteable.closed? + next _("Can't apply as the source branch was deleted.") unless noteable.source_branch_exists? + next outdated_reason if outdated?(cached: cached) || !note.active? + next _("This suggestion already matches its content.") unless different_content? end end @@ -73,4 +73,12 @@ class Suggestion < ApplicationRecord def different_content? from_content != to_content end + + def outdated_reason + if single_line? + _("Can't apply as this line was changed in a more recent version.") + else + _("Can't apply as these lines were changed in a more recent version.") + end + end end diff --git a/app/serializers/suggestion_entity.rb b/app/serializers/suggestion_entity.rb index c9fcbe14f2e..c224d0b4390 100644 --- a/app/serializers/suggestion_entity.rb +++ b/app/serializers/suggestion_entity.rb @@ -16,24 +16,8 @@ class SuggestionEntity < API::Entities::Suggestion expose :inapplicable_reason do |suggestion| next _("You don't have write access to the source branch.") unless can_apply?(suggestion) - next if suggestion.appliable? - case suggestion.inapplicable_reason - when :merge_request_merged - _("This merge request was merged. To apply this suggestion, edit this file directly.") - when :merge_request_closed - _("This merge request is closed. To apply this suggestion, edit this file directly.") - when :source_branch_deleted - _("Can't apply as the source branch was deleted.") - when :outdated - phrase = suggestion.single_line? ? 'this line was' : 'these lines were' - - _("Can't apply as %{phrase} changed in a more recent version.") % { phrase: phrase } - when :same_content - _("This suggestion already matches its content.") - else - _("Can't apply this suggestion.") - end + suggestion.inapplicable_reason end private diff --git a/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml b/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml index ecbf6d9005d..9ec1d7d0d67 100644 --- a/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml +++ b/app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml @@ -8,4 +8,4 @@ - viewer.errors.messages.each do |error| %li= error.join(': ') -= link_to _('Learn more'), help_page_path('operations/metrics/dashboards/index.md', anchor: 'defining-custom-dashboards-per-project') += link_to _('Learn more'), help_page_path('operations/metrics/dashboards/index.md') diff --git a/changelogs/unreleased/214382-remove-application_settings_tokens_optional_encryption-feature-fla.yml b/changelogs/unreleased/214382-remove-application_settings_tokens_optional_encryption-feature-fla.yml new file mode 100644 index 00000000000..bbf4ab110e6 --- /dev/null +++ b/changelogs/unreleased/214382-remove-application_settings_tokens_optional_encryption-feature-fla.yml @@ -0,0 +1,5 @@ +--- +title: Disable application_settings_tokens_optional_encryption feature flag. +merge_request: 31798 +author: Gilang Gumilar +type: changed diff --git a/changelogs/unreleased/214382-remove-ci_runners_tokens_optional_encryption-feature-flag.yml b/changelogs/unreleased/214382-remove-ci_runners_tokens_optional_encryption-feature-flag.yml new file mode 100644 index 00000000000..6c2951fa70a --- /dev/null +++ b/changelogs/unreleased/214382-remove-ci_runners_tokens_optional_encryption-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Disable ci_runners_tokens_optional_encryption feature flag. +merge_request: 31800 +author: Gilang Gumilar +type: changed diff --git a/changelogs/unreleased/214627-fix-incorrect-csv-export.yml b/changelogs/unreleased/214627-fix-incorrect-csv-export.yml new file mode 100644 index 00000000000..e69a392f4ac --- /dev/null +++ b/changelogs/unreleased/214627-fix-incorrect-csv-export.yml @@ -0,0 +1,5 @@ +--- +title: Fix CSV downloads for multiple series in the same chart +merge_request: 36556 +author: +type: fixed diff --git a/changelogs/unreleased/229588-pasting-an-image-into-a-comment-also-uploads-design.yml b/changelogs/unreleased/229588-pasting-an-image-into-a-comment-also-uploads-design.yml new file mode 100644 index 00000000000..279f0386f7f --- /dev/null +++ b/changelogs/unreleased/229588-pasting-an-image-into-a-comment-also-uploads-design.yml @@ -0,0 +1,5 @@ +--- +title: Resolve Pasting an image into a comment also uploads design +merge_request: 37171 +author: +type: fixed diff --git a/changelogs/unreleased/230459-change-pagerduty-webhook-url.yml b/changelogs/unreleased/230459-change-pagerduty-webhook-url.yml new file mode 100644 index 00000000000..e8a0a764b74 --- /dev/null +++ b/changelogs/unreleased/230459-change-pagerduty-webhook-url.yml @@ -0,0 +1,5 @@ +--- +title: Change PagerDuty webhook URL +merge_request: 37321 +author: +type: changed diff --git a/changelogs/unreleased/28167-dockerfile-template-rust.yml b/changelogs/unreleased/28167-dockerfile-template-rust.yml new file mode 100644 index 00000000000..1bf02b444e5 --- /dev/null +++ b/changelogs/unreleased/28167-dockerfile-template-rust.yml @@ -0,0 +1,5 @@ +--- +title: "Add Rust Dockerfile to GitLab templates" +merge_request: 28167 +author: +type: added diff --git a/changelogs/unreleased/id-fix-approvals-for-ee-without-license.yml b/changelogs/unreleased/id-fix-approvals-for-ee-without-license.yml new file mode 100644 index 00000000000..4d8d0158e65 --- /dev/null +++ b/changelogs/unreleased/id-fix-approvals-for-ee-without-license.yml @@ -0,0 +1,5 @@ +--- +title: Fix merge request approvals for EE without a license +merge_request: 37246 +author: +type: fixed diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index ee75ffbb8e9..e8110e21766 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -53,7 +53,7 @@ en: errors: messages: already_confirmed: "was already confirmed, please try signing in" - confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one below" expired: "has expired, please request a new one" not_found: "not found" not_locked: "was not locked" diff --git a/config/routes/project.rb b/config/routes/project.rb index 3bd72dbf87c..dd94e6155a0 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -291,6 +291,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get 'details', on: :member end + post 'incidents/integrations/pagerduty', to: 'incident_management/pager_duty_incidents#create' + namespace :error_tracking do resources :projects, only: :index end @@ -407,8 +409,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do post 'alerts/notify', to: 'alerting/notifications#create' - post 'incidents/pagerduty', to: 'incident_management/pager_duty_incidents#create' - draw :legacy_builds resources :hooks, only: [:index, :create, :edit, :update, :destroy], constraints: { id: /\d+/ } do # rubocop: disable Cop/PutProjectRoutesUnderScope diff --git a/doc/administration/geo/replication/database.md b/doc/administration/geo/replication/database.md index 02f51e79907..886a4af05f2 100644 --- a/doc/administration/geo/replication/database.md +++ b/doc/administration/geo/replication/database.md @@ -167,6 +167,11 @@ There is an [issue where support is being discussed](https://gitlab.com/gitlab-o corresponding to the given address. See [the PostgreSQL documentation](https://www.postgresql.org/docs/11/runtime-config-connection.html) for more details. + NOTE: **Note:** + If you need to use `0.0.0.0` or `*` as the listen_address, you will also need to add + `127.0.0.1/32` to the `postgresql['md5_auth_cidr_addresses']` setting, to allow Rails to connect through + `127.0.0.1`. For more information, see [omnibus-5258](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/5258). + Depending on your network configuration, the suggested addresses may not be correct. If your **primary** node and **secondary** nodes connect over a local area network, or a virtual network connecting availability zones like diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md index 44ba26296b9..636301898a2 100644 --- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md +++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md @@ -58,7 +58,7 @@ The dashboard uses metrics available in ![GitLab self monitoring default dashboard](img/self_monitoring_default_dashboard.png) You can also -[create your own dashboards](../../../operations/metrics/dashboards/index.md#defining-custom-dashboards-per-project). +[create your own dashboards](../../../operations/metrics/dashboards/index.md). ## Connection to Prometheus @@ -83,8 +83,8 @@ Once the webhook is setup, you can You can add custom metrics in the self monitoring project by: -1. [Duplicating](../../../operations/metrics/dashboards/index.md#duplicating-a-gitlab-defined-dashboard) the default dashboard. -1. [Editing](../../../operations/metrics/dashboards/index.md#view-and-edit-the-source-file-of-a-custom-dashboard) the newly created dashboard file and configuring it with [dashboard YAML properties](../../../operations/metrics/dashboards/yaml.md). +1. [Duplicating](../../../operations/metrics/dashboards/index.md#duplicate-a-gitlab-defined-dashboard) the default dashboard. +1. [Editing](../../../operations/metrics/index.md) the newly created dashboard file and configuring it with [dashboard YAML properties](../../../operations/metrics/dashboards/yaml.md). ## Troubleshooting diff --git a/doc/api/issues.md b/doc/api/issues.md index 22f5d994f85..9bb47d6dc09 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -755,11 +755,8 @@ the `weight` parameter: ## Rate limits -To help avoid abuse, users are limited to: - -| Request Type | Limit | -| ---------------- | --------------------------- | -| Create | 300 issues per minute | +To help avoid abuse, users can be limited to a specific number of `Create` requests per minute. +See [Issues rate limits](../user/admin_area/settings/rate_limit_on_issues_creation.md). ## Edit issue diff --git a/doc/api/settings.md b/doc/api/settings.md index ff4ee91d858..1effe58e6d0 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -371,6 +371,6 @@ are listed in the descriptions of the relevant settings. | `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. | | `web_ide_clientside_preview_enabled` | boolean | no | Live Preview (allow live previews of JavaScript projects in the Web IDE using CodeSandbox Live Preview). | | `snippet_size_limit` | integer | no | Max snippet content size in **bytes**. Default: 52428800 Bytes (50MB).| -| `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Default: 300. To disable throttling set to 0.| +| `issues_create_limit` | integer | no | Max number of issue creation requests per minute per user. Disabled by default.| | `raw_blob_request_limit` | integer | no | Max number of requests per minute for each raw path. Default: 300. To disable throttling set to 0.| | `wiki_page_max_content_bytes` | integer | no | Max wiki page content size in **bytes**. Default: 52428800 Bytes (50MB).| diff --git a/doc/api/templates/dockerfiles.md b/doc/api/templates/dockerfiles.md index a2c21ada05b..2fa3d7b7fa1 100644 --- a/doc/api/templates/dockerfiles.md +++ b/doc/api/templates/dockerfiles.md @@ -93,6 +93,10 @@ Example response: "name": "Ruby-alpine" }, { + "key": "Rust", + "name": "Rust" + }, + { "key": "Swift", "name": "Swift" } diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index 336c9b8ca35..af9c56df266 100644 --- a/doc/development/fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md @@ -23,6 +23,14 @@ Classes in [`utilities.scss`](https://gitlab.com/gitlab-org/gitlab/blob/master/a Avoid [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/). +NOTE: **Note:** +While migrating [Bootstrap's Utility Classes](https://getbootstrap.com/docs/4.3/utilities/) +to the [GitLab UI](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/master/doc/css.md#utilities) +utility classes, note both the classes for margin and padding differ. The size scale used at +GitLab differs from the scale used in the Bootstrap library. For a Bootstrap padding or margin +utility, you may need to double the size of the applied utility to achieve the same visual +result (such as `ml-1` becoming `gl-ml-2`). + #### Where should I put new utility classes? If a class you need has not been added to GitLab UI, you get to add it! Follow the naming patterns documented in the [utility files](https://gitlab.com/gitlab-org/gitlab-ui/-/tree/master/src/scss/utility-mixins) and refer to [GitLab UI's CSS documentation](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/master/doc/contributing/adding_css.md#adding-utility-mixins) for more details, especially about adding responsive and stateful rules. diff --git a/doc/development/geo/framework.md b/doc/development/geo/framework.md index de4d6fb869d..5c9b01466d6 100644 --- a/doc/development/geo/framework.md +++ b/doc/development/geo/framework.md @@ -180,6 +180,11 @@ For example, to add support for files referenced by a `Widget` model with a mount_uploader :file, WidgetUploader + def local? + # Must to be implemented, Check the uploader's storage types + file_store == ObjectStorage::Store::LOCAL + end + def self.replicables_for_geo_node # Should be implemented. The idea of the method is to restrict # the set of synced items depending on synchronization settings @@ -227,7 +232,7 @@ For example, to add support for files referenced by a `Widget` model with a ``` 1. Create the `widget_registry` table so Geo secondaries can track the sync and - verification state of each Widget's file: + verification state of each Widget's file. This migration belongs in `ee/db/geo/migrate`: ```ruby # frozen_string_literal: true @@ -286,6 +291,8 @@ For example, to add support for files referenced by a `Widget` model with a 1. Update `REGISTRY_CLASSES` in `ee/app/workers/geo/secondary/registry_consistency_worker.rb`. +1. Add `widget_registry` to `ActiveSupport::Inflector.inflections` in `config/initializers_before_autoloader/000_inflections.rb`. + 1. Create `ee/spec/factories/geo/widget_registry.rb`: ```ruby diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index ef9fd748dbb..5907a3592c9 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -771,13 +771,37 @@ yarn karma -f 'spec/javascripts/ide/**/file_spec.js' ## Frontend test fixtures -Code that is added to HAML templates (in `app/views/`) or makes Ajax requests to the backend has tests that require HTML or JSON from the backend. -Fixtures for these tests are located at: +Frontend fixtures are files containing responses from backend controllers. These responses can be either HTML +generated from haml templates or JSON payloads. Frontend tests that rely on these responses are +often using fixtures to validate correct integration with the backend code. + +### Generate fixtures + +You can find code to generate test fixtures in: - `spec/frontend/fixtures/`, for running tests in CE. - `ee/spec/frontend/fixtures/`, for running tests in EE. -Fixture files in: +You can generate fixtures by running: + +- `bin/rake frontend:fixtures` to generate all fixtures +- `bin/rspec spec/frontend/fixtures/merge_requests.rb` to generate specific fixtures (in this case for `merge_request.rb`) + +You can find generated fixtures are in `tmp/tests/frontend/fixtures-ee`. + +#### Creating new fixtures + +For each fixture, you can find the content of the `response` variable in the output file. +For example, test named `"merge_requests/diff_discussion.json"` in `spec/frontend/fixtures/merge_requests.rb` +will produce output file `tmp/tests/frontend/fixtures-ee/merge_requests/diff_discussion.json`. +The `response` variable gets automatically set if the test is marked as `type: :request` or `type: :controller`. + +When creating a new fixture, it often makes sense to take a look at the corresponding tests for the +endpoint in `(ee/)spec/controllers/` or `(ee/)spec/requests/`. + +### Use fixtures + +Jest and Karma test suites import fixtures in different ways: - The Karma test suite are served by [jasmine-jquery](https://github.com/velesin/jasmine-jquery). - Jest use `spec/frontend/helpers/fixtures.js`. @@ -803,14 +827,6 @@ it('uses some HTML element', () => { }); ``` -HTML and JSON fixtures are generated from backend views and controllers using RSpec (see `spec/frontend/fixtures/*.rb`). - -For each fixture, the content of the `response` variable is stored in the output file. -This variable gets automatically set if the test is marked as `type: :request` or `type: :controller`. -Fixtures are regenerated using the `bin/rake frontend:fixtures` command but you can also generate them individually, -for example `bin/rspec spec/frontend/fixtures/merge_requests.rb`. -When creating a new fixture, it often makes sense to take a look at the corresponding tests for the endpoint in `(ee/)spec/controllers/` or `(ee/)spec/requests/`. - ## Data-driven tests Similar to [RSpec's parameterized tests](best_practices.md#table-based--parameterized-tests), diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md index a46abdee2dc..e3b32f4337a 100644 --- a/doc/operations/metrics/dashboards/index.md +++ b/doc/operations/metrics/dashboards/index.md @@ -4,61 +4,129 @@ group: APM info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers --- -# Using the Metrics Dashboard +# Custom dashboards -## Manage the metrics dashboard settings +> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1. -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2. +By default, all projects include a GitLab-defined Prometheus dashboard, which +includes a few key metrics, but you can also define your own custom dashboards. -To manage the settings for your metrics dashboard: +You may create a [new dashboard from scratch](#add-a-new-dashboard-to-your-project) +or [duplicate a GitLab-defined Prometheus dashboard](#duplicate-a-gitlab-defined-dashboard). -1. Sign in as a user with project Maintainer or Admin +NOTE: **Note:** +The metrics as defined below do not support alerts, unlike +[custom metrics](../index.md#adding-custom-metrics). + +## Add a new dashboard to your project + +> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2. + +You can configure a custom dashboard by adding a new YAML file into your project's +`.gitlab/dashboards/` directory. For the dashboard to display on your project's +**{cloud-gear}** **Operations > Metrics** page, the files must have a `.yml` +extension and be present in your project's **default** branch. + +To create a new dashboard from the GitLab user interface: + +1. Sign in to GitLab as a user with Maintainer or Owner [permissions](../../../user/permissions.md#project-members-permissions). 1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**. -1. In the top-right corner of your dashboard, click **{settings}** **Metrics Settings**: +1. In the top-right corner of your dashboard, click the **{file-addition-solid}** **Actions** menu, + and select **Create new**: + ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png) +1. In the modal window, click **Open Repository**, then follow the instructions + for creating a new dashboard from the command line. - ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/metrics_settings_button_v13_2.png) +To create a new dashboard from the command line: -## Chart Context Menu +1. Create `.gitlab/dashboards/prom_alerts.yml` under your repository's root + directory. Each YAML file should define the layout of the dashboard and the + Prometheus queries used to populate data. This example dashboard displays a + single area chart: + + ```yaml + dashboard: 'Dashboard Title' + panel_groups: + - group: 'Group Title' + panels: + - type: area-chart + title: "Chart Title" + y_label: "Y-Axis" + y_axis: + format: number + precision: 0 + metrics: + - id: my_metric_id + query_range: 'http_requests_total' + label: "Instance: {{instance}}, method: {{method}}" + unit: "count" + ``` -From each of the panels in the dashboard, you can access the context menu by clicking the **{ellipsis_v}** **More actions** dropdown box above the upper right corner of the panel to take actions related to the chart's data. +1. Save the file, commit, and push to your repository. The file must be present in your **default** branch. +1. Navigate to your project's **Operations > Metrics** and choose the custom + dashboard from the dropdown. -![Context Menu](../../../user/project/integrations/img/panel_context_menu_v13_0.png) +Your custom dashboard is available at `https://example.com/project/-/metrics/custom_dashboard_name.yml`. -The options are: +NOTE: **Note:** +Configuration files nested under subdirectories of `.gitlab/dashboards` are not +supported and won't be available in the UI. -- [Expand panel](#expand-panel) -- [View logs](#view-logs-ultimate) -- [Download CSV](#downloading-data-as-csv) -- [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics) -- [Alerts](../alerts.md) +## Duplicate a GitLab-defined dashboard -### View and edit the source file of a custom dashboard +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7. +> - From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/39505), custom metrics are also duplicated when you duplicate a dashboard. -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5. +You can save a complete copy of a GitLab defined dashboard along with all custom metrics added to it. +The resulting `.yml` file can be customized and adapted to your project. +You can decide to save the dashboard `.yml` file in the project's **default** branch or in a +new branch. + +1. Click **Duplicate dashboard** in the dashboard dropdown or in the actions menu. + + NOTE: **Note:** + You can duplicate only GitLab-defined dashboards. + +1. Enter the filename and other information, such as the new commit's message, and click **Duplicate**. +1. Select a branch to add your dashboard to: + - *If you select your **default** branch,* the new dashboard becomes immediately available. + - *If you select another branch,* this branch should be merged to your **default** branch first. -When viewing a custom dashboard of a project, you can view the original -`.yml` file by clicking on the **Edit dashboard** button. +Your custom dashboard is available at `https://example.com/project/-/metrics/custom_dashboard_name.yml`. + +## Manage the metrics dashboard settings -### Expand panel +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2. -> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0. +To manage the settings for your metrics dashboard: -To view a larger version of a visualization, expand the panel by clicking the -**{ellipsis_v}** **More actions** icon and selecting **Expand panel**. +1. Sign in as a user with project Maintainer or Admin + [permissions](../../../user/permissions.md#project-members-permissions). +1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**. +1. In the top-right corner of your dashboard, click **{settings}** **Metrics Settings**: -To return to the metrics dashboard, click the **Back** button in your -browser, or pressing the <kbd>Esc</kbd> key. + ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/metrics_settings_button_v13_2.png) -### View Logs **(ULTIMATE)** +## Chart Context Menu -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122013) in GitLab 12.8. +You can take action related to a chart's data by clicking the +**{ellipsis_v}** **More actions** dropdown box above the upper right corner of +any chart on a dashboard: -If you have [Logs](../../../user/project/clusters/kubernetes_pod_logs.md) enabled, -you can navigate from the charts in the dashboard to view Logs by -clicking on the context menu in the upper-right corner. +![Context Menu](../../../user/project/integrations/img/panel_context_menu_v13_0.png) -If you use the **Timeline zoom** function at the bottom of the chart, logs will narrow down to the time range you selected. +The options are: + +- **Expand panel** - Displays a larger version of a visualization. To return to + the dashboard, click the **Back** button in your browser, or press the <kbd>Esc</kbd> key. + ([Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3100) in GitLab 13.0.) +- **View logs** **(ULTIMATE)** - Displays [Logs](../../../user/project/clusters/kubernetes_pod_logs.md), + if they are enabled. If used in conjunction with the [timeline zoom](#timeline-zoom-and-url-sharing) + feature, logs narrow down to the selected time range. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/122013) in GitLab 12.8.) +- **Download CSV** - Data from Prometheus charts on the metrics dashboard can be downloaded as CSV. +- [Copy link to chart](../embed.md#embedding-gitlab-managed-kubernetes-metrics) +- [Alerts](../alerts.md) ### Timeline zoom and URL sharing @@ -69,10 +137,6 @@ on a date and time of your choice. When you click and drag the sliders to select a different beginning or end date of data to display, GitLab adds your selected start and end times to the URL, enabling you to share specific timeframes more easily. -### Downloading data as CSV - -Data from Prometheus charts on the metrics dashboard can be downloaded as CSV. - ## Dashboard Annotations > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211330) in GitLab 12.10 (enabled by feature flag `metrics_dashboard_annotations`). @@ -90,7 +154,7 @@ You can create annotations by making requests to the ![Annotations UI](../../../user/project/integrations/img/metrics_dashboard_annotations_ui_v13.0.png) -### Retention policy +### Annotation retention policy > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/211433) in GitLab 13.01. @@ -139,99 +203,6 @@ links: type: grafana ``` -## Defining custom dashboards per project - -> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/59974) in GitLab 12.1. - -By default, all projects include a GitLab-defined Prometheus dashboard, which -includes a few key metrics, but you can also define your own custom dashboards. - -You may create a new file from scratch or duplicate a GitLab-defined Prometheus -dashboard. - -NOTE: **Note:** -The metrics as defined below do not support alerts, unlike -[custom metrics](../index.md#adding-custom-metrics). - -### Adding a new dashboard to your project - -> UI option [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223204) in GitLab 13.2. - -You can configure a custom dashboard by adding a new YAML file into your project's -`.gitlab/dashboards/` directory. In order for the dashboards to be displayed on -the project's **{cloud-gear}** **Operations > Metrics** page, the files must have a `.yml` -extension and should be present in the project's **default** branch. - -To create a new dashboard from the GitLab user interface: - -1. Sign in to GitLab as a user with Maintainer or Owner - [permissions](../../../user/permissions.md#project-members-permissions). -1. Navigate to your dashboard at **{cloud-gear}** **Operations > Metrics**. -1. In the top-right corner of your dashboard, click the **{file-addition-solid}** **Actions** menu, - and select **Create new**: - ![Monitoring Dashboard actions menu with create new item](../../../user/project/integrations/img/actions_menu_create_new_dashboard_v13_2.png) -1. In the modal window, click **Open Repository**, then follow the instructions - for creating a new dashboard from the command line. - -To create a new dashboard from the command line: - -1. Create `.gitlab/dashboards/prom_alerts.yml` under your repository's root - directory. Each YAML file should define the layout of the dashboard and the - Prometheus queries used to populate data. This example dashboard displays a - single area chart: - - ```yaml - dashboard: 'Dashboard Title' - panel_groups: - - group: 'Group Title' - panels: - - type: area-chart - title: "Chart Title" - y_label: "Y-Axis" - y_axis: - format: number - precision: 0 - metrics: - - id: my_metric_id - query_range: 'http_requests_total' - label: "Instance: {{instance}}, method: {{method}}" - unit: "count" - ``` - -1. Save the file, commit, and push to your repository. The file must be present in your **default** branch. -1. Navigate to your project's **Operations > Metrics** and choose the custom - dashboard from the dropdown. - -NOTE: **Note:** -Configuration files nested under subdirectories of `.gitlab/dashboards` are not -supported and will not be available in the UI. - -### Navigating to a custom dashboard - -Custom dashboards are uniquely identified by their filenames. In order to quickly view the custom dashboard, -just use the dashboard filename in the URL this way: -`https://gitlab-instance.example.com/project/-/metrics/custom_dashboard_name.yml`. - -### Duplicating a GitLab-defined dashboard - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7. -> - From [GitLab 12.8 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/39505), custom metrics are also duplicated when you duplicate a dashboard. - -You can save a complete copy of a GitLab defined dashboard along with all custom metrics added to it. -Resulting `.yml` file can be customized and adapted to your project. -You can decide to save the dashboard `.yml` file in the project's **default** branch or in a -new branch. - -1. Click **Duplicate dashboard** in the dashboard dropdown or in the actions menu. - - NOTE: **Note:** - You can duplicate only GitLab-defined dashboards. - -1. Enter the file name and other information, such as the new commit's message, and click **Duplicate**. - -If you select your **default** branch, the new dashboard becomes immediately available. -If you select another branch, this branch should be merged to your **default** branch first. - ## Troubleshooting When troubleshooting issues with a managed Prometheus app, it is often useful to diff --git a/doc/operations/metrics/embed.md b/doc/operations/metrics/embed.md index 3d089d4fcc9..256f05980d3 100644 --- a/doc/operations/metrics/embed.md +++ b/doc/operations/metrics/embed.md @@ -53,7 +53,7 @@ Metric charts may also be hidden: ![Show Hide](../../user/project/integrations/img/hide_embedded_metrics_v12_10.png) You can open the link directly into your browser for a -[detailed view of the data](dashboards/index.md#expand-panel). +[detailed view of the data](dashboards/index.md#chart-context-menu). ## Embedding metrics in issue templates diff --git a/doc/operations/metrics/img/example-dashboard_v13_1.png b/doc/operations/metrics/img/example-dashboard_v13_1.png Binary files differindex 0cda4ece689..0805346b916 100644 --- a/doc/operations/metrics/img/example-dashboard_v13_1.png +++ b/doc/operations/metrics/img/example-dashboard_v13_1.png diff --git a/doc/operations/metrics/index.md b/doc/operations/metrics/index.md index 12088884f44..c5ca8815bd9 100644 --- a/doc/operations/metrics/index.md +++ b/doc/operations/metrics/index.md @@ -41,8 +41,11 @@ navigation bar contains: Starred dashboards display a solid star **{star}** button, and display first in the **Dashboard** dropdown list. ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214582) in GitLab 13.0.) +- **Edit dashboard** - Edit the source YAML file of a custom dashboard. Only available on + [custom dashboards](dashboards/index.md). + ([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/34779) in GitLab 12.5.) - **Create dashboard** **{file-addition-solid}** - Create a - [new custom dashboard for your project](dashboards/index.md#adding-a-new-dashboard-to-your-project). + [new custom dashboard for your project](dashboards/index.md#add-a-new-dashboard-to-your-project). - **Metrics settings** **{settings}** - Configure the [settings for this dashboard](dashboards/index.md#manage-the-metrics-dashboard-settings). diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png Binary files differdeleted file mode 100644 index e1edfcdd024..00000000000 --- a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png +++ /dev/null diff --git a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png Binary files differnew file mode 100644 index 00000000000..bf759f44dc5 --- /dev/null +++ b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png diff --git a/doc/user/compliance/compliance_dashboard/img/failed_icon_v13_3.png b/doc/user/compliance/compliance_dashboard/img/failed_icon_v13_3.png Binary files differnew file mode 100644 index 00000000000..c3f386c9dee --- /dev/null +++ b/doc/user/compliance/compliance_dashboard/img/failed_icon_v13_3.png diff --git a/doc/user/compliance/compliance_dashboard/img/success_icon_v13_3.png b/doc/user/compliance/compliance_dashboard/img/success_icon_v13_3.png Binary files differnew file mode 100644 index 00000000000..ea6ca924f81 --- /dev/null +++ b/doc/user/compliance/compliance_dashboard/img/success_icon_v13_3.png diff --git a/doc/user/compliance/compliance_dashboard/img/warning_icon_v13_3.png b/doc/user/compliance/compliance_dashboard/img/warning_icon_v13_3.png Binary files differnew file mode 100644 index 00000000000..168a7021948 --- /dev/null +++ b/doc/user/compliance/compliance_dashboard/img/warning_icon_v13_3.png diff --git a/doc/user/compliance/compliance_dashboard/index.md b/doc/user/compliance/compliance_dashboard/index.md index e7db73e25d9..b96dab90b80 100644 --- a/doc/user/compliance/compliance_dashboard/index.md +++ b/doc/user/compliance/compliance_dashboard/index.md @@ -17,7 +17,7 @@ for merging into production. To access the Compliance Dashboard for a group, navigate to **{shield}** **Security & Compliance > Compliance** on the group's menu. -![Compliance Dashboard](img/compliance_dashboard_v13_2.png) +![Compliance Dashboard](img/compliance_dashboard_v13_3.png) ## Use cases @@ -34,3 +34,28 @@ You can use the dashboard to: - On [GitLab Ultimate](https://about.gitlab.com/pricing/) tier. - By **Administrators** and **Group Owners**. + +## Approval status and separation of duties + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217939) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.3. + +We support a separation of duties policy between users who create and approve Merge Requests. +The approval status column can help you identify violations of this policy. +Our criteria for the separation of duties is as follows: + +- [A Merge Request author is **not** allowed to approve their Merge Request](../../project/merge_requests/merge_request_approvals.md#allowing-merge-request-authors-to-approve-their-own-merge-requests) +- [A Merge Request committer is **not** allowed to approve a Merge Request they have added commits to](../../project/merge_requests/merge_request_approvals.md#prevent-approval-of-merge-requests-by-their-committers) +- [The minimum number of approvals required to merge a Merge Request is **at least** two](../../project/merge_requests/merge_request_approvals.md#approval-rules) + +The "Approval status" column shows you, at a glance, whether a Merge Request is complying with the above. +This column has four states: + +| State | Description | +|:------|:------------| +| Empty | The Merge Request approval status is unknown | +| ![Failed](img/failed_icon_v13_3.png) | The Merge Request **does not** comply with any of the above criteria | +| ![Warning](img/warning_icon_v13_3.png) | The Merge Request complies with **some** of the above criteria | +| ![Success](img/success_icon_v13_3.png) | The Merge Request complies with **all** of the above criteria | + +If you do not see the success icon in your Compliance dashboard; please review the above criteria for the Merge Requests +project to make sure it complies with the separation of duties described above. diff --git a/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png b/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png Binary files differindex 9277b013676..0991e963e02 100644 --- a/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png +++ b/doc/user/incident_management/img/pagerduty_incidents_integration_13_2.png diff --git a/doc/user/incident_management/index.md b/doc/user/incident_management/index.md index 2efe5a8f790..a8714660afd 100644 --- a/doc/user/incident_management/index.md +++ b/doc/user/incident_management/index.md @@ -105,11 +105,8 @@ in incidents and issue templates. You can view more details about an embedded metrics panel from the context menu. To access the context menu, click the **{ellipsis_v}** **More actions** dropdown box -above the upper right corner of the panel. The options are: - -- [View logs](#view-logs-from-metrics-panel). -- **Download CSV** - Data from embedded charts can be - [downloaded as CSV](../../operations/metrics/dashboards/index.md#downloading-data-as-csv). +above the upper right corner of the panel. For a list of options, see +[Chart context menu](../../operations/metrics/dashboards/index.md#chart-context-menu). #### View logs from metrics panel @@ -117,7 +114,7 @@ above the upper right corner of the panel. The options are: > - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25455) to [GitLab Core](https://about.gitlab.com/pricing/) 12.9. Viewing logs from a metrics panel can be useful if you're triaging an application -incident and need to [explore logs](../../operations/metrics/dashboards/index.md#view-logs-ultimate) +incident and need to [explore logs](../../operations/metrics/dashboards/index.md#chart-context-menu) from across your application. These logs help you understand what is affecting your application's performance and resolve any problems. diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index f1567208a8f..145e32e0816 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -121,7 +121,8 @@ one of them will be used: [Prometheus manual configuration](#manual-configuration-of-prometheus) and a [managed Prometheus on Kubernetes](#managed-prometheus-on-kubernetes), the manual configuration takes precedence and is used to run queries from - [dashboards](../../../operations/metrics/dashboards/index.md#defining-custom-dashboards-per-project) and [custom metrics](../../../operations/metrics/index.md#adding-custom-metrics). + [custom dashboards](../../../operations/metrics/dashboards/index.md) and + [custom metrics](../../../operations/metrics/index.md#adding-custom-metrics). - If you have managed Prometheus applications installed on Kubernetes clusters at **different** levels (project, group, instance), the order of precedence is described in [Cluster precedence](../../instance/clusters/index.md#cluster-precedence). diff --git a/lib/api/entities/merge_request_approvals.rb b/lib/api/entities/merge_request_approvals.rb index e3d58d687c4..0c464844ae7 100644 --- a/lib/api/entities/merge_request_approvals.rb +++ b/lib/api/entities/merge_request_approvals.rb @@ -8,8 +8,7 @@ module API end expose :user_can_approve do |merge_request, options| - !merge_request.approved_by?(options[:current_user]) && - options[:current_user].can?(:approve_merge_request, merge_request) + merge_request.can_be_approved_by?(options[:current_user]) end expose :approved do |merge_request| diff --git a/lib/gitlab/ci/build/auto_retry.rb b/lib/gitlab/ci/build/auto_retry.rb new file mode 100644 index 00000000000..c690427daaa --- /dev/null +++ b/lib/gitlab/ci/build/auto_retry.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +class Gitlab::Ci::Build::AutoRetry + include Gitlab::Utils::StrongMemoize + + DEFAULT_RETRIES = { + scheduler_failure: 2 + }.freeze + + def initialize(build) + @build = build + end + + def allowed? + return false unless @build.retryable? + + within_max_retry_limit? + end + + private + + def within_max_retry_limit? + max_allowed_retries > 0 && max_allowed_retries > @build.retries_count + end + + def max_allowed_retries + strong_memoize(:max_allowed_retries) do + options_retry_max || DEFAULT_RETRIES.fetch(@build.failure_reason.to_sym, 0) + end + end + + def options_retry_max + options_retry[:max] if retry_on_reason_or_always? + end + + def options_retry_when + options_retry.fetch(:when, ['always']) + end + + def retry_on_reason_or_always? + options_retry_when.include?(@build.failure_reason.to_s) || + options_retry_when.include?('always') + end + + # The format of the retry option changed in GitLab 11.5: Before it was + # integer only, after it is a hash. New builds are created with the new + # format, but builds created before GitLab 11.5 and saved in database still + # have the old integer only format. This method returns the retry option + # normalized as a hash in 11.5+ format. + def options_retry + strong_memoize(:options_retry) do + value = @build.options&.dig(:retry) + value = value.is_a?(Integer) ? { max: value } : value.to_h + value.with_indifferent_access + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5bc105ee943..bac41af8a66 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3777,6 +3777,9 @@ msgstr "" msgid "Blocked issue" msgstr "" +msgid "Blocking issues" +msgstr "" + msgid "Blocks" msgstr "" @@ -4146,10 +4149,13 @@ msgstr "" msgid "Can override approvers and approvals required per merge request" msgstr "" -msgid "Can't apply as %{phrase} changed in a more recent version." +msgid "Can't apply as the source branch was deleted." +msgstr "" + +msgid "Can't apply as these lines were changed in a more recent version." msgstr "" -msgid "Can't apply as the source branch was deleted." +msgid "Can't apply as this line was changed in a more recent version." msgstr "" msgid "Can't apply this suggestion." diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb index 05631fe1ab5..ec88042673c 100644 --- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb @@ -2,14 +2,15 @@ module QA RSpec.describe 'Plan', :reliable do - let(:user) do + let!(:user) do Resource::User.fabricate_via_api! do |user| user.name = "eve <img src=x onerror=alert(2)<img src=x onerror=alert(1)>" user.password = "test1234" + user.api_client = Runtime::API::Client.as_admin end end - let(:project) do + let!(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'xss-test-for-mentions-project' end @@ -17,16 +18,6 @@ module QA describe 'check xss occurence in @mentions in issues', :requires_admin do before do - QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token - - unless QA::Runtime::Env.personal_access_token - Flow::Login.sign_in_as_admin - end - - QA::Runtime::Env.personal_access_token = nil - - Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) } - Flow::Login.sign_in Flow::Project.add_member(project: project, username: user.username) diff --git a/spec/controllers/projects/settings/operations_controller_spec.rb b/spec/controllers/projects/settings/operations_controller_spec.rb index d4f3c5d0c9b..191b718af56 100644 --- a/spec/controllers/projects/settings/operations_controller_spec.rb +++ b/spec/controllers/projects/settings/operations_controller_spec.rb @@ -206,7 +206,7 @@ RSpec.describe Projects::Settings::OperationsController do reset_pagerduty_token new_token = incident_management_setting.reload.pagerduty_token - new_webhook_url = project_incidents_pagerduty_url(project, token: new_token) + new_webhook_url = project_incidents_integrations_pagerduty_url(project, token: new_token) expect(response).to have_gitlab_http_status(:ok) expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url) @@ -219,7 +219,7 @@ RSpec.describe Projects::Settings::OperationsController do it 'does not reset a token' do reset_pagerduty_token - new_webhook_url = project_incidents_pagerduty_url(project, token: nil) + new_webhook_url = project_incidents_integrations_pagerduty_url(project, token: nil) expect(response).to have_gitlab_http_status(:ok) expect(json_response['pagerduty_webhook_url']).to eq(new_webhook_url) diff --git a/spec/frontend/batch_comments/mock_data.js b/spec/frontend/batch_comments/mock_data.js index c50fea94fe3..5601e489066 100644 --- a/spec/frontend/batch_comments/mock_data.js +++ b/spec/frontend/batch_comments/mock_data.js @@ -1,5 +1,6 @@ import { TEST_HOST } from 'spec/test_constants'; +// eslint-disable-next-line import/prefer-default-export export const createDraft = () => ({ author: { id: 1, @@ -23,5 +24,3 @@ export const createDraft = () => ({ isDraft: true, position: null, }); - -export default () => {}; diff --git a/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap index 3d1fe143ac3..902803b0ad1 100644 --- a/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap +++ b/spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap @@ -2,7 +2,7 @@ exports[`Design management index page designs does not render toolbar when there is no permission 1`] = ` <div - class="gl-mt-5" + class="gl-mt-5 designs-root" data-testid="designs-root" > <!----> @@ -87,7 +87,7 @@ exports[`Design management index page designs does not render toolbar when there exports[`Design management index page designs renders designs list and header with upload button 1`] = ` <div - class="gl-mt-5" + class="gl-mt-5 designs-root" data-testid="designs-root" > <header diff --git a/spec/frontend/helpers/dom_events_helper.js b/spec/frontend/helpers/dom_events_helper.js index b66c12daf4f..139e0813397 100644 --- a/spec/frontend/helpers/dom_events_helper.js +++ b/spec/frontend/helpers/dom_events_helper.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/prefer-default-export export const triggerDOMEvent = type => { window.document.dispatchEvent( new Event(type, { @@ -6,5 +7,3 @@ export const triggerDOMEvent = type => { }), ); }; - -export default () => {}; diff --git a/spec/frontend/helpers/monitor_helper_spec.js b/spec/frontend/helpers/monitor_helper_spec.js index 083b6404125..219b05e312b 100644 --- a/spec/frontend/helpers/monitor_helper_spec.js +++ b/spec/frontend/helpers/monitor_helper_spec.js @@ -1,12 +1,38 @@ -import * as monitorHelper from '~/helpers/monitor_helper'; +import { getSeriesLabel, makeDataSeries } from '~/helpers/monitor_helper'; describe('monitor helper', () => { const defaultConfig = { default: true, name: 'default name' }; const name = 'data name'; const series = [[1, 1], [2, 2], [3, 3]]; - const data = ({ metric = { default_name: name }, values = series } = {}) => [{ metric, values }]; + + describe('getSeriesLabel', () => { + const metricAttributes = { __name__: 'up', app: 'prometheus' }; + + it('gets a single attribute label', () => { + expect(getSeriesLabel('app', metricAttributes)).toBe('app: prometheus'); + }); + + it('gets a templated label', () => { + expect(getSeriesLabel('{{__name__}}', metricAttributes)).toBe('up'); + expect(getSeriesLabel('{{app}}', metricAttributes)).toBe('prometheus'); + expect(getSeriesLabel('{{missing}}', metricAttributes)).toBe('{{missing}}'); + }); + + it('gets a multiple label', () => { + expect(getSeriesLabel(null, metricAttributes)).toBe('__name__: up, app: prometheus'); + expect(getSeriesLabel('', metricAttributes)).toBe('__name__: up, app: prometheus'); + }); + + it('gets a simple label', () => { + expect(getSeriesLabel('A label', {})).toBe('A label'); + }); + }); describe('makeDataSeries', () => { + const data = ({ metric = { default_name: name }, values = series } = {}) => [ + { metric, values }, + ]; + const expectedDataSeries = [ { ...defaultConfig, @@ -15,19 +41,17 @@ describe('monitor helper', () => { ]; it('converts query results to data series', () => { - expect(monitorHelper.makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual( - expectedDataSeries, - ); + expect(makeDataSeries(data({ metric: {} }), defaultConfig)).toEqual(expectedDataSeries); }); it('returns an empty array if no query results exist', () => { - expect(monitorHelper.makeDataSeries([], defaultConfig)).toEqual([]); + expect(makeDataSeries([], defaultConfig)).toEqual([]); }); it('handles multi-series query results', () => { const expectedData = { ...expectedDataSeries[0], name: 'default name: data name' }; - expect(monitorHelper.makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ + expect(makeDataSeries([...data(), ...data()], defaultConfig)).toEqual([ expectedData, expectedData, ]); @@ -39,10 +63,7 @@ describe('monitor helper', () => { name: '{{cmd}}', }; - const [result] = monitorHelper.makeDataSeries( - [{ metric: { cmd: 'brpop' }, values: series }], - config, - ); + const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config); expect(result.name).toEqual('brpop'); }); @@ -53,7 +74,7 @@ describe('monitor helper', () => { name: '', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [ { metric: { @@ -79,7 +100,7 @@ describe('monitor helper', () => { name: 'backend: {{ backend }}', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { backend: 'HA Server' }, values: series }], config, ); @@ -90,10 +111,7 @@ describe('monitor helper', () => { it('supports repeated template variables', () => { const config = { ...defaultConfig, name: '{{cmd}}, {{cmd}}' }; - const [result] = monitorHelper.makeDataSeries( - [{ metric: { cmd: 'brpop' }, values: series }], - config, - ); + const [result] = makeDataSeries([{ metric: { cmd: 'brpop' }, values: series }], config); expect(result.name).toEqual('brpop, brpop'); }); @@ -101,7 +119,7 @@ describe('monitor helper', () => { it('supports hyphenated template variables', () => { const config = { ...defaultConfig, name: 'expired - {{ test-attribute }}' }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { 'test-attribute': 'test-attribute-value' }, values: series }], config, ); @@ -115,7 +133,7 @@ describe('monitor helper', () => { name: '{{job}}: {{cmd}}', }; - const [result] = monitorHelper.makeDataSeries( + const [result] = makeDataSeries( [{ metric: { cmd: 'brpop', job: 'redis' }, values: series }], config, ); @@ -129,7 +147,7 @@ describe('monitor helper', () => { name: '{{cmd}}', }; - const [firstSeries, secondSeries] = monitorHelper.makeDataSeries( + const [firstSeries, secondSeries] = makeDataSeries( [ { metric: { cmd: 'brpop' }, values: series }, { metric: { cmd: 'zrangebyscore' }, values: series }, diff --git a/spec/frontend/issuables_list/components/issuable_spec.js b/spec/frontend/issuables_list/components/issuable_spec.js index 87868b7eeff..5b46a186dda 100644 --- a/spec/frontend/issuables_list/components/issuable_spec.js +++ b/spec/frontend/issuables_list/components/issuable_spec.js @@ -85,12 +85,13 @@ describe('Issuable component', () => { const findMilestoneTooltip = () => findMilestone().attributes('title'); const findDueDate = () => wrapper.find('.js-due-date'); const findLabels = () => wrapper.findAll(GlLabel); - const findWeight = () => wrapper.find('.js-weight'); + const findWeight = () => wrapper.find('[data-testid="weight"]'); const findAssignees = () => wrapper.find(IssueAssignees); - const findMergeRequestsCount = () => wrapper.find('.js-merge-requests'); - const findUpvotes = () => wrapper.find('.js-upvotes'); - const findDownvotes = () => wrapper.find('.js-downvotes'); - const findNotes = () => wrapper.find('.js-notes'); + const findBlockingIssuesCount = () => wrapper.find('[data-testid="blocking-issues"]'); + const findMergeRequestsCount = () => wrapper.find('[data-testid="merge-requests"]'); + const findUpvotes = () => wrapper.find('[data-testid="upvotes"]'); + const findDownvotes = () => wrapper.find('[data-testid="downvotes"]'); + const findNotes = () => wrapper.find('[data-testid="notes-count"]'); const findBulkCheckbox = () => wrapper.find('input.selected-issuable'); const findScopedLabels = () => findLabels().filter(w => isScopedLabel({ title: w.text() })); const findUnscopedLabels = () => findLabels().filter(w => !isScopedLabel({ title: w.text() })); @@ -181,6 +182,7 @@ describe('Issuable component', () => { ${'due date'} | ${checkExists(findDueDate)} ${'labels'} | ${checkExists(findLabels)} ${'weight'} | ${checkExists(findWeight)} + ${'blocking issues count'} | ${checkExists(findBlockingIssuesCount)} ${'merge request count'} | ${checkExists(findMergeRequestsCount)} ${'upvotes'} | ${checkExists(findUpvotes)} ${'downvotes'} | ${checkExists(findDownvotes)} @@ -430,11 +432,12 @@ describe('Issuable component', () => { }); describe.each` - desc | key | finder - ${'with merge requests count'} | ${'merge_requests_count'} | ${findMergeRequestsCount} - ${'with upvote count'} | ${'upvotes'} | ${findUpvotes} - ${'with downvote count'} | ${'downvotes'} | ${findDownvotes} - ${'with notes count'} | ${'user_notes_count'} | ${findNotes} + desc | key | finder + ${'with blocking issues count'} | ${'blocking_issues_count'} | ${findBlockingIssuesCount} + ${'with merge requests count'} | ${'merge_requests_count'} | ${findMergeRequestsCount} + ${'with upvote count'} | ${'upvotes'} | ${findUpvotes} + ${'with downvote count'} | ${'downvotes'} | ${findDownvotes} + ${'with notes count'} | ${'user_notes_count'} | ${findNotes} `('$desc', ({ key, finder }) => { beforeEach(() => { issuable[key] = TEST_META_COUNT; @@ -442,7 +445,7 @@ describe('Issuable component', () => { factory({ issuable }); }); - it('renders merge requests count', () => { + it('renders correct count', () => { expect(finder().exists()).toBe(true); expect(finder().text()).toBe(TEST_META_COUNT.toString()); expect(finder().classes('no-comments')).toBe(false); diff --git a/spec/frontend/issuables_list/issuable_list_test_data.js b/spec/frontend/issuables_list/issuable_list_test_data.js index 19d8ee7f71a..85c40117198 100644 --- a/spec/frontend/issuables_list/issuable_list_test_data.js +++ b/spec/frontend/issuables_list/issuable_list_test_data.js @@ -18,6 +18,7 @@ export const simpleIssue = { }, assignee: null, user_notes_count: 0, + blocking_issues_count: 0, merge_requests_count: 0, upvotes: 0, downvotes: 0, diff --git a/spec/frontend/monitoring/components/charts/heatmap_spec.js b/spec/frontend/monitoring/components/charts/heatmap_spec.js index 2a1c78025ae..27a2021e9be 100644 --- a/spec/frontend/monitoring/components/charts/heatmap_spec.js +++ b/spec/frontend/monitoring/components/charts/heatmap_spec.js @@ -2,7 +2,7 @@ import { shallowMount } from '@vue/test-utils'; import { GlHeatmap } from '@gitlab/ui/dist/charts'; import timezoneMock from 'timezone-mock'; import Heatmap from '~/monitoring/components/charts/heatmap.vue'; -import { graphDataPrometheusQueryRangeMultiTrack } from '../../mock_data'; +import { heatmapGraphData } from '../../graph_data'; describe('Heatmap component', () => { let wrapper; @@ -10,10 +10,12 @@ describe('Heatmap component', () => { const findChart = () => wrapper.find(GlHeatmap); + const graphData = heatmapGraphData(); + const createWrapper = (props = {}) => { wrapper = shallowMount(Heatmap, { propsData: { - graphData: graphDataPrometheusQueryRangeMultiTrack, + graphData: heatmapGraphData(), containerWidth: 100, ...props, }, @@ -38,11 +40,11 @@ describe('Heatmap component', () => { }); it('should display a label on the x axis', () => { - expect(wrapper.vm.xAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.x_label); + expect(wrapper.vm.xAxisName).toBe(graphData.xLabel); }); it('should display a label on the y axis', () => { - expect(wrapper.vm.yAxisName).toBe(graphDataPrometheusQueryRangeMultiTrack.y_label); + expect(wrapper.vm.yAxisName).toBe(graphData.y_label); }); // According to the echarts docs https://echarts.apache.org/en/option.html#series-heatmap.data @@ -54,24 +56,24 @@ describe('Heatmap component', () => { const row = wrapper.vm.chartData[0]; expect(row.length).toBe(3); - expect(wrapper.vm.chartData.length).toBe(30); + expect(wrapper.vm.chartData.length).toBe(6); }); it('returns a series of labels for the x axis', () => { const { xAxisLabels } = wrapper.vm; - expect(xAxisLabels.length).toBe(5); + expect(xAxisLabels.length).toBe(2); }); describe('y axis labels', () => { - const gmtLabels = ['3:00 PM', '4:00 PM', '5:00 PM', '6:00 PM', '7:00 PM', '8:00 PM']; + const gmtLabels = ['8:10 PM', '8:12 PM', '8:14 PM']; it('y-axis labels are formatted in AM/PM format', () => { expect(findChart().props('yAxisLabels')).toEqual(gmtLabels); }); describe('when in PT timezone', () => { - const ptLabels = ['8:00 AM', '9:00 AM', '10:00 AM', '11:00 AM', '12:00 PM', '1:00 PM']; + const ptLabels = ['1:10 PM', '1:12 PM', '1:14 PM']; const utcLabels = gmtLabels; // Identical in this case beforeAll(() => { diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 693818aa55a..a38af9770cf 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -14,11 +14,10 @@ import { mockNamespace, mockNamespacedData, mockTimeRange, - graphDataPrometheusQueryRangeMultiTrack, barMockData, } from '../mock_data'; import { dashboardProps, graphData, graphDataEmpty } from '../fixture_data'; -import { anomalyGraphData, singleStatGraphData } from '../graph_data'; +import { anomalyGraphData, singleStatGraphData, heatmapGraphData } from '../graph_data'; import { panelTypes } from '~/monitoring/constants'; @@ -235,7 +234,7 @@ describe('Dashboard Panel', () => { ${anomalyGraphData()} | ${MonitorAnomalyChart} | ${false} ${dataWithType(panelTypes.COLUMN)} | ${MonitorColumnChart} | ${false} ${dataWithType(panelTypes.STACKED_COLUMN)} | ${MonitorStackedColumnChart} | ${false} - ${graphDataPrometheusQueryRangeMultiTrack} | ${MonitorHeatmapChart} | ${false} + ${heatmapGraphData()} | ${MonitorHeatmapChart} | ${false} ${barMockData} | ${MonitorBarChart} | ${false} `('when $data.type data is provided', ({ data, component, hasCtxMenu }) => { const attrs = { attr1: 'attr1Value', attr2: 'attr2Value' }; @@ -444,7 +443,7 @@ describe('Dashboard Panel', () => { describe('csvText', () => { it('converts metrics data from json to csv', () => { - const header = `timestamp,${graphData.y_label}`; + const header = `timestamp,"${graphData.y_label} > ${graphData.metrics[0].label}"`; const data = graphData.metrics[0].result[0].values; const firstRow = `${data[0][0]},${data[0][1]}`; const secondRow = `${data[1][0]},${data[1][1]}`; @@ -523,7 +522,7 @@ describe('Dashboard Panel', () => { }); it('displays a heatmap in local timezone', () => { - createWrapper({ graphData: graphDataPrometheusQueryRangeMultiTrack }); + createWrapper({ graphData: heatmapGraphData() }); expect(wrapper.find(MonitorHeatmapChart).props('timezone')).toBe('LOCAL'); }); @@ -538,7 +537,7 @@ describe('Dashboard Panel', () => { }); it('displays a heatmap with UTC', () => { - createWrapper({ graphData: graphDataPrometheusQueryRangeMultiTrack }); + createWrapper({ graphData: heatmapGraphData() }); expect(wrapper.find(MonitorHeatmapChart).props('timezone')).toBe('UTC'); }); }); diff --git a/spec/frontend/monitoring/csv_export_spec.js b/spec/frontend/monitoring/csv_export_spec.js new file mode 100644 index 00000000000..90d6eaa435f --- /dev/null +++ b/spec/frontend/monitoring/csv_export_spec.js @@ -0,0 +1,126 @@ +import { timeSeriesGraphData } from './graph_data'; +import { graphDataToCsv } from '~/monitoring/csv_export'; + +describe('monitoring export_csv', () => { + describe('graphDataToCsv', () => { + const expectCsvToMatchLines = (csv, lines) => expect(`${lines.join('\r\n')}\r\n`).toEqual(csv); + + it('should return a csv with 0 metrics', () => { + const data = timeSeriesGraphData({}, { metricCount: 0 }); + + expect(graphDataToCsv(data)).toEqual(''); + }); + + it('should return a csv with 1 metric with no data', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + // When state is NO_DATA, result is null + data.metrics[0].result = null; + + expect(graphDataToCsv(data)).toEqual(''); + }); + + it('should return a csv with multiple metrics and one with no data', () => { + const data = timeSeriesGraphData({}, { metricCount: 2 }); + + // When state is NO_DATA, result is null + data.metrics[0].result = null; + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 2"`, + '2015-07-01T20:10:51.781Z,1', + '2015-07-01T20:11:06.781Z,2', + '2015-07-01T20:11:21.781Z,3', + ]); + }); + + it('should return a csv when not all metrics have the same timestamps', () => { + const data = timeSeriesGraphData({}, { metricCount: 3 }); + + // Add an "odd" timestamp that is not in the dataset + Object.assign(data.metrics[2].result[0], { + value: ['2016-01-01T00:00:00.000Z', 9], + values: [['2016-01-01T00:00:00.000Z', 9]], + }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`, + '2015-07-01T20:10:51.781Z,1,1,', + '2015-07-01T20:11:06.781Z,2,2,', + '2015-07-01T20:11:21.781Z,3,3,', + '2016-01-01T00:00:00.000Z,,,9', + ]); + }); + + it('should return a csv with 1 metric', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1"`, + '2015-07-01T20:10:51.781Z,1', + '2015-07-01T20:11:06.781Z,2', + '2015-07-01T20:11:21.781Z,3', + ]); + }); + + it('should escape double quotes in metric labels with two double quotes ("")', () => { + const data = timeSeriesGraphData({}, { metricCount: 1 }); + + data.metrics[0].label = 'My "quoted" metric'; + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > My ""quoted"" metric"`, + '2015-07-01T20:10:51.781Z,1', + '2015-07-01T20:11:06.781Z,2', + '2015-07-01T20:11:21.781Z,3', + ]); + }); + + it('should return a csv with multiple metrics', () => { + const data = timeSeriesGraphData({}, { metricCount: 3 }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 2","Y Axis > Metric 3"`, + '2015-07-01T20:10:51.781Z,1,1,1', + '2015-07-01T20:11:06.781Z,2,2,2', + '2015-07-01T20:11:21.781Z,3,3,3', + ]); + }); + + it('should return a csv with 1 metric and multiple series with labels', () => { + const data = timeSeriesGraphData({}, { isMultiSeries: true }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > Metric 1","Y Axis > Metric 1"`, + '2015-07-01T20:10:51.781Z,1,4', + '2015-07-01T20:11:06.781Z,2,5', + '2015-07-01T20:11:21.781Z,3,6', + ]); + }); + + it('should return a csv with 1 metric and multiple series', () => { + const data = timeSeriesGraphData({}, { isMultiSeries: true, withLabels: false }); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`, + '2015-07-01T20:10:51.781Z,1,4', + '2015-07-01T20:11:06.781Z,2,5', + '2015-07-01T20:11:21.781Z,3,6', + ]); + }); + + it('should return a csv with multiple metrics and multiple series', () => { + const data = timeSeriesGraphData( + {}, + { metricCount: 3, isMultiSeries: true, withLabels: false }, + ); + + expectCsvToMatchLines(graphDataToCsv(data), [ + `timestamp,"Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091","Y Axis > __name__: up, job: prometheus, instance: localhost:9090","Y Axis > __name__: up, job: node, instance: localhost:9091"`, + '2015-07-01T20:10:51.781Z,1,4,1,4,1,4', + '2015-07-01T20:11:06.781Z,2,5,2,5,2,5', + '2015-07-01T20:11:21.781Z,3,6,3,6,3,6', + ]); + }); + }); +}); diff --git a/spec/frontend/monitoring/graph_data.js b/spec/frontend/monitoring/graph_data.js index e1b95723f3d..fcdca95ac09 100644 --- a/spec/frontend/monitoring/graph_data.js +++ b/spec/frontend/monitoring/graph_data.js @@ -1,10 +1,11 @@ import { mapPanelToViewModel, normalizeQueryResponseData } from '~/monitoring/stores/utils'; import { panelTypes, metricStates } from '~/monitoring/constants'; -const initTime = 1435781451.781; +const initTime = 1435781450; // "Wed, 01 Jul 2015 20:10:50 GMT" +const intervalSeconds = 120; const makeValue = val => [initTime, val]; -const makeValues = vals => vals.map((val, i) => [initTime + 15 * i, val]); +const makeValues = vals => vals.map((val, i) => [initTime + intervalSeconds * i, val]); // Normalized Prometheus Responses @@ -82,7 +83,7 @@ const matrixMultiResult = ({ values1 = ['1', '2', '3'], values2 = ['4', '5', '6' * @param {Object} dataOptions.isMultiSeries */ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => { - const { metricCount = 1, isMultiSeries = false } = dataOptions; + const { metricCount = 1, isMultiSeries = false, withLabels = true } = dataOptions; return mapPanelToViewModel({ title: 'Time Series Panel', @@ -90,7 +91,7 @@ export const timeSeriesGraphData = (panelOptions = {}, dataOptions = {}) => { x_label: 'X Axis', y_label: 'Y Axis', metrics: Array.from(Array(metricCount), (_, i) => ({ - label: `Metric ${i + 1}`, + label: withLabels ? `Metric ${i + 1}` : undefined, state: metricStates.OK, result: isMultiSeries ? matrixMultiResult() : matrixSingleResult(), })), @@ -162,3 +163,23 @@ export const anomalyGraphData = (panelOptions = {}, dataOptions = {}) => { ...panelOptions, }); }; + +/** + * Generate mock graph data for heatmaps according to options + */ +export const heatmapGraphData = (panelOptions = {}, dataOptions = {}) => { + const { metricCount = 1 } = dataOptions; + + return mapPanelToViewModel({ + title: 'Heatmap Panel', + type: panelTypes.HEATMAP, + x_label: 'X Axis', + y_label: 'Y Axis', + metrics: Array.from(Array(metricCount), (_, i) => ({ + label: `Metric ${i + 1}`, + state: metricStates.OK, + result: matrixMultiResult(), + })), + ...panelOptions, + }); +}; diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js index 49ad33402c6..11e6eb2e86f 100644 --- a/spec/frontend/monitoring/mock_data.js +++ b/spec/frontend/monitoring/mock_data.js @@ -244,83 +244,6 @@ export const metricsResult = [ }, ]; -export const graphDataPrometheusQueryRangeMultiTrack = { - title: 'Super Chart A3', - type: 'heatmap', - weight: 3, - x_label: 'Status Code', - y_label: 'Time', - metrics: [ - { - metricId: '1_metric_b', - id: 'response_metrics_nginx_ingress_throughput_status_code', - query_range: - 'sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[60m])) by (status_code)', - unit: 'req / sec', - label: 'Status Code', - prometheus_endpoint_path: - '/root/rails_nodb/environments/3/prometheus/api/v1/query_range?query=sum%28rate%28nginx_upstream_responses_total%7Bupstream%3D~%22%25%7Bkube_namespace%7D-%25%7Bci_environment_slug%7D-.%2A%22%7D%5B2m%5D%29%29+by+%28status_code%29', - result: [ - { - metric: { status_code: '1xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 0], - ['2019-08-30T16:00:00.000Z', 2], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 0], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 3], - ], - }, - { - metric: { status_code: '2xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 1], - ['2019-08-30T16:00:00.000Z', 3], - ['2019-08-30T17:00:00.000Z', 6], - ['2019-08-30T18:00:00.000Z', 10], - ['2019-08-30T19:00:00.000Z', 8], - ['2019-08-30T20:00:00.000Z', 6], - ], - }, - { - metric: { status_code: '3xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 1], - ['2019-08-30T16:00:00.000Z', 2], - ['2019-08-30T17:00:00.000Z', 3], - ['2019-08-30T18:00:00.000Z', 3], - ['2019-08-30T19:00:00.000Z', 2], - ['2019-08-30T20:00:00.000Z', 1], - ], - }, - { - metric: { status_code: '4xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 2], - ['2019-08-30T16:00:00.000Z', 0], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 2], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 2], - ], - }, - { - metric: { status_code: '5xx' }, - values: [ - ['2019-08-30T15:00:00.000Z', 0], - ['2019-08-30T16:00:00.000Z', 1], - ['2019-08-30T17:00:00.000Z', 0], - ['2019-08-30T18:00:00.000Z', 0], - ['2019-08-30T19:00:00.000Z', 0], - ['2019-08-30T20:00:00.000Z', 2], - ], - }, - ], - }, - ], -}; - export const stackedColumnMockedData = { title: 'memories', type: 'stacked-column', diff --git a/spec/frontend/reports/accessibility_report/mock_data.js b/spec/frontend/reports/accessibility_report/mock_data.js index f8e832c1ce5..20ad01bd802 100644 --- a/spec/frontend/reports/accessibility_report/mock_data.js +++ b/spec/frontend/reports/accessibility_report/mock_data.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/prefer-default-export export const mockReport = { status: 'failed', summary: { @@ -51,5 +52,3 @@ export const mockReport = { existing_notes: [], existing_warnings: [], }; - -export default () => {}; diff --git a/spec/frontend/serverless/utils.js b/spec/frontend/serverless/utils.js index 5ce2e37d493..ba451b7d573 100644 --- a/spec/frontend/serverless/utils.js +++ b/spec/frontend/serverless/utils.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line import/prefer-default-export export const adjustMetricQuery = data => { const updatedMetric = data.metrics; @@ -15,6 +16,3 @@ export const adjustMetricQuery = data => { updatedMetric.queries = queries; return updatedMetric; }; - -// prevent babel-plugin-rewire from generating an invalid default during karma tests -export default () => {}; diff --git a/spec/helpers/operations_helper_spec.rb b/spec/helpers/operations_helper_spec.rb index 73deb2249bc..8e3b1db5272 100644 --- a/spec/helpers/operations_helper_spec.rb +++ b/spec/helpers/operations_helper_spec.rb @@ -152,7 +152,7 @@ RSpec.describe OperationsHelper do send_email: 'false', pagerduty_active: 'true', pagerduty_token: operations_settings.pagerduty_token, - pagerduty_webhook_url: project_incidents_pagerduty_url(project, token: operations_settings.pagerduty_token), + pagerduty_webhook_url: project_incidents_integrations_pagerduty_url(project, token: operations_settings.pagerduty_token), pagerduty_reset_key_path: reset_pagerduty_token_project_settings_operations_path(project) ) end diff --git a/spec/lib/gitlab/ci/build/auto_retry_spec.rb b/spec/lib/gitlab/ci/build/auto_retry_spec.rb new file mode 100644 index 00000000000..cdec4cd28ee --- /dev/null +++ b/spec/lib/gitlab/ci/build/auto_retry_spec.rb @@ -0,0 +1,126 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Ci::Build::AutoRetry do + let(:auto_retry) { described_class.new(build) } + + describe '#allowed?' do + using RSpec::Parameterized::TableSyntax + + let(:build) { create(:ci_build) } + + subject { auto_retry.allowed? } + + where(:description, :retry_count, :options, :failure_reason, :result) do + "retries are disabled" | 0 | { max: 0 } | nil | false + "max equals count" | 2 | { max: 2 } | nil | false + "max is higher than count" | 1 | { max: 2 } | nil | true + "matching failure reason" | 0 | { when: %w[api_failure], max: 2 } | :api_failure | true + "not matching with always" | 0 | { when: %w[always], max: 2 } | :api_failure | true + "not matching reason" | 0 | { when: %w[script_error], max: 2 } | :api_failure | false + "scheduler failure override" | 1 | { when: %w[scheduler_failure], max: 1 } | :scheduler_failure | false + "default for scheduler failure" | 1 | {} | :scheduler_failure | true + end + + with_them do + before do + allow(build).to receive(:retries_count) { retry_count } + + build.options[:retry] = options + build.failure_reason = failure_reason + allow(build).to receive(:retryable?).and_return(true) + end + + it { is_expected.to eq(result) } + end + + context 'when build is not retryable' do + before do + allow(build).to receive(:retryable?).and_return(false) + end + + specify { expect(subject).to eq(false) } + end + end + + describe '#options_retry_max' do + subject(:result) { auto_retry.send(:options_retry_max) } + + context 'with retries max config option' do + let(:build) { create(:ci_build, options: { retry: { max: 1 } }) } + + context 'when build_metadata_config is set' do + before do + stub_feature_flags(ci_build_metadata_config: true) + end + + it 'returns the number of configured max retries' do + expect(result).to eq 1 + end + end + + context 'when build_metadata_config is not set' do + before do + stub_feature_flags(ci_build_metadata_config: false) + end + + it 'returns the number of configured max retries' do + expect(result).to eq 1 + end + end + end + + context 'without retries max config option' do + let(:build) { create(:ci_build) } + + it 'returns nil' do + expect(result).to be_nil + end + end + + context 'when build is degenerated' do + let(:build) { create(:ci_build, :degenerated) } + + it 'returns nil' do + expect(result).to be_nil + end + end + + context 'with integer only config option' do + let(:build) { create(:ci_build, options: { retry: 1 }) } + + it 'returns the number of configured max retries' do + expect(result).to eq 1 + end + end + end + + describe '#options_retry_when' do + subject(:result) { auto_retry.send(:options_retry_when) } + + context 'with retries when config option' do + let(:build) { create(:ci_build, options: { retry: { when: ['some_reason'] } }) } + + it 'returns the configured when' do + expect(result).to eq ['some_reason'] + end + end + + context 'without retries when config option' do + let(:build) { create(:ci_build) } + + it 'returns always array' do + expect(result).to eq ['always'] + end + end + + context 'with integer only config option' do + let(:build) { create(:ci_build, options: { retry: 1 }) } + + it 'returns always array' do + expect(result).to eq ['always'] + end + end + end +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 75347227c19..2cafe184945 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1703,112 +1703,6 @@ RSpec.describe Ci::Build do end end end - - describe '#options_retry_max' do - context 'with retries max config option' do - subject { create(:ci_build, options: { retry: { max: 1 } }) } - - context 'when build_metadata_config is set' do - before do - stub_feature_flags(ci_build_metadata_config: true) - end - - it 'returns the number of configured max retries' do - expect(subject.options_retry_max).to eq 1 - end - end - - context 'when build_metadata_config is not set' do - before do - stub_feature_flags(ci_build_metadata_config: false) - end - - it 'returns the number of configured max retries' do - expect(subject.options_retry_max).to eq 1 - end - end - end - - context 'without retries max config option' do - subject { create(:ci_build) } - - it 'returns nil' do - expect(subject.options_retry_max).to be_nil - end - end - - context 'when build is degenerated' do - subject { create(:ci_build, :degenerated) } - - it 'returns nil' do - expect(subject.options_retry_max).to be_nil - end - end - - context 'with integer only config option' do - subject { create(:ci_build, options: { retry: 1 }) } - - it 'returns the number of configured max retries' do - expect(subject.options_retry_max).to eq 1 - end - end - end - - describe '#options_retry_when' do - context 'with retries when config option' do - subject { create(:ci_build, options: { retry: { when: ['some_reason'] } }) } - - it 'returns the configured when' do - expect(subject.options_retry_when).to eq ['some_reason'] - end - end - - context 'without retries when config option' do - subject { create(:ci_build) } - - it 'returns always array' do - expect(subject.options_retry_when).to eq ['always'] - end - end - - context 'with integer only config option' do - subject { create(:ci_build, options: { retry: 1 }) } - - it 'returns always array' do - expect(subject.options_retry_when).to eq ['always'] - end - end - end - - describe '#retry_failure?' do - using RSpec::Parameterized::TableSyntax - - let(:build) { create(:ci_build) } - - subject { build.retry_failure? } - - where(:description, :retry_count, :options, :failure_reason, :result) do - "retries are disabled" | 0 | { max: 0 } | nil | false - "max equals count" | 2 | { max: 2 } | nil | false - "max is higher than count" | 1 | { max: 2 } | nil | true - "matching failure reason" | 0 | { when: %w[api_failure], max: 2 } | :api_failure | true - "not matching with always" | 0 | { when: %w[always], max: 2 } | :api_failure | true - "not matching reason" | 0 | { when: %w[script_error], max: 2 } | :api_failure | false - "scheduler failure override" | 1 | { when: %w[scheduler_failure], max: 1 } | :scheduler_failure | false - "default for scheduler failure" | 1 | {} | :scheduler_failure | true - end - - with_them do - before do - allow(build).to receive(:retries_count) { retry_count } - - build.options[:retry] = options - build.failure_reason = failure_reason - end - - it { is_expected.to eq(result) } - end - end end describe '.keep_artifacts!' do diff --git a/spec/models/concerns/approvable_base_spec.rb b/spec/models/concerns/approvable_base_spec.rb index 8fda8bccf09..a9e944cf220 100644 --- a/spec/models/concerns/approvable_base_spec.rb +++ b/spec/models/concerns/approvable_base_spec.rb @@ -3,10 +3,10 @@ require 'spec_helper' RSpec.describe ApprovableBase do - describe '#approved_by?' do - let(:merge_request) { create(:merge_request) } - let(:user) { create(:user) } + let(:merge_request) { create(:merge_request) } + let(:user) { create(:user) } + describe '#approved_by?' do subject { merge_request.approved_by?(user) } context 'when a user has not approved' do @@ -31,4 +31,32 @@ RSpec.describe ApprovableBase do end end end + + describe '#can_be_approved_by?' do + subject { merge_request.can_be_approved_by?(user) } + + before do + merge_request.project.add_developer(user) + end + + it 'returns true' do + is_expected.to be_truthy + end + + context 'when a user has approved' do + let!(:approval) { create(:approval, merge_request: merge_request, user: user) } + + it 'returns false' do + is_expected.to be_falsy + end + end + + context 'when a user is nil' do + let(:user) { nil } + + it 'returns false' do + is_expected.to be_falsy + end + end + end end diff --git a/spec/models/suggestion_spec.rb b/spec/models/suggestion_spec.rb index 6c30bc39c1d..e88fc13ecee 100644 --- a/spec/models/suggestion_spec.rb +++ b/spec/models/suggestion_spec.rb @@ -53,7 +53,7 @@ RSpec.describe Suggestion do end context 'when inapplicable_reason is not nil' do - let(:inapplicable_reason) { :applied } + let(:inapplicable_reason) { "Can't apply this suggestion." } it { is_expected.to be_falsey } end @@ -77,7 +77,7 @@ RSpec.describe Suggestion do context 'when suggestion is already applied' do let(:suggestion) { build(:suggestion, :applied, note: note) } - it { is_expected.to eq(:applied) } + it { is_expected.to eq("Can't apply this suggestion.") } end context 'when merge request was merged' do @@ -85,7 +85,7 @@ RSpec.describe Suggestion do merge_request.mark_as_merged! end - it { is_expected.to eq(:merge_request_merged) } + it { is_expected.to eq("This merge request was merged. To apply this suggestion, edit this file directly.") } end context 'when merge request is closed' do @@ -93,7 +93,7 @@ RSpec.describe Suggestion do merge_request.close! end - it { is_expected.to eq(:merge_request_closed) } + it { is_expected.to eq("This merge request is closed. To apply this suggestion, edit this file directly.") } end context 'when source branch is deleted' do @@ -101,23 +101,51 @@ RSpec.describe Suggestion do merge_request.project.repository.rm_branch(merge_request.author, merge_request.source_branch) end - it { is_expected.to eq(:source_branch_deleted) } + it { is_expected.to eq("Can't apply as the source branch was deleted.") } end - context 'when content is outdated' do - before do - allow(suggestion).to receive(:outdated?).and_return(true) + context 'when outdated' do + shared_examples_for 'outdated suggestion' do + before do + allow(suggestion).to receive(:single_line?).and_return(single_line) + end + + context 'and suggestion is for a single line' do + let(:single_line) { true } + + it { is_expected.to eq("Can't apply as this line was changed in a more recent version.") } + end + + context 'and suggestion is for multiple lines' do + let(:single_line) { false } + + it { is_expected.to eq("Can't apply as these lines were changed in a more recent version.") } + end end - it { is_expected.to eq(:outdated) } + context 'and content is outdated' do + before do + allow(suggestion).to receive(:outdated?).and_return(true) + end + + it_behaves_like 'outdated suggestion' + end + + context 'and note is outdated' do + before do + allow(note).to receive(:active?).and_return(false) + end + + it_behaves_like 'outdated suggestion' + end end - context 'when note is outdated' do + context 'when suggestion has the same content' do before do - allow(note).to receive(:active?).and_return(false) + allow(suggestion).to receive(:different_content?).and_return(false) end - it { is_expected.to eq(:outdated) } + it { is_expected.to eq("This suggestion already matches its content.") } end context 'when applicable' do diff --git a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb index c246aacb4c7..b18bffdb110 100644 --- a/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb +++ b/spec/requests/projects/incident_management/pagerduty_incidents_spec.rb @@ -12,7 +12,7 @@ RSpec.describe 'PagerDuty webhook' do def make_request headers = { 'Content-Type' => 'application/json' } - post project_incidents_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers + post project_incidents_integrations_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers end before do diff --git a/spec/serializers/suggestion_entity_spec.rb b/spec/serializers/suggestion_entity_spec.rb index b133c3fb82e..25301bb20cc 100644 --- a/spec/serializers/suggestion_entity_spec.rb +++ b/spec/serializers/suggestion_entity_spec.rb @@ -36,87 +36,11 @@ RSpec.describe SuggestionEntity do let(:can_apply_suggestion) { true } before do - allow(suggestion).to receive(:appliable?).and_return(appliable) + allow(suggestion).to receive(:inapplicable_reason).and_return("Can't apply this suggestion.") end - context 'and suggestion is appliable' do - let(:appliable) { true } - - it 'returns nil' do - expect(inapplicable_reason).to be_nil - end - end - - context 'but suggestion is not applicable' do - let(:appliable) { false } - - before do - allow(suggestion).to receive(:inapplicable_reason).and_return(reason) - end - - context 'and merge request was merged' do - let(:reason) { :merge_request_merged } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("This merge request was merged. To apply this suggestion, edit this file directly.") - end - end - - context 'and source branch was deleted' do - let(:reason) { :source_branch_deleted } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("Can't apply as the source branch was deleted.") - end - end - - context 'and merge request is closed' do - let(:reason) { :merge_request_closed } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("This merge request is closed. To apply this suggestion, edit this file directly.") - end - end - - context 'and suggestion is outdated' do - let(:reason) { :outdated } - - before do - allow(suggestion).to receive(:single_line?).and_return(single_line) - end - - context 'and suggestion is for a single line' do - let(:single_line) { true } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("Can't apply as this line was changed in a more recent version.") - end - end - - context 'and suggestion is for multiple lines' do - let(:single_line) { false } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("Can't apply as these lines were changed in a more recent version.") - end - end - end - - context 'and suggestion has the same content' do - let(:reason) { :same_content } - - it 'returns appropriate message' do - expect(inapplicable_reason).to eq("This suggestion already matches its content.") - end - end - - context 'and suggestion is inapplicable for other reasons' do - let(:reason) { :some_other_reason } - - it 'returns default message' do - expect(inapplicable_reason).to eq("Can't apply this suggestion.") - end - end + it 'returns the inapplicable reason' do + expect(inapplicable_reason).to eq(suggestion.inapplicable_reason) end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 9dc518be996..28035102e03 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -905,6 +905,7 @@ RSpec.describe Ci::CreatePipelineService do stub_ci_pipeline_yaml_file(YAML.dump({ rspec: { script: 'rspec', retry: retry_value } })) + rspec_job.update!(options: { retry: retry_value }) end context 'as an integer' do @@ -912,8 +913,6 @@ RSpec.describe Ci::CreatePipelineService do it 'correctly creates builds with auto-retry value configured' do expect(pipeline).to be_persisted - expect(rspec_job.options_retry_max).to eq 2 - expect(rspec_job.options_retry_when).to eq ['always'] end end @@ -922,8 +921,6 @@ RSpec.describe Ci::CreatePipelineService do it 'correctly creates builds with auto-retry value configured' do expect(pipeline).to be_persisted - expect(rspec_job.options_retry_max).to eq 2 - expect(rspec_job.options_retry_when).to eq ['runner_system_failure'] end end end diff --git a/vendor/Dockerfile/Rust.Dockerfile b/vendor/Dockerfile/Rust.Dockerfile new file mode 100644 index 00000000000..d066d9f6cf6 --- /dev/null +++ b/vendor/Dockerfile/Rust.Dockerfile @@ -0,0 +1,13 @@ +FROM rust:1.45 as builder + +WORKDIR /usr/src/app + +COPY . . +RUN cargo build --release + +FROM debian:buster-slim + +COPY --from=builder /usr/src/app/target/release/app . + +EXPOSE 8080 +CMD ["./app"] |