summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 18:09:27 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-20 18:09:27 +0000
commitbf213f07c8146b7121240af90a07cb4b2ecc41fa (patch)
tree184537ad2334124c5090f2a729a9fd58bccf6b51
parentdd2da214c9644ef1064061d706dfeec50f9fad8f (diff)
downloadgitlab-ce-bf213f07c8146b7121240af90a07cb4b2ecc41fa.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/admin/statistics_panel/store/actions.js3
-rw-r--r--app/assets/javascripts/admin/statistics_panel/store/getters.js4
-rw-r--r--app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js3
-rw-r--r--app/assets/javascripts/batch_comments/stores/modules/batch_comments/getters.js3
-rw-r--r--app/assets/javascripts/clusters_list/store/actions.js3
-rw-r--r--app/assets/javascripts/contributors/stores/actions.js4
-rw-r--r--app/assets/javascripts/contributors/stores/getters.js3
-rw-r--r--app/assets/javascripts/create_cluster/gke_cluster/store/actions.js3
-rw-r--r--app/assets/javascripts/design_management_new/pages/index.vue24
-rw-r--r--app/assets/javascripts/diffs/store/actions.js3
-rw-r--r--app/assets/javascripts/diffs/store/getters.js4
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js2
-rw-r--r--app/assets/javascripts/error_tracking/store/details/actions.js3
-rw-r--r--app/assets/javascripts/error_tracking/store/details/getters.js3
-rw-r--r--app/assets/javascripts/error_tracking/store/list/actions.js2
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/actions.js3
-rw-r--r--app/assets/javascripts/error_tracking_settings/store/getters.js3
-rw-r--r--app/assets/javascripts/error_tracking_settings/utils.js2
-rw-r--r--app/assets/javascripts/frequent_items/store/actions.js3
-rw-r--r--app/assets/javascripts/frequent_items/store/getters.js4
-rw-r--r--app/assets/javascripts/helpers/monitor_helper.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/branches/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/file_templates/getters.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/merge_requests/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/actions.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/pipelines/getters.js2
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/actions/index.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/terminal/getters.js3
-rw-r--r--app/assets/javascripts/import_projects/store/actions.js3
-rw-r--r--app/assets/javascripts/issuables_list/components/issuable.vue92
-rw-r--r--app/assets/javascripts/jobs/store/actions.js3
-rw-r--r--app/assets/javascripts/jobs/store/getters.js3
-rw-r--r--app/assets/javascripts/logs/stores/actions.js3
-rw-r--r--app/assets/javascripts/monitoring/components/charts/heatmap.vue2
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_panel.vue12
-rw-r--r--app/assets/javascripts/monitoring/csv_export.js147
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/actions.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/getters.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/embed_group/mutation_types.js3
-rw-r--r--app/assets/javascripts/notes/stores/actions.js3
-rw-r--r--app/assets/javascripts/notes/stores/getters.js3
-rw-r--r--app/assets/javascripts/operation_settings/store/actions.js3
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/actions.js3
-rw-r--r--app/assets/javascripts/pipelines/stores/test_reports/getters.js3
-rw-r--r--app/assets/javascripts/pipelines/utils.js3
-rw-r--r--app/assets/javascripts/registry/explorer/stores/actions.js2
-rw-r--r--app/assets/javascripts/registry/settings/store/actions.js3
-rw-r--r--app/assets/javascripts/related_merge_requests/store/actions.js3
-rw-r--r--app/assets/javascripts/releases/stores/modules/list/actions.js3
-rw-r--r--app/assets/javascripts/reports/accessibility_report/store/actions.js3
-rw-r--r--app/assets/javascripts/reports/accessibility_report/store/getters.js3
-rw-r--r--app/assets/javascripts/reports/store/actions.js3
-rw-r--r--app/assets/javascripts/reports/store/getters.js4
-rw-r--r--app/assets/javascripts/serverless/store/actions.js3
-rw-r--r--app/assets/javascripts/serverless/store/getters.js3
-rw-r--r--app/assets/javascripts/serverless/utils.js3
-rw-r--r--app/assets/javascripts/snippets/index.js2
-rw-r--r--app/assets/javascripts/snippets/mixins/snippets.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/actions.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/artifacts_list/getters.js4
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/actions.js3
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/labels_select_vue/store/getters.js3
-rw-r--r--app/assets/javascripts/vuex_shared/bindings.js3
-rw-r--r--app/assets/javascripts/vuex_shared/modules/modal/actions.js3
-rw-r--r--app/assets/stylesheets/components/design_management/design_list_item.scss9
-rw-r--r--app/controllers/projects/settings/operations_controller.rb2
-rw-r--r--app/helpers/operations_helper.rb2
-rw-r--r--app/models/application_setting.rb2
-rw-r--r--app/models/ci/build.rb46
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/concerns/approvable_base.rb4
-rw-r--r--app/models/suggestion.rb20
-rw-r--r--app/serializers/suggestion_entity.rb18
-rw-r--r--app/views/projects/blob/viewers/_metrics_dashboard_yml.html.haml2
-rw-r--r--changelogs/unreleased/214382-remove-application_settings_tokens_optional_encryption-feature-fla.yml5
-rw-r--r--changelogs/unreleased/214382-remove-ci_runners_tokens_optional_encryption-feature-flag.yml5
-rw-r--r--changelogs/unreleased/214627-fix-incorrect-csv-export.yml5
-rw-r--r--changelogs/unreleased/229588-pasting-an-image-into-a-comment-also-uploads-design.yml5
-rw-r--r--changelogs/unreleased/230459-change-pagerduty-webhook-url.yml5
-rw-r--r--changelogs/unreleased/28167-dockerfile-template-rust.yml5
-rw-r--r--changelogs/unreleased/id-fix-approvals-for-ee-without-license.yml5
-rw-r--r--config/locales/devise.en.yml2
-rw-r--r--config/routes/project.rb4
-rw-r--r--doc/administration/geo/replication/database.md5
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/index.md6
-rw-r--r--doc/api/issues.md7
-rw-r--r--doc/api/settings.md2
-rw-r--r--doc/api/templates/dockerfiles.md4
-rw-r--r--doc/development/fe_guide/style/scss.md8
-rw-r--r--doc/development/geo/framework.md9
-rw-r--r--doc/development/testing_guide/frontend_testing.md38
-rw-r--r--doc/operations/metrics/dashboards/index.md231
-rw-r--r--doc/operations/metrics/embed.md2
-rw-r--r--doc/operations/metrics/img/example-dashboard_v13_1.pngbin31439 -> 33311 bytes
-rw-r--r--doc/operations/metrics/index.md5
-rw-r--r--doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.pngbin84922 -> 0 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.pngbin0 -> 41721 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/img/failed_icon_v13_3.pngbin0 -> 4118 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/img/success_icon_v13_3.pngbin0 -> 4121 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/img/warning_icon_v13_3.pngbin0 -> 4095 bytes
-rw-r--r--doc/user/compliance/compliance_dashboard/index.md27
-rw-r--r--doc/user/incident_management/img/pagerduty_incidents_integration_13_2.pngbin34698 -> 43318 bytes
-rw-r--r--doc/user/incident_management/index.md9
-rw-r--r--doc/user/project/integrations/prometheus.md3
-rw-r--r--lib/api/entities/merge_request_approvals.rb3
-rw-r--r--lib/gitlab/ci/build/auto_retry.rb57
-rw-r--r--locale/gitlab.pot10
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb15
-rw-r--r--spec/controllers/projects/settings/operations_controller_spec.rb4
-rw-r--r--spec/frontend/batch_comments/mock_data.js3
-rw-r--r--spec/frontend/design_management_new/pages/__snapshots__/index_spec.js.snap4
-rw-r--r--spec/frontend/helpers/dom_events_helper.js3
-rw-r--r--spec/frontend/helpers/monitor_helper_spec.js58
-rw-r--r--spec/frontend/issuables_list/components/issuable_spec.js25
-rw-r--r--spec/frontend/issuables_list/issuable_list_test_data.js1
-rw-r--r--spec/frontend/monitoring/components/charts/heatmap_spec.js18
-rw-r--r--spec/frontend/monitoring/components/dashboard_panel_spec.js11
-rw-r--r--spec/frontend/monitoring/csv_export_spec.js126
-rw-r--r--spec/frontend/monitoring/graph_data.js29
-rw-r--r--spec/frontend/monitoring/mock_data.js77
-rw-r--r--spec/frontend/reports/accessibility_report/mock_data.js3
-rw-r--r--spec/frontend/serverless/utils.js4
-rw-r--r--spec/helpers/operations_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/auto_retry_spec.rb126
-rw-r--r--spec/models/ci/build_spec.rb106
-rw-r--r--spec/models/concerns/approvable_base_spec.rb34
-rw-r--r--spec/models/suggestion_spec.rb52
-rw-r--r--spec/requests/projects/incident_management/pagerduty_incidents_spec.rb2
-rw-r--r--spec/serializers/suggestion_entity_spec.rb82
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb5
-rw-r--r--vendor/Dockerfile/Rust.Dockerfile13
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">
&middot;
<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
index 0cda4ece689..0805346b916 100644
--- a/doc/operations/metrics/img/example-dashboard_v13_1.png
+++ b/doc/operations/metrics/img/example-dashboard_v13_1.png
Binary files differ
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
deleted file mode 100644
index e1edfcdd024..00000000000
--- a/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_2.png
+++ /dev/null
Binary files differ
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
new file mode 100644
index 00000000000..bf759f44dc5
--- /dev/null
+++ b/doc/user/compliance/compliance_dashboard/img/compliance_dashboard_v13_3.png
Binary files differ
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
new file mode 100644
index 00000000000..c3f386c9dee
--- /dev/null
+++ b/doc/user/compliance/compliance_dashboard/img/failed_icon_v13_3.png
Binary files differ
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
new file mode 100644
index 00000000000..ea6ca924f81
--- /dev/null
+++ b/doc/user/compliance/compliance_dashboard/img/success_icon_v13_3.png
Binary files differ
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
new file mode 100644
index 00000000000..168a7021948
--- /dev/null
+++ b/doc/user/compliance/compliance_dashboard/img/warning_icon_v13_3.png
Binary files differ
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
index 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
Binary files differ
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)&lt;img src=x onerror=alert(1)&gt;"
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"]