summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-07-08 15:09:24 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-07-08 15:09:24 +0000
commit833d57e60da633435d845a7867e46e6092c46520 (patch)
tree35676d3a0da36ef28b67cadb06af474b6c8f5b85
parentc52b72f5772d52e9fc85bd9f4e8b8497a6278c37 (diff)
downloadgitlab-ce-833d57e60da633435d845a7867e46e6092c46520.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml1
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/alert_management/components/system_notes/system_note.vue2
-rw-r--r--app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql23
-rw-r--r--app/assets/javascripts/diffs/constants.js6
-rw-r--r--app/assets/javascripts/diffs/index.js14
-rw-r--r--app/assets/javascripts/diffs/store/actions.js9
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js11
-rw-r--r--app/assets/javascripts/diffs/store/utils.js9
-rw-r--r--app/assets/javascripts/monitoring/components/create_dashboard_modal.vue66
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard_header.vue87
-rw-r--r--app/assets/javascripts/monitoring/components/dashboards_dropdown.vue79
-rw-r--r--app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue95
-rw-r--r--app/assets/javascripts/repository/components/breadcrumbs.vue12
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue14
-rw-r--r--app/assets/javascripts/repository/components/preview/index.vue8
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue6
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue4
-rw-r--r--app/assets/javascripts/repository/components/tree_content.vue8
-rw-r--r--app/assets/javascripts/repository/log_tree.js14
-rw-r--r--app/assets/javascripts/repository/mixins/get_ref.js4
-rw-r--r--app/assets/javascripts/repository/mixins/preload.js8
-rw-r--r--app/assets/javascripts/repository/queries/getCommit.query.graphql (renamed from app/assets/javascripts/repository/queries/commit.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/getCommits.query.graphql (renamed from app/assets/javascripts/repository/queries/commits.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql (renamed from app/assets/javascripts/repository/queries/files.query.graphql)6
-rw-r--r--app/assets/javascripts/repository/queries/getPermissions.query.graphql (renamed from app/assets/javascripts/repository/queries/permissions.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/getProjectPath.query.graphql3
-rw-r--r--app/assets/javascripts/repository/queries/getProjectShortPath.query.graphql (renamed from app/assets/javascripts/repository/queries/project_short_path.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/getReadme.query.graphql (renamed from app/assets/javascripts/repository/queries/readme.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/getRef.query.graphql (renamed from app/assets/javascripts/repository/queries/ref.query.graphql)2
-rw-r--r--app/assets/javascripts/repository/queries/pathLastCommit.query.graphql (renamed from app/assets/javascripts/repository/queries/path_last_commit.query.graphql)6
-rw-r--r--app/assets/javascripts/repository/queries/project_path.query.graphql3
-rw-r--r--app/graphql/types/commit_type.rb2
-rw-r--r--app/graphql/types/notes/note_type.rb6
-rw-r--r--app/graphql/types/tree/blob_type.rb2
-rw-r--r--app/graphql/types/tree/tree_entry_type.rb2
-rw-r--r--app/graphql/types/user_type.rb2
-rw-r--r--app/helpers/ci/pipeline_schedules_helper.rb15
-rw-r--r--app/helpers/environments_helper.rb1
-rw-r--r--app/helpers/pipeline_schedules_helper.rb13
-rw-r--r--app/presenters/blob_presenter.rb4
-rw-r--r--app/presenters/commit_presenter.rb4
-rw-r--r--app/presenters/tree_entry_presenter.rb4
-rw-r--r--app/presenters/user_presenter.rb4
-rw-r--r--app/services/projects/prometheus/alerts/create_events_service.rb71
-rw-r--r--app/services/projects/prometheus/alerts/notify_service.rb13
-rw-r--r--app/workers/incident_management/process_prometheus_alert_worker.rb69
-rw-r--r--changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml5
-rw-r--r--changelogs/unreleased/jdb-save-whitespace-setting.yml5
-rw-r--r--changelogs/unreleased/update-rack-timeout.yml5
-rw-r--r--changelogs/unreleased/update-secure-smau-metric.yml5
-rw-r--r--config/initializers/rack_timeout.rb2
-rw-r--r--config/settings.rb7
-rw-r--r--db/post_migrate/20200618152212_update_secure_smau_index.rb23
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql25
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json78
-rw-r--r--doc/api/graphql/reference/index.md5
-rw-r--r--doc/raketasks/backup_restore.md8
-rw-r--r--locale/gitlab.pot39
-rw-r--r--package.json2
-rwxr-xr-xqa/bin/rubymine9
-rw-r--r--spec/config/settings_spec.rb22
-rw-r--r--spec/factories/notes.rb5
-rw-r--r--spec/features/clusters/installing_applications_shared_examples.rb38
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js6
-rw-r--r--spec/frontend/alert_management/mocks/alerts.json3
-rw-r--r--spec/frontend/diffs/store/actions_spec.js6
-rw-r--r--spec/frontend/diffs/store/utils_spec.js22
-rw-r--r--spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap15
-rw-r--r--spec/frontend/monitoring/components/create_dashboard_modal_spec.js48
-rw-r--r--spec/frontend/monitoring/components/dashboard_header_spec.js160
-rw-r--r--spec/frontend/monitoring/components/dashboard_spec.js2
-rw-r--r--spec/frontend/monitoring/components/dashboards_dropdown_spec.js161
-rw-r--r--spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js111
-rw-r--r--spec/frontend/monitoring/mock_data.js10
-rw-r--r--spec/frontend/monitoring/store/mutations_spec.js1
-rw-r--r--spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap12
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js2
-rw-r--r--spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/repository/components/preview/index_spec.js3
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/note_type_spec.rb1
-rw-r--r--spec/graphql/types/tree/blob_type_spec.rb2
-rw-r--r--spec/graphql/types/tree/tree_entry_type_spec.rb2
-rw-r--r--spec/graphql/types/user_type_spec.rb1
-rw-r--r--spec/helpers/ci/pipeline_schedules_helper_spec.rb24
-rw-r--r--spec/helpers/environments_helper_spec.rb1
-rw-r--r--spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb32
-rw-r--r--spec/services/projects/prometheus/alerts/create_events_service_spec.rb312
-rw-r--r--spec/services/projects/prometheus/alerts/notify_service_spec.rb90
-rw-r--r--spec/workers/incident_management/process_prometheus_alert_worker_spec.rb132
-rw-r--r--yarn.lock8
97 files changed, 1043 insertions, 1152 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 72ef88cfeee..9cce7ffac84 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -222,6 +222,7 @@ coverage-frontend:
- run_timed_command "retry yarn install --frozen-lockfile"
script:
- run_timed_command "yarn node scripts/frontend/merge_coverage_frontend.js"
+ coverage: '/^Statements\s*:\s*?(\d+(?:\.\d+)?)%/'
artifacts:
name: coverage-frontend
expire_in: 31d
diff --git a/Gemfile b/Gemfile
index c86874201fc..62d6d3767ed 100644
--- a/Gemfile
+++ b/Gemfile
@@ -164,6 +164,8 @@ gem 'diff_match_patch', '~> 0.1.0'
# Application server
gem 'rack', '~> 2.0.9'
+# https://github.com/sharpstone/rack-timeout/blob/master/README.md#rails-apps-manually
+gem 'rack-timeout', '~> 0.5.1', require: 'rack/timeout/base'
group :unicorn do
gem 'unicorn', '~> 5.5'
@@ -173,7 +175,6 @@ end
group :puma do
gem 'gitlab-puma', '~> 4.3.3.gitlab.2', require: false
gem 'gitlab-puma_worker_killer', '~> 0.1.1.gitlab.1', require: false
- gem 'rack-timeout', require: false
end
# State machine
diff --git a/Gemfile.lock b/Gemfile.lock
index 70f2dfafe3a..f68fd455352 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -817,7 +817,7 @@ GEM
rack
rack-test (1.1.0)
rack (>= 1.0, < 3)
- rack-timeout (0.5.1)
+ rack-timeout (0.5.2)
rails (6.0.3.1)
actioncable (= 6.0.3.1)
actionmailbox (= 6.0.3.1)
@@ -1350,7 +1350,7 @@ DEPENDENCIES
rack-cors (~> 1.0.6)
rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
- rack-timeout
+ rack-timeout (~> 0.5.1)
rails (~> 6.0.3.1)
rails-controller-testing
rails-i18n (~> 6.0)
diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
index 9042d51aecf..39717ab609f 100644
--- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
+++ b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue
@@ -24,7 +24,7 @@ export default {
return { ...author, id: id?.split('/').pop() };
},
iconHtml() {
- return spriteIcon('user');
+ return spriteIcon(this.note?.systemNoteIconName);
},
},
};
diff --git a/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql b/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
index c72300e9757..74b425717a0 100644
--- a/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
+++ b/app/assets/javascripts/alert_management/graphql/fragments/alert_note.fragment.graphql
@@ -1,16 +1,17 @@
#import "~/graphql_shared/fragments/author.fragment.graphql"
fragment AlertNote on Note {
+ id
+ author {
id
- author {
- id
- state
- ...Author
- }
- body
- bodyHtml
- createdAt
- discussion {
- id
- }
+ state
+ ...Author
+ }
+ body
+ bodyHtml
+ createdAt
+ discussion {
+ id
+ }
+ systemNoteIconName
}
diff --git a/app/assets/javascripts/diffs/constants.js b/app/assets/javascripts/diffs/constants.js
index 9269dacd582..e3dd882f3dc 100644
--- a/app/assets/javascripts/diffs/constants.js
+++ b/app/assets/javascripts/diffs/constants.js
@@ -1,3 +1,7 @@
+// The backend actually uses "hide_whitespace" while the frontend
+// uses "show whitspace" so these values are opposite what you might expect
+export const NO_SHOW_WHITESPACE = '1';
+export const SHOW_WHITESPACE = '0';
export const INLINE_DIFF_VIEW_TYPE = 'inline';
export const PARALLEL_DIFF_VIEW_TYPE = 'parallel';
export const MATCH_LINE_TYPE = 'match';
@@ -20,6 +24,7 @@ export const LINE_SIDE_LEFT = 'left-side';
export const LINE_SIDE_RIGHT = 'right-side';
export const DIFF_VIEW_COOKIE_NAME = 'diff_view';
+export const DIFF_WHITESPACE_COOKIE_NAME = 'diff_whitespace';
export const LINE_HOVER_CLASS_NAME = 'is-over';
export const LINE_UNFOLD_CLASS_NAME = 'unfold js-unfold';
export const CONTEXT_LINE_CLASS_NAME = 'diff-expanded';
@@ -35,7 +40,6 @@ export const MR_TREE_SHOW_KEY = 'mr_tree_show';
export const TREE_TYPE = 'tree';
export const TREE_LIST_STORAGE_KEY = 'mr_diff_tree_list';
-export const WHITESPACE_STORAGE_KEY = 'mr_show_whitespace';
export const TREE_LIST_WIDTH_STORAGE_KEY = 'mr_tree_list_width';
export const INITIAL_TREE_WIDTH = 320;
diff --git a/app/assets/javascripts/diffs/index.js b/app/assets/javascripts/diffs/index.js
index ce48e36bfd7..028f04095a9 100644
--- a/app/assets/javascripts/diffs/index.js
+++ b/app/assets/javascripts/diffs/index.js
@@ -1,11 +1,11 @@
import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import { parseBoolean } from '~/lib/utils/common_utils';
-import { getParameterValues } from '~/lib/utils/url_utility';
import FindFile from '~/vue_shared/components/file_finder/index.vue';
import eventHub from '../notes/event_hub';
import diffsApp from './components/app.vue';
-import { TREE_LIST_STORAGE_KEY } from './constants';
+import { TREE_LIST_STORAGE_KEY, DIFF_WHITESPACE_COOKIE_NAME } from './constants';
+import Cookies from 'js-cookie';
export default function initDiffsApp(store) {
const fileFinderEl = document.getElementById('js-diff-file-finder');
@@ -86,15 +86,16 @@ export default function initDiffsApp(store) {
}),
},
created() {
- let hideWhitespace = getParameterValues('w')[0];
const treeListStored = localStorage.getItem(TREE_LIST_STORAGE_KEY);
const renderTreeList = treeListStored !== null ? parseBoolean(treeListStored) : true;
this.setRenderTreeList(renderTreeList);
- if (!hideWhitespace) {
- hideWhitespace = this.showWhitespaceDefault ? '0' : '1';
+
+ // Set whitespace default as per user preferences unless cookie is already set
+ if (!Cookies.get(DIFF_WHITESPACE_COOKIE_NAME)) {
+ const hideWhitespace = this.showWhitespaceDefault ? '0' : '1';
+ this.setShowWhitespace({ showWhitespace: hideWhitespace !== '1' });
}
- this.setShowWhitespace({ showWhitespace: hideWhitespace !== '1' });
},
methods: {
...mapActions('diffs', ['setRenderTreeList', 'setShowWhitespace']),
@@ -114,7 +115,6 @@ export default function initDiffsApp(store) {
isFluidLayout: this.isFluidLayout,
dismissEndpoint: this.dismissEndpoint,
showSuggestPopover: this.showSuggestPopover,
- showWhitespaceDefault: this.showWhitespaceDefault,
},
});
},
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index a8d348e1836..6f903bd09f0 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -25,7 +25,6 @@ import {
DIFF_VIEW_COOKIE_NAME,
MR_TREE_SHOW_KEY,
TREE_LIST_STORAGE_KEY,
- WHITESPACE_STORAGE_KEY,
TREE_LIST_WIDTH_STORAGE_KEY,
OLD_LINE_KEY,
NEW_LINE_KEY,
@@ -38,6 +37,9 @@ import {
INLINE_DIFF_LINES_KEY,
PARALLEL_DIFF_LINES_KEY,
DIFFS_PER_PAGE,
+ DIFF_WHITESPACE_COOKIE_NAME,
+ SHOW_WHITESPACE,
+ NO_SHOW_WHITESPACE,
} from '../constants';
import { diffViewerModes } from '~/ide/constants';
@@ -484,11 +486,12 @@ export const setRenderTreeList = ({ commit }, renderTreeList) => {
export const setShowWhitespace = ({ commit }, { showWhitespace, pushState = false }) => {
commit(types.SET_SHOW_WHITESPACE, showWhitespace);
+ const w = showWhitespace ? SHOW_WHITESPACE : NO_SHOW_WHITESPACE;
- localStorage.setItem(WHITESPACE_STORAGE_KEY, showWhitespace);
+ Cookies.set(DIFF_WHITESPACE_COOKIE_NAME, w);
if (pushState) {
- historyPushState(mergeUrlParams({ w: showWhitespace ? '0' : '1' }, window.location.href));
+ historyPushState(mergeUrlParams({ w }, window.location.href));
}
eventHub.$emit('refetchDiffData');
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 87938ababed..1f165dd4971 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,10 +1,17 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
-import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
+import {
+ INLINE_DIFF_VIEW_TYPE,
+ DIFF_VIEW_COOKIE_NAME,
+ DIFF_WHITESPACE_COOKIE_NAME,
+} from '../../constants';
+import { getDefaultWhitespace } from '../utils';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
+const whiteSpaceFromQueryString = getParameterValues('w')[0];
+const whiteSpaceFromCookie = Cookies.get(DIFF_WHITESPACE_COOKIE_NAME);
export default () => ({
isLoading: true,
@@ -29,7 +36,7 @@ export default () => ({
commentForms: [],
highlightedRow: null,
renderTreeList: true,
- showWhitespace: true,
+ showWhitespace: getDefaultWhitespace(whiteSpaceFromQueryString, whiteSpaceFromCookie),
fileFinderVisible: false,
dismissEndpoint: '',
showSuggestPopover: true,
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index d261be1b550..bc85dd0a1d4 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -15,6 +15,8 @@ import {
TREE_TYPE,
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
+ SHOW_WHITESPACE,
+ NO_SHOW_WHITESPACE,
} from '../constants';
export function findDiffFile(files, match, matchKey = 'file_hash') {
@@ -701,3 +703,10 @@ export const allDiscussionWrappersExpanded = diff => {
return discussionsExpanded;
};
+
+export const getDefaultWhitespace = (queryString, cookie) => {
+ // Querystring should override stored cookie value
+ if (queryString) return queryString === SHOW_WHITESPACE;
+ if (cookie === NO_SHOW_WHITESPACE) return false;
+ return true;
+};
diff --git a/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue
new file mode 100644
index 00000000000..74799002b17
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/create_dashboard_modal.vue
@@ -0,0 +1,66 @@
+<script>
+import { GlButton, GlModal, GlSprintf } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { isSafeURL } from '~/lib/utils/url_utility';
+
+export default {
+ components: { GlButton, GlModal, GlSprintf },
+ props: {
+ modalId: {
+ type: String,
+ required: true,
+ },
+ projectPath: {
+ type: String,
+ required: true,
+ validator: isSafeURL,
+ },
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
+ },
+ methods: {
+ cancelHandler() {
+ this.$refs.modal.hide();
+ },
+ },
+ i18n: {
+ titleText: s__('Metrics|Create your dashboard configuration file'),
+ mainText: s__(
+ 'Metrics|To create a new dashboard, add a new YAML file to %{codeStart}.gitlab/dashboards%{codeEnd} at the root of this project.',
+ ),
+ },
+};
+</script>
+
+<template>
+ <gl-modal ref="modal" :modal-id="modalId" :title="$options.i18n.titleText">
+ <p>
+ <gl-sprintf :message="$options.i18n.mainText">
+ <template #code="{ content }">
+ <code>{{ content }}</code>
+ </template>
+ </gl-sprintf>
+ </p>
+ <template #modal-footer>
+ <gl-button category="secondary" @click="cancelHandler">{{ s__('Metrics|Cancel') }}</gl-button>
+ <gl-button
+ category="secondary"
+ variant="info"
+ target="_blank"
+ :href="addDashboardDocumentationPath"
+ data-testid="create-dashboard-modal-docs-button"
+ >
+ {{ s__('Metrics|View documentation') }}
+ </gl-button>
+ <gl-button
+ variant="success"
+ data-testid="create-dashboard-modal-repo-button"
+ :href="projectPath"
+ >
+ {{ s__('Metrics|Open repository') }}
+ </gl-button>
+ </template>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 28c588de468..f685d67751c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -72,6 +72,10 @@ export default {
type: String,
required: true,
},
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
settingsPath: {
type: String,
required: true,
@@ -395,6 +399,7 @@ export default {
v-if="showHeader"
ref="prometheusGraphsHeader"
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
+ :add-dashboard-documentation-path="addDashboardDocumentationPath"
:default-branch="defaultBranch"
:rearrange-panels-available="rearrangePanelsAvailable"
:custom-metrics-available="customMetricsAvailable"
diff --git a/app/assets/javascripts/monitoring/components/dashboard_header.vue b/app/assets/javascripts/monitoring/components/dashboard_header.vue
index b47adc9db11..4c973b6bb67 100644
--- a/app/assets/javascripts/monitoring/components/dashboard_header.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard_header.vue
@@ -8,6 +8,9 @@ import {
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
GlModal,
GlLoadingIcon,
GlSearchBoxByType,
@@ -23,6 +26,8 @@ import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_p
import DashboardsDropdown from './dashboards_dropdown.vue';
import RefreshButton from './refresh_button.vue';
+import CreateDashboardModal from './create_dashboard_modal.vue';
+import DuplicateDashboardModal from './duplicate_dashboard_modal.vue';
import TrackEventDirective from '~/vue_shared/directives/track_event';
import { getAddMetricTrackingOptions, timeRangeToUrl } from '../utils';
@@ -39,6 +44,9 @@ export default {
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
+ GlNewDropdown,
+ GlNewDropdownDivider,
+ GlNewDropdownItem,
GlSearchBoxByType,
GlModal,
CustomMetricsFormFields,
@@ -46,6 +54,8 @@ export default {
DateTimePicker,
DashboardsDropdown,
RefreshButton,
+ DuplicateDashboardModal,
+ CreateDashboardModal,
},
directives: {
GlModal: GlModalDirective,
@@ -95,6 +105,10 @@ export default {
type: Object,
required: true,
},
+ addDashboardDocumentationPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -108,8 +122,12 @@ export default {
'isUpdatingStarredValue',
'showEmptyState',
'dashboardTimezone',
+ 'projectPath',
]),
...mapGetters('monitoringDashboard', ['selectedDashboard', 'filteredEnvironments']),
+ isSystemDashboard() {
+ return this.selectedDashboard?.system_dashboard;
+ },
shouldShowEnvironmentsDropdownNoMatchedMsg() {
return !this.environmentsLoading && this.filteredEnvironments.length === 0;
},
@@ -129,6 +147,9 @@ export default {
displayUtc() {
return this.dashboardTimezone === timezones.UTC;
},
+ shouldShowActionsMenu() {
+ return Boolean(this.projectPath);
+ },
},
methods: {
...mapActions('monitoringDashboard', ['filterEnvironments', 'toggleStarredValue']),
@@ -136,6 +157,7 @@ export default {
const params = {
dashboard: encodeURIComponent(dashboard.path),
};
+
redirectTo(mergeUrlParams(params, window.location.href));
},
debouncedEnvironmentsSearch: debounce(function environmentsSearchOnInput(searchTerm) {
@@ -162,13 +184,15 @@ export default {
this.$refs.customMetricsForm.submit();
},
},
- addMetric: {
- title: s__('Metrics|Add metric'),
- modalId: 'add-metric',
+ modalIds: {
+ addMetric: 'addMetric',
+ createDashboard: 'createDashboard',
+ duplicateDashboard: 'duplicateDashboard',
},
i18n: {
starDashboard: s__('Metrics|Star dashboard'),
unstarDashboard: s__('Metrics|Unstar dashboard'),
+ addMetric: s__('Metrics|Add metric'),
},
timeRanges,
};
@@ -176,17 +200,20 @@ export default {
<template>
<div ref="prometheusGraphsHeader">
- <div class="mb-2 pr-2 d-flex d-sm-block">
+ <div class="mb-2 mr-2 d-flex d-sm-block">
<dashboards-dropdown
id="monitor-dashboards-dropdown"
data-qa-selector="dashboards_filter_dropdown"
class="flex-grow-1"
toggle-class="dropdown-menu-toggle"
:default-branch="defaultBranch"
+ :modal-id="$options.modalIds.duplicateDashboard"
@selectDashboard="selectDashboard"
/>
</div>
+ <span aria-hidden="true" class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"></span>
+
<div class="mb-2 pr-2 d-flex d-sm-block">
<gl-dropdown
id="monitor-environments-dropdown"
@@ -290,17 +317,17 @@ export default {
<div v-if="addingMetricsAvailable" class="mb-2 mr-2 d-flex d-sm-block">
<gl-deprecated-button
ref="addMetricBtn"
- v-gl-modal="$options.addMetric.modalId"
+ v-gl-modal="$options.modalIds.addMetric"
variant="outline-success"
data-qa-selector="add_metric_button"
class="flex-grow-1"
>
- {{ $options.addMetric.title }}
+ {{ $options.i18n.addMetric }}
</gl-deprecated-button>
<gl-modal
ref="addMetricModal"
- :modal-id="$options.addMetric.modalId"
- :title="$options.addMetric.title"
+ :modal-id="$options.modalIds.addMetric"
+ :title="$options.i18n.addMetric"
>
<form ref="customMetricsForm" :action="customMetricsPath" method="post">
<custom-metrics-form-fields
@@ -353,6 +380,50 @@ export default {
{{ __('View full dashboard') }} <icon name="external-link" />
</gl-deprecated-button>
</div>
+
+ <template v-if="shouldShowActionsMenu">
+ <span aria-hidden="true" class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"></span>
+
+ <div class="gl-mb-3 gl-mr-3 d-flex d-sm-block">
+ <gl-new-dropdown
+ v-gl-tooltip
+ right
+ class="gl-flex-grow-1"
+ data-testid="actions-menu"
+ :title="s__('Metrics|Create dashboard')"
+ :icon="'plus-square'"
+ >
+ <gl-new-dropdown-item
+ v-gl-modal="$options.modalIds.createDashboard"
+ data-testid="action-create-dashboard"
+ >{{ s__('Metrics|Create new dashboard') }}</gl-new-dropdown-item
+ >
+
+ <create-dashboard-modal
+ data-testid="create-dashboard-modal"
+ :add-dashboard-documentation-path="addDashboardDocumentationPath"
+ :modal-id="$options.modalIds.createDashboard"
+ :project-path="projectPath"
+ />
+
+ <template v-if="isSystemDashboard">
+ <gl-new-dropdown-divider />
+ <gl-new-dropdown-item
+ ref="duplicateDashboardItem"
+ v-gl-modal="$options.modalIds.duplicateDashboard"
+ data-testid="action-duplicate-dashboard"
+ >
+ {{ s__('Metrics|Duplicate current dashboard') }}
+ </gl-new-dropdown-item>
+ </template>
+ </gl-new-dropdown>
+ </div>
+ </template>
</div>
+ <duplicate-dashboard-modal
+ :default-branch="defaultBranch"
+ :modal-id="$options.modalIds.duplicateDashboard"
+ @dashboardDuplicated="selectDashboard"
+ />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
index 8b86890715f..0e96d57b8a1 100644
--- a/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
+++ b/app/assets/javascripts/monitoring/components/dashboards_dropdown.vue
@@ -1,19 +1,14 @@
<script>
import { mapState, mapActions, mapGetters } from 'vuex';
import {
- GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
- GlModal,
- GlLoadingIcon,
GlModalDirective,
} from '@gitlab/ui';
-import { s__ } from '~/locale';
-import DuplicateDashboardForm from './duplicate_dashboard_form.vue';
const events = {
selectDashboard: 'selectDashboard',
@@ -21,16 +16,12 @@ const events = {
export default {
components: {
- GlAlert,
GlIcon,
GlDropdown,
GlDropdownItem,
GlDropdownHeader,
GlDropdownDivider,
GlSearchBoxByType,
- GlModal,
- GlLoadingIcon,
- DuplicateDashboardForm,
},
directives: {
GlModal: GlModalDirective,
@@ -40,12 +31,13 @@ export default {
type: String,
required: true,
},
+ modalId: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
- alert: null,
- loading: false,
- form: {},
searchTerm: '',
};
},
@@ -76,10 +68,6 @@ export default {
nonStarredDashboards() {
return this.filteredDashboards.filter(({ starred }) => !starred);
},
-
- okButtonText() {
- return this.loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate');
- },
},
methods: {
...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
@@ -89,37 +77,6 @@ export default {
selectDashboard(dashboard) {
this.$emit(events.selectDashboard, dashboard);
},
- ok(bvModalEvt) {
- // Prevent modal from hiding in case submit fails
- bvModalEvt.preventDefault();
-
- this.loading = true;
- this.alert = null;
- this.duplicateSystemDashboard(this.form)
- .then(createdDashboard => {
- this.loading = false;
- this.alert = null;
-
- // Trigger hide modal as submit is successful
- this.$refs.duplicateDashboardModal.hide();
-
- // Dashboards in the default branch become available immediately.
- // Not so in other branches, so we refresh the current dashboard
- const dashboard =
- this.form.branch === this.defaultBranch ? createdDashboard : this.selectedDashboard;
- this.$emit(events.selectDashboard, dashboard);
- })
- .catch(error => {
- this.loading = false;
- this.alert = error;
- });
- },
- hide() {
- this.alert = null;
- },
- formChange(form) {
- this.form = form;
- },
},
};
</script>
@@ -178,32 +135,14 @@ export default {
{{ __('No matching results') }}
</div>
+ <!--
+ This Duplicate Dashboard item will be removed from the dashboards dropdown
+ in https://gitlab.com/gitlab-org/gitlab/-/issues/223223
+ -->
<template v-if="isSystemDashboard">
<gl-dropdown-divider />
- <gl-modal
- ref="duplicateDashboardModal"
- modal-id="duplicateDashboardModal"
- :title="s__('Metrics|Duplicate dashboard')"
- ok-variant="success"
- @ok="ok"
- @hide="hide"
- >
- <gl-alert v-if="alert" class="mb-3" variant="danger" @dismiss="alert = null">
- {{ alert }}
- </gl-alert>
- <duplicate-dashboard-form
- :dashboard="selectedDashboard"
- :default-branch="defaultBranch"
- @change="formChange"
- />
- <template #modal-ok>
- <gl-loading-icon v-if="loading" inline color="light" />
- {{ okButtonText }}
- </template>
- </gl-modal>
-
- <gl-dropdown-item ref="duplicateDashboardItem" v-gl-modal="'duplicateDashboardModal'">
+ <gl-dropdown-item v-gl-modal="modalId" data-testid="duplicateDashboardItem">
{{ s__('Metrics|Duplicate dashboard') }}
</gl-dropdown-item>
</template>
diff --git a/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue b/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue
new file mode 100644
index 00000000000..e64afc01fd9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/duplicate_dashboard_modal.vue
@@ -0,0 +1,95 @@
+<script>
+import { mapActions, mapGetters } from 'vuex';
+import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import DuplicateDashboardForm from './duplicate_dashboard_form.vue';
+
+const events = {
+ dashboardDuplicated: 'dashboardDuplicated',
+};
+
+export default {
+ components: { GlAlert, GlLoadingIcon, GlModal, DuplicateDashboardForm },
+ props: {
+ defaultBranch: {
+ type: String,
+ required: true,
+ },
+ modalId: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ alert: null,
+ loading: false,
+ form: {},
+ };
+ },
+ computed: {
+ ...mapGetters('monitoringDashboard', ['selectedDashboard']),
+ okButtonText() {
+ return this.loading ? s__('Metrics|Duplicating...') : s__('Metrics|Duplicate');
+ },
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', ['duplicateSystemDashboard']),
+ ok(bvModalEvt) {
+ // Prevent modal from hiding in case submit fails
+ bvModalEvt.preventDefault();
+
+ this.loading = true;
+ this.alert = null;
+ this.duplicateSystemDashboard(this.form)
+ .then(createdDashboard => {
+ this.loading = false;
+ this.alert = null;
+
+ // Trigger hide modal as submit is successful
+ this.$refs.duplicateDashboardModal.hide();
+
+ // Dashboards in the default branch become available immediately.
+ // Not so in other branches, so we refresh the current dashboard
+ const dashboard =
+ this.form.branch === this.defaultBranch ? createdDashboard : this.selectedDashboard;
+ this.$emit(events.dashboardDuplicated, dashboard);
+ })
+ .catch(error => {
+ this.loading = false;
+ this.alert = error;
+ });
+ },
+ hide() {
+ this.alert = null;
+ },
+ formChange(form) {
+ this.form = form;
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-modal
+ ref="duplicateDashboardModal"
+ :modal-id="modalId"
+ :title="s__('Metrics|Duplicate dashboard')"
+ ok-variant="success"
+ @ok="ok"
+ @hide="hide"
+ >
+ <gl-alert v-if="alert" class="mb-3" variant="danger" @dismiss="alert = null">
+ {{ alert }}
+ </gl-alert>
+ <duplicate-dashboard-form
+ :dashboard="selectedDashboard"
+ :default-branch="defaultBranch"
+ @change="formChange"
+ />
+ <template #modal-ok>
+ <gl-loading-icon v-if="loading" inline color="light" />
+ {{ okButtonText }}
+ </template>
+ </gl-modal>
+</template>
diff --git a/app/assets/javascripts/repository/components/breadcrumbs.vue b/app/assets/javascripts/repository/components/breadcrumbs.vue
index 8a11c3dcafe..45c343c3f7f 100644
--- a/app/assets/javascripts/repository/components/breadcrumbs.vue
+++ b/app/assets/javascripts/repository/components/breadcrumbs.vue
@@ -4,9 +4,9 @@ import { joinPaths, escapeFileUrl } from '~/lib/utils/url_utility';
import { __ } from '../../locale';
import Icon from '../../vue_shared/components/icon.vue';
import getRefMixin from '../mixins/get_ref';
-import projectShortPathQuery from '../queries/project_short_path.query.graphql';
-import projetPathQuery from '../queries/project_path.query.graphql';
-import permissionsQuery from '../queries/permissions.query.graphql';
+import getProjectShortPath from '../queries/getProjectShortPath.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
+import getPermissions from '../queries/getPermissions.query.graphql';
const ROW_TYPES = {
header: 'header',
@@ -23,13 +23,13 @@ export default {
},
apollo: {
projectShortPath: {
- query: projectShortPathQuery,
+ query: getProjectShortPath,
},
projectPath: {
- query: projetPathQuery,
+ query: getProjectPath,
},
userPermissions: {
- query: permissionsQuery,
+ query: getPermissions,
variables() {
return {
projectPath: this.projectPath,
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 0206a61487c..c5c99d56e2a 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -8,8 +8,8 @@ import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import getRefMixin from '../mixins/get_ref';
-import projectPathQuery from '../queries/project_path.query.graphql';
-import pathLastCommitQuery from '../queries/path_last_commit.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
+import pathLastCommit from '../queries/pathLastCommit.query.graphql';
export default {
components: {
@@ -28,10 +28,10 @@ export default {
mixins: [getRefMixin],
apollo: {
projectPath: {
- query: projectPathQuery,
+ query: getProjectPath,
},
commit: {
- query: pathLastCommitQuery,
+ query: pathLastCommit,
variables() {
return {
projectPath: this.projectPath,
@@ -102,7 +102,7 @@ export default {
<template v-else-if="commit">
<user-avatar-link
v-if="commit.author"
- :link-href="commit.author.webPath"
+ :link-href="commit.author.webUrl"
:img-src="commit.author.avatarUrl"
:img-size="40"
class="avatar-cell"
@@ -118,7 +118,7 @@ export default {
<div class="commit-detail flex-list">
<div class="commit-content qa-commit-content">
<gl-link
- :href="commit.webPath"
+ :href="commit.webUrl"
:class="{ 'font-italic': !commit.message }"
class="commit-row-message item-title"
v-html="commit.titleHtml"
@@ -135,7 +135,7 @@ export default {
<div class="committer">
<gl-link
v-if="commit.author"
- :href="commit.author.webPath"
+ :href="commit.author.webUrl"
class="commit-author-link js-user-link"
>
{{ commit.author.name }}
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
index ecf98ebe7db..f96523bb497 100644
--- a/app/assets/javascripts/repository/components/preview/index.vue
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -3,15 +3,15 @@ import $ from 'jquery';
import '~/behaviors/markdown/render_gfm';
import { GlLink, GlLoadingIcon } from '@gitlab/ui';
import { handleLocationHash } from '~/lib/utils/common_utils';
-import readmeQery from '../../queries/readme.query.graphql';
+import getReadmeQuery from '../../queries/getReadme.query.graphql';
export default {
apollo: {
readme: {
- query: readmeQery,
+ query: getReadmeQuery,
variables() {
return {
- url: this.blob.webPath,
+ url: this.blob.webUrl,
};
},
loadingKey: 'loading',
@@ -51,7 +51,7 @@ export default {
<div class="js-file-title file-title-flex-parent">
<div class="file-header-content">
<i aria-hidden="true" class="fa fa-file-text-o fa-fw"></i>
- <gl-link :href="blob.webPath">
+ <gl-link :href="blob.webUrl">
<strong>{{ blob.name }}</strong>
</gl-link>
</div>
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 4077e3f8cdc..c1f350dccd6 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -2,7 +2,7 @@
import { GlSkeletonLoading } from '@gitlab/ui';
import { sprintf, __ } from '../../../locale';
import getRefMixin from '../../mixins/get_ref';
-import projectPathQuery from '../../queries/project_path.query.graphql';
+import getProjectPath from '../../queries/getProjectPath.query.graphql';
import TableHeader from './header.vue';
import TableRow from './row.vue';
import ParentRow from './parent_row.vue';
@@ -17,7 +17,7 @@ export default {
mixins: [getRefMixin],
apollo: {
projectPath: {
- query: projectPathQuery,
+ query: getProjectPath,
},
},
props: {
@@ -96,7 +96,7 @@ export default {
:name="entry.name"
:path="entry.flatPath"
:type="entry.type"
- :url="entry.webUrl || entry.webPath"
+ :url="entry.webUrl"
:submodule-tree-url="entry.treeUrl"
:lfs-oid="entry.lfsOid"
:loading-path="loadingPath"
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 95e34280665..d5363016335 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -12,7 +12,7 @@ import { escapeFileUrl } from '~/lib/utils/url_utility';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import getRefMixin from '../../mixins/get_ref';
-import commitQuery from '../../queries/commit.query.graphql';
+import getCommit from '../../queries/getCommit.query.graphql';
export default {
components: {
@@ -29,7 +29,7 @@ export default {
},
apollo: {
commit: {
- query: commitQuery,
+ query: getCommit,
variables() {
return {
fileName: this.name,
diff --git a/app/assets/javascripts/repository/components/tree_content.vue b/app/assets/javascripts/repository/components/tree_content.vue
index 721cc6787dc..59ba1caa8c9 100644
--- a/app/assets/javascripts/repository/components/tree_content.vue
+++ b/app/assets/javascripts/repository/components/tree_content.vue
@@ -3,8 +3,8 @@ import createFlash from '~/flash';
import { __ } from '../../locale';
import FileTable from './table/index.vue';
import getRefMixin from '../mixins/get_ref';
-import filesQuery from '../queries/files.query.graphql';
-import projectPathQuery from '../queries/project_path.query.graphql';
+import getFiles from '../queries/getFiles.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
import FilePreview from './preview/index.vue';
import { readmeFile } from '../utils/readme';
@@ -18,7 +18,7 @@ export default {
mixins: [getRefMixin],
apollo: {
projectPath: {
- query: projectPathQuery,
+ query: getProjectPath,
},
},
props: {
@@ -70,7 +70,7 @@ export default {
return this.$apollo
.query({
- query: filesQuery,
+ query: getFiles,
variables: {
projectPath: this.projectPath,
ref: this.ref,
diff --git a/app/assets/javascripts/repository/log_tree.js b/app/assets/javascripts/repository/log_tree.js
index 704dd88aabe..cef17bf7acb 100644
--- a/app/assets/javascripts/repository/log_tree.js
+++ b/app/assets/javascripts/repository/log_tree.js
@@ -1,8 +1,8 @@
import { normalizeData } from 'ee_else_ce/repository/utils/commit';
import axios from '~/lib/utils/axios_utils';
-import commitsQuery from './queries/commits.query.graphql';
-import projectPathQuery from './queries/project_path.query.graphql';
-import refQuery from './queries/ref.query.graphql';
+import getCommits from './queries/getCommits.query.graphql';
+import getProjectPath from './queries/getProjectPath.query.graphql';
+import getRef from './queries/getRef.query.graphql';
let fetchpromise;
let resolvers = [];
@@ -22,8 +22,8 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
if (fetchpromise) return fetchpromise;
- const { projectPath } = client.readQuery({ query: projectPathQuery });
- const { escapedRef } = client.readQuery({ query: refQuery });
+ const { projectPath } = client.readQuery({ query: getProjectPath });
+ const { escapedRef } = client.readQuery({ query: getRef });
fetchpromise = axios
.get(
@@ -36,10 +36,10 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
)
.then(({ data, headers }) => {
const headerLogsOffset = headers['more-logs-offset'];
- const { commits } = client.readQuery({ query: commitsQuery });
+ const { commits } = client.readQuery({ query: getCommits });
const newCommitData = [...commits, ...normalizeData(data, path)];
client.writeQuery({
- query: commitsQuery,
+ query: getCommits,
data: { commits: newCommitData },
});
diff --git a/app/assets/javascripts/repository/mixins/get_ref.js b/app/assets/javascripts/repository/mixins/get_ref.js
index 1f1880a48c7..99d19b77c35 100644
--- a/app/assets/javascripts/repository/mixins/get_ref.js
+++ b/app/assets/javascripts/repository/mixins/get_ref.js
@@ -1,9 +1,9 @@
-import refQuery from '../queries/ref.query.graphql';
+import getRef from '../queries/getRef.query.graphql';
export default {
apollo: {
ref: {
- query: refQuery,
+ query: getRef,
manual: true,
result({ data, loading }) {
if (!loading) {
diff --git a/app/assets/javascripts/repository/mixins/preload.js b/app/assets/javascripts/repository/mixins/preload.js
index cb1d7f3aac9..cb6c2294679 100644
--- a/app/assets/javascripts/repository/mixins/preload.js
+++ b/app/assets/javascripts/repository/mixins/preload.js
@@ -1,12 +1,12 @@
-import filesQuery from '../queries/files.query.graphql';
+import getFiles from '../queries/getFiles.query.graphql';
import getRefMixin from './get_ref';
-import projectPathQuery from '../queries/project_path.query.graphql';
+import getProjectPath from '../queries/getProjectPath.query.graphql';
export default {
mixins: [getRefMixin],
apollo: {
projectPath: {
- query: projectPathQuery,
+ query: getProjectPath,
},
},
data() {
@@ -21,7 +21,7 @@ export default {
return this.$apollo
.query({
- query: filesQuery,
+ query: getFiles,
variables: {
projectPath: this.projectPath,
ref: this.ref,
diff --git a/app/assets/javascripts/repository/queries/commit.query.graphql b/app/assets/javascripts/repository/queries/getCommit.query.graphql
index d2c94733bb9..e4aeaaff8fe 100644
--- a/app/assets/javascripts/repository/queries/commit.query.graphql
+++ b/app/assets/javascripts/repository/queries/getCommit.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/repository/queries/commit.fragment.graphql"
-query Commit($fileName: String!, $type: String!, $path: String!) {
+query getCommit($fileName: String!, $type: String!, $path: String!) {
commit(path: $path, fileName: $fileName, type: $type) @client {
...TreeEntryCommit
}
diff --git a/app/assets/javascripts/repository/queries/commits.query.graphql b/app/assets/javascripts/repository/queries/getCommits.query.graphql
index 8747649f56f..0976b8f32d7 100644
--- a/app/assets/javascripts/repository/queries/commits.query.graphql
+++ b/app/assets/javascripts/repository/queries/getCommits.query.graphql
@@ -1,6 +1,6 @@
#import "ee_else_ce/repository/queries/commit.fragment.graphql"
-query Commits {
+query getCommits {
commits @client {
...TreeEntryCommit
}
diff --git a/app/assets/javascripts/repository/queries/files.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index 10843b0450f..2aaf5066b4a 100644
--- a/app/assets/javascripts/repository/queries/files.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -8,7 +8,7 @@ fragment TreeEntry on Entry {
type
}
-query Files(
+query getFiles(
$projectPath: ID!
$path: String
$ref: String!
@@ -22,7 +22,7 @@ query Files(
edges {
node {
...TreeEntry
- webPath
+ webUrl
}
}
pageInfo {
@@ -45,7 +45,7 @@ query Files(
edges {
node {
...TreeEntry
- webPath
+ webUrl
lfsOid
}
}
diff --git a/app/assets/javascripts/repository/queries/permissions.query.graphql b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
index 57976ca2689..092fa44e2d0 100644
--- a/app/assets/javascripts/repository/queries/permissions.query.graphql
+++ b/app/assets/javascripts/repository/queries/getPermissions.query.graphql
@@ -1,4 +1,4 @@
-query Permissions($projectPath: ID!) {
+query getPermissions($projectPath: ID!) {
project(fullPath: $projectPath) {
userPermissions {
pushCode
diff --git a/app/assets/javascripts/repository/queries/getProjectPath.query.graphql b/app/assets/javascripts/repository/queries/getProjectPath.query.graphql
new file mode 100644
index 00000000000..74e73e07577
--- /dev/null
+++ b/app/assets/javascripts/repository/queries/getProjectPath.query.graphql
@@ -0,0 +1,3 @@
+query getProjectPath {
+ projectPath
+}
diff --git a/app/assets/javascripts/repository/queries/project_short_path.query.graphql b/app/assets/javascripts/repository/queries/getProjectShortPath.query.graphql
index e6abe9d78cd..34eb26598c2 100644
--- a/app/assets/javascripts/repository/queries/project_short_path.query.graphql
+++ b/app/assets/javascripts/repository/queries/getProjectShortPath.query.graphql
@@ -1,3 +1,3 @@
-query ProjectShortPath {
+query getProjectShortPath {
projectShortPath @client
}
diff --git a/app/assets/javascripts/repository/queries/readme.query.graphql b/app/assets/javascripts/repository/queries/getReadme.query.graphql
index 38ecc2e1bb2..cf056330133 100644
--- a/app/assets/javascripts/repository/queries/readme.query.graphql
+++ b/app/assets/javascripts/repository/queries/getReadme.query.graphql
@@ -1,4 +1,4 @@
-query Readme($url: String!) {
+query getReadme($url: String!) {
readme(url: $url) @client {
html
}
diff --git a/app/assets/javascripts/repository/queries/ref.query.graphql b/app/assets/javascripts/repository/queries/getRef.query.graphql
index e3498acd688..91afb751626 100644
--- a/app/assets/javascripts/repository/queries/ref.query.graphql
+++ b/app/assets/javascripts/repository/queries/getRef.query.graphql
@@ -1,4 +1,4 @@
-query Ref {
+query getRef {
ref @client
escapedRef @client
}
diff --git a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
index 8dc00fd9c7b..f54f09fd647 100644
--- a/app/assets/javascripts/repository/queries/path_last_commit.query.graphql
+++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
@@ -1,4 +1,4 @@
-query PathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
+query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
project(fullPath: $projectPath) {
repository {
tree(path: $path, ref: $ref) {
@@ -8,14 +8,14 @@ query PathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
titleHtml
description
message
- webPath
+ webUrl
authoredDate
authorName
authorGravatar
author {
name
avatarUrl
- webPath
+ webUrl
}
signatureHtml
pipelines(ref: $ref, first: 1) {
diff --git a/app/assets/javascripts/repository/queries/project_path.query.graphql b/app/assets/javascripts/repository/queries/project_path.query.graphql
deleted file mode 100644
index 462ae5382a1..00000000000
--- a/app/assets/javascripts/repository/queries/project_path.query.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-query ProjectPath {
- projectPath
-}
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index b4ec1d5cb6c..be5165da545 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -23,8 +23,6 @@ module Types
description: 'Timestamp of when the commit was authored'
field :web_url, type: GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the commit'
- field :web_path, type: GraphQL::STRING_TYPE, null: false,
- description: 'Web path of the commit'
field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
description: 'Rendered HTML of the commit signature'
field :author_name, type: GraphQL::STRING_TYPE, null: true,
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index 8755b4ccad5..5d41f0032bd 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -27,6 +27,8 @@ module Types
field :system, GraphQL::BOOLEAN_TYPE,
null: false,
description: 'Indicates whether this note was created by the system or by a user'
+ field :system_note_icon_name, GraphQL::STRING_TYPE, null: true,
+ description: 'Name of the icon corresponding to a system note'
field :body, GraphQL::STRING_TYPE,
null: false,
@@ -46,6 +48,10 @@ module Types
field :confidential, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates if this note is confidential',
method: :confidential?
+
+ def system_note_icon_name
+ SystemNoteHelper.system_note_icon_name(object) if object.system?
+ end
end
end
end
diff --git a/app/graphql/types/tree/blob_type.rb b/app/graphql/types/tree/blob_type.rb
index 4fdb4570b7d..22349203519 100644
--- a/app/graphql/types/tree/blob_type.rb
+++ b/app/graphql/types/tree/blob_type.rb
@@ -12,8 +12,6 @@ module Types
field :web_url, GraphQL::STRING_TYPE, null: true,
description: 'Web URL of the blob'
- field :web_path, GraphQL::STRING_TYPE, null: true,
- description: 'Web path of the blob'
field :lfs_oid, GraphQL::STRING_TYPE, null: true,
description: 'LFS ID of the blob',
resolve: -> (blob, args, ctx) do
diff --git a/app/graphql/types/tree/tree_entry_type.rb b/app/graphql/types/tree/tree_entry_type.rb
index aff2e025761..81a7a7e66ae 100644
--- a/app/graphql/types/tree/tree_entry_type.rb
+++ b/app/graphql/types/tree/tree_entry_type.rb
@@ -13,8 +13,6 @@ module Types
field :web_url, GraphQL::STRING_TYPE, null: true,
description: 'Web URL for the tree entry (directory)'
- field :web_path, GraphQL::STRING_TYPE, null: true,
- description: 'Web path for the tree entry (directory)'
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb
index f636f1b02b2..ab3c84ea539 100644
--- a/app/graphql/types/user_type.rb
+++ b/app/graphql/types/user_type.rb
@@ -22,8 +22,6 @@ module Types
description: "URL of the user's avatar"
field :web_url, GraphQL::STRING_TYPE, null: false,
description: 'Web URL of the user'
- field :web_path, GraphQL::STRING_TYPE, null: false,
- description: 'Web path of the user'
field :todos, Types::TodoType.connection_type, null: false,
resolver: Resolvers::TodoResolver,
description: 'Todos of the user'
diff --git a/app/helpers/ci/pipeline_schedules_helper.rb b/app/helpers/ci/pipeline_schedules_helper.rb
new file mode 100644
index 00000000000..20e5c90a60e
--- /dev/null
+++ b/app/helpers/ci/pipeline_schedules_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Ci
+ module PipelineSchedulesHelper
+ def timezone_data
+ ActiveSupport::TimeZone.all.map do |timezone|
+ {
+ name: timezone.name,
+ offset: timezone.now.utc_offset,
+ identifier: timezone.tzinfo.identifier
+ }
+ end
+ end
+ end
+end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 41a255434af..4e5d12de8d3 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -92,6 +92,7 @@ module EnvironmentsHelper
def static_metrics_data
{
'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add-dashboard-documentation-path' => help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path' => image_path('illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path' => image_path('illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path' => image_path('illustrations/monitoring/no_data.svg'),
diff --git a/app/helpers/pipeline_schedules_helper.rb b/app/helpers/pipeline_schedules_helper.rb
deleted file mode 100644
index 0e166106b32..00000000000
--- a/app/helpers/pipeline_schedules_helper.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module PipelineSchedulesHelper
- def timezone_data
- ActiveSupport::TimeZone.all.map do |timezone|
- {
- name: timezone.name,
- offset: timezone.now.utc_offset,
- identifier: timezone.tzinfo.identifier
- }
- end
- end
-end
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index cff935d51b5..e0077db8d5c 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -18,10 +18,6 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path))
end
- def web_path
- Gitlab::Routing.url_helpers.project_blob_path(blob.repository.project, File.join(blob.commit_id, blob.path))
- end
-
private
def load_all_blob_data
diff --git a/app/presenters/commit_presenter.rb b/app/presenters/commit_presenter.rb
index 998e5160f8b..9ded00fcb7a 100644
--- a/app/presenters/commit_presenter.rb
+++ b/app/presenters/commit_presenter.rb
@@ -21,10 +21,6 @@ class CommitPresenter < Gitlab::View::Presenter::Delegated
url_builder.build(commit)
end
- def web_path
- url_builder.build(commit, only_path: true)
- end
-
def signature_html
return unless commit.has_signature?
diff --git a/app/presenters/tree_entry_presenter.rb b/app/presenters/tree_entry_presenter.rb
index 216b3b0d4c9..7bb10cd1455 100644
--- a/app/presenters/tree_entry_presenter.rb
+++ b/app/presenters/tree_entry_presenter.rb
@@ -6,8 +6,4 @@ class TreeEntryPresenter < Gitlab::View::Presenter::Delegated
def web_url
Gitlab::Routing.url_helpers.project_tree_url(tree.repository.project, File.join(tree.commit_id, tree.path))
end
-
- def web_path
- Gitlab::Routing.url_helpers.project_tree_path(tree.repository.project, File.join(tree.commit_id, tree.path))
- end
end
diff --git a/app/presenters/user_presenter.rb b/app/presenters/user_presenter.rb
index 2a4d6a070f8..14ef53e9ec8 100644
--- a/app/presenters/user_presenter.rb
+++ b/app/presenters/user_presenter.rb
@@ -6,8 +6,4 @@ class UserPresenter < Gitlab::View::Presenter::Delegated
def web_url
Gitlab::Routing.url_helpers.user_url(user)
end
-
- def web_path
- Gitlab::Routing.url_helpers.user_path(user)
- end
end
diff --git a/app/services/projects/prometheus/alerts/create_events_service.rb b/app/services/projects/prometheus/alerts/create_events_service.rb
deleted file mode 100644
index 4fcf841314b..00000000000
--- a/app/services/projects/prometheus/alerts/create_events_service.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-module Projects
- module Prometheus
- module Alerts
- # Persists a series of Prometheus alert events as list of PrometheusAlertEvent.
- class CreateEventsService < BaseService
- def execute
- create_events_from(alerts)
- end
-
- private
-
- def create_events_from(alerts)
- Array.wrap(alerts).map { |alert| create_event(alert) }.compact
- end
-
- def create_event(payload)
- parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: payload)
-
- return unless parsed_alert.valid?
-
- if parsed_alert.gitlab_managed?
- create_managed_prometheus_alert_event(parsed_alert)
- else
- create_self_managed_prometheus_alert_event(parsed_alert)
- end
- end
-
- def alerts
- params['alerts']
- end
-
- def find_alert(metric)
- Projects::Prometheus::AlertsFinder
- .new(project: project, metric: metric)
- .execute
- .first
- end
-
- def create_managed_prometheus_alert_event(parsed_alert)
- alert = find_alert(parsed_alert.metric_id)
- event = PrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, alert, parsed_alert.gitlab_fingerprint)
-
- set_status(parsed_alert, event)
- end
-
- def create_self_managed_prometheus_alert_event(parsed_alert)
- event = SelfManagedPrometheusAlertEvent.find_or_initialize_by_payload_key(parsed_alert.project, parsed_alert.gitlab_fingerprint) do |event|
- event.environment = parsed_alert.environment
- event.title = parsed_alert.title
- event.query_expression = parsed_alert.full_query
- end
-
- set_status(parsed_alert, event)
- end
-
- def set_status(parsed_alert, event)
- persisted = case parsed_alert.status
- when 'firing'
- event.fire(parsed_alert.starts_at)
- when 'resolved'
- event.resolve(parsed_alert.ends_at)
- end
-
- event if persisted
- end
- end
- end
- end
-end
diff --git a/app/services/projects/prometheus/alerts/notify_service.rb b/app/services/projects/prometheus/alerts/notify_service.rb
index 877a4f99a94..4b3aed2d1d9 100644
--- a/app/services/projects/prometheus/alerts/notify_service.rb
+++ b/app/services/projects/prometheus/alerts/notify_service.rb
@@ -23,9 +23,7 @@ module Projects
return unauthorized unless valid_alert_manager_token?(token)
process_prometheus_alerts
- persist_events
send_alert_email if send_email?
- process_incident_issues if process_issues?
ServiceResponse.success
end
@@ -132,13 +130,6 @@ module Projects
.prometheus_alerts_fired(project, firings)
end
- def process_incident_issues
- alerts.each do |alert|
- IncidentManagement::ProcessPrometheusAlertWorker
- .perform_async(project.id, alert.to_h)
- end
- end
-
def process_prometheus_alerts
alerts.each do |alert|
AlertManagement::ProcessPrometheusAlertService
@@ -147,10 +138,6 @@ module Projects
end
end
- def persist_events
- CreateEventsService.new(project, nil, params).execute
- end
-
def bad_request
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
end
diff --git a/app/workers/incident_management/process_prometheus_alert_worker.rb b/app/workers/incident_management/process_prometheus_alert_worker.rb
index e405bc2c2d2..4b778f6a621 100644
--- a/app/workers/incident_management/process_prometheus_alert_worker.rb
+++ b/app/workers/incident_management/process_prometheus_alert_worker.rb
@@ -9,68 +9,13 @@ module IncidentManagement
worker_resource_boundary :cpu
def perform(project_id, alert_hash)
- project = find_project(project_id)
- return unless project
-
- parsed_alert = Gitlab::Alerting::Alert.new(project: project, payload: alert_hash)
- event = find_prometheus_alert_event(parsed_alert)
-
- if event&.resolved?
- issue = event.related_issues.order_created_at_desc.detect(&:opened?)
-
- close_issue(project, issue)
- else
- issue = create_issue(project, alert_hash)
-
- relate_issue_to_event(event, issue)
- end
- end
-
- private
-
- def find_project(project_id)
- Project.find_by_id(project_id)
- end
-
- def find_prometheus_alert_event(alert)
- if alert.gitlab_managed?
- find_gitlab_managed_event(alert)
- else
- find_self_managed_event(alert)
- end
- end
-
- def find_gitlab_managed_event(alert)
- PrometheusAlertEvent.find_by_payload_key(alert.gitlab_fingerprint)
- end
-
- def find_self_managed_event(alert)
- SelfManagedPrometheusAlertEvent.find_by_payload_key(alert.gitlab_fingerprint)
- end
-
- def create_issue(project, alert)
- IncidentManagement::CreateIssueService
- .new(project, alert)
- .execute
- .dig(:issue)
- end
-
- def close_issue(project, issue)
- return if issue.blank? || issue.closed?
-
- processed_issue = Issues::CloseService
- .new(project, User.alert_bot)
- .execute(issue, system_note: false)
-
- SystemNoteService.auto_resolve_prometheus_alert(issue, project, User.alert_bot) if processed_issue.reset.closed?
- end
-
- def relate_issue_to_event(event, issue)
- return unless event && issue
-
- if event.related_issues.exclude?(issue)
- event.related_issues << issue
- end
+ # no-op
+ #
+ # This worker is not scheduled anymore since
+ # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35943
+ # and will be removed completely via
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/227146
+ # in 14.0.
end
end
end
diff --git a/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml b/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml
new file mode 100644
index 00000000000..71d6a5f34a9
--- /dev/null
+++ b/changelogs/unreleased/astoicescu-addMetricsDashboardActionsMenu.yml
@@ -0,0 +1,5 @@
+---
+title: Add metrics settings menu to dashboard header
+merge_request: 35028
+author:
+type: added
diff --git a/changelogs/unreleased/jdb-save-whitespace-setting.yml b/changelogs/unreleased/jdb-save-whitespace-setting.yml
new file mode 100644
index 00000000000..1785accf764
--- /dev/null
+++ b/changelogs/unreleased/jdb-save-whitespace-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Save show whitespace changes
+merge_request: 35806
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-rack-timeout.yml b/changelogs/unreleased/update-rack-timeout.yml
new file mode 100644
index 00000000000..a7cafe7255f
--- /dev/null
+++ b/changelogs/unreleased/update-rack-timeout.yml
@@ -0,0 +1,5 @@
+---
+title: Update `rack-timeout` to `0.5.2`
+merge_request: 36289
+author:
+type: changed
diff --git a/changelogs/unreleased/update-secure-smau-metric.yml b/changelogs/unreleased/update-secure-smau-metric.yml
new file mode 100644
index 00000000000..b07ec45a9f3
--- /dev/null
+++ b/changelogs/unreleased/update-secure-smau-metric.yml
@@ -0,0 +1,5 @@
+---
+title: Report all unique users for Secure scanners
+merge_request: 33881
+author:
+type: changed
diff --git a/config/initializers/rack_timeout.rb b/config/initializers/rack_timeout.rb
index 5d5a5fcf980..e217398ee7d 100644
--- a/config/initializers/rack_timeout.rb
+++ b/config/initializers/rack_timeout.rb
@@ -10,8 +10,6 @@
# logged and we should fix the potential timeout issue in the code itself.
if Gitlab::Runtime.puma? && !Rails.env.test?
- require 'rack/timeout/base'
-
Rack::Timeout::Logger.level = Logger::ERROR
Gitlab::Application.configure do |config|
diff --git a/config/settings.rb b/config/settings.rb
index 99f1b85202e..c681fa32491 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -184,14 +184,15 @@ class Settings < Settingslogic
URI.parse(url_without_path).host
end
- # Runs at a random time of day on a consistent day of the week based on
+ # Runs at a consistent random time of day on a day of the week based on
# the instance UUID. This is to balance the load on the service receiving
# these pings. The sidekiq job handles temporary http failures.
def cron_for_usage_ping
- hour = rand(24)
- minute = rand(60)
# Set a default UUID for the case when the UUID hasn't been initialized.
uuid = Gitlab::CurrentSettings.uuid || 'uuid-not-set'
+
+ minute = Digest::MD5.hexdigest(uuid + 'minute').to_i(16) % 60
+ hour = Digest::MD5.hexdigest(uuid + 'hour').to_i(16) % 24
day_of_week = Digest::MD5.hexdigest(uuid).to_i(16) % 7
"#{minute} #{hour} * * #{day_of_week}"
diff --git a/db/post_migrate/20200618152212_update_secure_smau_index.rb b/db/post_migrate/20200618152212_update_secure_smau_index.rb
new file mode 100644
index 00000000000..ba989c279be
--- /dev/null
+++ b/db/post_migrate/20200618152212_update_secure_smau_index.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class UpdateSecureSmauIndex < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_secure_ci_builds_on_user_id_created_at'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(
+ :ci_builds,
+ [:user_id, :created_at],
+ where: "(((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text])))",
+ name: INDEX_NAME
+ )
+ end
+
+ def down
+ remove_concurrent_index_by_name :ci_builds, INDEX_NAME
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 20e27b4350d..796ece6bc93 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -20079,6 +20079,8 @@ CREATE UNIQUE INDEX index_scim_identities_on_user_id_and_group_id ON public.scim
CREATE UNIQUE INDEX index_scim_oauth_access_tokens_on_group_id_and_token_encrypted ON public.scim_oauth_access_tokens USING btree (group_id, token_encrypted);
+CREATE INDEX index_secure_ci_builds_on_user_id_created_at ON public.ci_builds USING btree (user_id, created_at) WHERE (((type)::text = 'Ci::Build'::text) AND ((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('license_scanning'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text])));
+
CREATE INDEX index_security_ci_builds_on_name_and_id ON public.ci_builds USING btree (name, id) WHERE (((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text, ('secret_detection'::character varying)::text, ('license_scanning'::character varying)::text])) AND ((type)::text = 'Ci::Build'::text));
CREATE INDEX index_self_managed_prometheus_alert_events_on_environment_id ON public.self_managed_prometheus_alert_events USING btree (environment_id);
@@ -23560,6 +23562,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200618105638
20200618134223
20200618134723
+20200618152212
20200619000316
20200619154527
20200619154528
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 1c792172ddb..e27988b64ab 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -814,11 +814,6 @@ type Blob implements Entry {
type: EntryType!
"""
- Web path of the blob
- """
- webPath: String
-
- """
Web URL of the blob
"""
webUrl: String
@@ -1222,11 +1217,6 @@ type Commit {
titleHtml: String
"""
- Web path of the commit
- """
- webPath: String!
-
- """
Web URL of the commit
"""
webUrl: String!
@@ -8266,6 +8256,11 @@ type Note implements ResolvableInterface {
system: Boolean!
"""
+ Name of the icon corresponding to a system note
+ """
+ systemNoteIconName: String
+
+ """
Timestamp of the note's last activity
"""
updatedAt: Time!
@@ -13388,11 +13383,6 @@ type TreeEntry implements Entry {
type: EntryType!
"""
- Web path for the tree entry (directory)
- """
- webPath: String
-
- """
Web URL for the tree entry (directory)
"""
webUrl: String
@@ -14294,11 +14284,6 @@ type User {
username: String!
"""
- Web path of the user
- """
- webPath: String!
-
- """
Web URL of the user
"""
webUrl: String!
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 0d282c49aad..aa886021e15 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -2129,20 +2129,6 @@
"deprecationReason": null
},
{
- "name": "webPath",
- "description": "Web path of the blob",
- "args": [
-
- ],
- "type": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
"name": "webUrl",
"description": "Web URL of the blob",
"args": [
@@ -3305,24 +3291,6 @@
"deprecationReason": null
},
{
- "name": "webPath",
- "description": "Web path of the commit",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
"name": "webUrl",
"description": "Web URL of the commit",
"args": [
@@ -24634,6 +24602,20 @@
"deprecationReason": null
},
{
+ "name": "systemNoteIconName",
+ "description": "Name of the icon corresponding to a system note",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "updatedAt",
"description": "Timestamp of the note's last activity",
"args": [
@@ -39551,20 +39533,6 @@
"deprecationReason": null
},
{
- "name": "webPath",
- "description": "Web path for the tree entry (directory)",
- "args": [
-
- ],
- "type": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
"name": "webUrl",
"description": "Web URL for the tree entry (directory)",
"args": [
@@ -41982,24 +41950,6 @@
"deprecationReason": null
},
{
- "name": "webPath",
- "description": "Web path of the user",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
"name": "webUrl",
"description": "Web URL of the user",
"args": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 937df0f92b1..94afeb727ae 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -164,7 +164,6 @@ Autogenerated return type of AwardEmojiToggle
| `path` | String! | Path of the entry |
| `sha` | String! | Last commit sha for the entry |
| `type` | EntryType! | Type of tree entry |
-| `webPath` | String | Web path of the blob |
| `webUrl` | String | Web URL of the blob |
## Board
@@ -228,7 +227,6 @@ Autogenerated return type of BoardListUpdateLimitMetrics
| `signatureHtml` | String | Rendered HTML of the commit signature |
| `title` | String | Title of the commit message |
| `titleHtml` | String | The GitLab Flavored Markdown rendering of `title` |
-| `webPath` | String! | Web path of the commit |
| `webUrl` | String! | Web URL of the commit |
## CommitCreatePayload
@@ -1264,6 +1262,7 @@ Contains statistics about a milestone
| `resolvedAt` | Time | Timestamp of when the object was resolved |
| `resolvedBy` | User | User who resolved the object |
| `system` | Boolean! | Indicates whether this note was created by the system or by a user |
+| `systemNoteIconName` | String | Name of the icon corresponding to a system note |
| `updatedAt` | Time! | Timestamp of the note's last activity |
| `userPermissions` | NotePermissions! | Permissions for the current user on the resource |
@@ -2021,7 +2020,6 @@ Represents a directory
| `path` | String! | Path of the entry |
| `sha` | String! | Last commit sha for the entry |
| `type` | EntryType! | Type of tree entry |
-| `webPath` | String | Web path for the tree entry (directory) |
| `webUrl` | String | Web URL for the tree entry (directory) |
## UpdateAlertStatusPayload
@@ -2125,7 +2123,6 @@ Autogenerated return type of UpdateSnippet
| `state` | UserState! | State of the user |
| `userPermissions` | UserPermissions! | Permissions for the current user on the resource |
| `username` | String! | Username of the user. Unique within this instance of GitLab |
-| `webPath` | String! | Web path of the user |
| `webUrl` | String! | Web URL of the user |
## UserPermissions
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 569ee48e282..c10dac2750c 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -458,7 +458,13 @@ For Omnibus GitLab packages:
gitlab_rails['backup_upload_connection'] = {
'provider' => 'Google',
'google_storage_access_key_id' => 'Access Key',
- 'google_storage_secret_access_key' => 'Secret'
+ 'google_storage_secret_access_key' => 'Secret',
+
+ ## If you have CNAME buckets (foo.example.com), you might run into SSL issues
+ ## when uploading backups ("hostname foo.example.com.storage.googleapis.com
+ ## does not match the server certificate"). In that case, uncomnent the following
+ ## setting. See: https://github.com/fog/fog/issues/2834
+ #'path_style' => true
}
gitlab_rails['backup_upload_remote_directory'] = 'my.google.bucket'
```
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 7b29ccf0439..4b8d61f625d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -3240,18 +3240,6 @@ msgstr ""
msgid "Audit Log"
msgstr ""
-msgid "AuditEvents|(removed)"
-msgstr ""
-
-msgid "AuditEvents|Action"
-msgstr ""
-
-msgid "AuditEvents|At"
-msgstr ""
-
-msgid "AuditEvents|Target"
-msgstr ""
-
msgid "AuditLogs|(removed)"
msgstr ""
@@ -6833,6 +6821,9 @@ msgstr ""
msgid "Create new label"
msgstr ""
+msgid "Create new value stream"
+msgstr ""
+
msgid "Create new..."
msgstr ""
@@ -14506,15 +14497,27 @@ msgstr ""
msgid "Metrics|Avg"
msgstr ""
+msgid "Metrics|Cancel"
+msgstr ""
+
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr ""
msgid "Metrics|Create custom dashboard %{fileName}"
msgstr ""
+msgid "Metrics|Create dashboard"
+msgstr ""
+
msgid "Metrics|Create metric"
msgstr ""
+msgid "Metrics|Create new dashboard"
+msgstr ""
+
+msgid "Metrics|Create your dashboard configuration file"
+msgstr ""
+
msgid "Metrics|Current"
msgstr ""
@@ -14527,6 +14530,9 @@ msgstr ""
msgid "Metrics|Duplicate"
msgstr ""
+msgid "Metrics|Duplicate current dashboard"
+msgstr ""
+
msgid "Metrics|Duplicate dashboard"
msgstr ""
@@ -14577,6 +14583,9 @@ msgstr ""
msgid "Metrics|New metric"
msgstr ""
+msgid "Metrics|Open repository"
+msgstr ""
+
msgid "Metrics|PromQL query is valid"
msgstr ""
@@ -14631,6 +14640,9 @@ msgstr ""
msgid "Metrics|There was an error while retrieving metrics. %{message}"
msgstr ""
+msgid "Metrics|To create a new dashboard, add a new YAML file to %{codeStart}.gitlab/dashboards%{codeEnd} at the root of this project."
+msgstr ""
+
msgid "Metrics|Unexpected deployment data response from prometheus endpoint"
msgstr ""
@@ -14652,6 +14664,9 @@ msgstr ""
msgid "Metrics|Values"
msgstr ""
+msgid "Metrics|View documentation"
+msgstr ""
+
msgid "Metrics|View logs"
msgstr ""
diff --git a/package.json b/package.json
index fd06989ea27..7cee19793c9 100644
--- a/package.json
+++ b/package.json
@@ -41,7 +41,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.151.0",
- "@gitlab/ui": "17.18.1",
+ "@gitlab/ui": "17.19.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",
diff --git a/qa/bin/rubymine b/qa/bin/rubymine
new file mode 100755
index 00000000000..0be0cf0ec33
--- /dev/null
+++ b/qa/bin/rubymine
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+
+require_relative '../qa'
+
+ARGV.unshift('Test::Instance::All', ENV['GITLAB_URL'], '--')
+
+QA::Scenario
+ .const_get(ARGV.shift)
+ .launch!(ARGV)
diff --git a/spec/config/settings_spec.rb b/spec/config/settings_spec.rb
index 9db3d35cbe5..ed873478fc9 100644
--- a/spec/config/settings_spec.rb
+++ b/spec/config/settings_spec.rb
@@ -112,4 +112,26 @@ RSpec.describe Settings do
end
end
end
+
+ describe '.cron_for_usage_ping' do
+ it 'returns correct crontab for some manually calculated example' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { 'd9e2f4e8-db1f-4e51-b03d-f427e1965c4a'}
+
+ expect(described_class.send(:cron_for_usage_ping)).to eq('21 18 * * 4')
+ end
+
+ it 'returns min, hour, day in the valid range' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:uuid) { SecureRandom.uuid }
+
+ 10.times do
+ cron = described_class.send(:cron_for_usage_ping).split(/\s/)
+
+ expect(cron[0].to_i).to be_between(0, 59)
+ expect(cron[1].to_i).to be_between(0, 23)
+ expect(cron[4].to_i).to be_between(0, 6)
+ end
+ end
+ end
end
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index 52e91f31ec1..4b1f3194ce5 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -159,6 +159,11 @@ FactoryBot.define do
system { true }
end
+ trait :with_system_note_metadata do
+ system
+ system_note_metadata
+ end
+
trait :downvote do
note { "thumbsdown" }
end
diff --git a/spec/features/clusters/installing_applications_shared_examples.rb b/spec/features/clusters/installing_applications_shared_examples.rb
index 7528bcaa8b7..74150c42519 100644
--- a/spec/features/clusters/installing_applications_shared_examples.rb
+++ b/spec/features/clusters/installing_applications_shared_examples.rb
@@ -168,32 +168,54 @@ RSpec.shared_examples "installing applications for a cluster" do |managed_apps_l
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
create(:clusters_applications_helm, :installed, cluster: cluster) unless managed_apps_local_tiller
+ end
+ it 'shows status transition' do
page.within('.js-cluster-application-row-cert_manager') do
click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installing!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
+
+ Clusters::Cluster.last.application_cert_manager.make_installed!
+
+ expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
- wait_for_requests
+ expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
- it 'shows status transition' do
+ it 'installs with custom email' do
+ custom_email = 'new_email@example.org'
+
page.within('.js-cluster-application-row-cert_manager') do
- expect(page).to have_field('Issuer Email', with: cluster.user.email)
+ # Wait for the polling to finish
+ wait_for_requests
+
+ page.find('.js-email').set(custom_email)
+ click_button 'Install'
+ wait_for_requests
+
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
- page.find('.js-email').set("new_email@example.org")
Clusters::Cluster.last.application_cert_manager.make_installing!
- expect(page).to have_field('Issuer Email', with: 'new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installing')
Clusters::Cluster.last.application_cert_manager.make_installed!
- expect(page).to have_field('Issuer Email', with: 'new_email@example.org')
+ expect(page).to have_field('Issuer Email', with: custom_email)
expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
-
- expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
end
end
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index fab500f47bf..05f4c16ef60 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -27,7 +27,7 @@ RSpec.describe 'Merge request > User toggles whitespace changes', :js do
find('.js-show-diff-settings').click
- expect(find('#show-whitespace')).to be_checked
+ expect(find('#show-whitespace')).not_to be_checked
end
end
end
diff --git a/spec/features/projects/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 921bbbfbe7d..1e2cd3c0a69 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe 'Pipeline Schedules', :js do
- include PipelineSchedulesHelper
-
let!(:project) { create(:project, :repository) }
let!(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project ) }
let!(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule) }
diff --git a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
index 652cbc4b838..8dd663e55d9 100644
--- a/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
+++ b/spec/frontend/alert_management/components/system_notes/alert_management_system_note_spec.js
@@ -28,7 +28,11 @@ describe('Alert Details System Note', () => {
});
it('renders the correct system note', () => {
- expect(wrapper.find('.note-wrapper').attributes('id')).toBe('note_1628');
+ const noteId = wrapper.find('.note-wrapper').attributes('id');
+ const iconRoute = wrapper.find('use').attributes('href');
+
+ expect(noteId).toBe('note_1628');
+ expect(iconRoute.includes('user')).toBe(true);
});
});
});
diff --git a/spec/frontend/alert_management/mocks/alerts.json b/spec/frontend/alert_management/mocks/alerts.json
index 34eed7ae024..f63019d1e5c 100644
--- a/spec/frontend/alert_management/mocks/alerts.json
+++ b/spec/frontend/alert_management/mocks/alerts.json
@@ -33,7 +33,8 @@
"name": "Administrator",
"username": "root",
"webUrl": "http://192.168.1.4:3000/root"
- }
+ },
+ "systemNoteIconName": "user"
}
]
}
diff --git a/spec/frontend/diffs/store/actions_spec.js b/spec/frontend/diffs/store/actions_spec.js
index 7d79dcfbfe3..ee55669cd9b 100644
--- a/spec/frontend/diffs/store/actions_spec.js
+++ b/spec/frontend/diffs/store/actions_spec.js
@@ -6,6 +6,8 @@ import {
INLINE_DIFF_VIEW_TYPE,
PARALLEL_DIFF_VIEW_TYPE,
DIFFS_PER_PAGE,
+ DIFF_WHITESPACE_COOKIE_NAME,
+ SHOW_WHITESPACE,
} from '~/diffs/constants';
import {
setBaseConfig,
@@ -1187,10 +1189,10 @@ describe('DiffsStoreActions', () => {
);
});
- it('sets localStorage', () => {
+ it('sets cookie', () => {
setShowWhitespace({ commit() {} }, { showWhitespace: true });
- expect(localStorage.setItem).toHaveBeenCalledWith('mr_show_whitespace', true);
+ expect(Cookies.get(DIFF_WHITESPACE_COOKIE_NAME)).toEqual(SHOW_WHITESPACE);
});
it('calls history pushState', () => {
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 891de45e268..d87619e1e3c 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -1090,4 +1090,26 @@ describe('DiffsStoreUtils', () => {
]);
});
});
+
+ describe('getDefaultWhitespace', () => {
+ it('defaults to true if querystring and cookie are undefined', () => {
+ expect(utils.getDefaultWhitespace()).toBe(true);
+ });
+
+ it('returns false if querystring is `1`', () => {
+ expect(utils.getDefaultWhitespace('1', '0')).toBe(false);
+ });
+
+ it('returns true if querystring is `0`', () => {
+ expect(utils.getDefaultWhitespace('0', undefined)).toBe(true);
+ });
+
+ it('returns false if cookie is `1`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '1')).toBe(false);
+ });
+
+ it('returns true if cookie is `0`', () => {
+ expect(utils.getDefaultWhitespace(undefined, '0')).toBe(true);
+ });
+ });
});
diff --git a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
index dac663252ab..cb3f74ca73c 100644
--- a/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
+++ b/spec/frontend/monitoring/components/__snapshots__/dashboard_template_spec.js.snap
@@ -13,17 +13,23 @@ exports[`Dashboard template matches the default snapshot 1`] = `
class="prometheus-graphs-header d-sm-flex flex-sm-wrap pt-2 pr-1 pb-0 pl-2 border-bottom bg-gray-light"
>
<div
- class="mb-2 pr-2 d-flex d-sm-block"
+ class="mb-2 mr-2 d-flex d-sm-block"
>
<dashboards-dropdown-stub
class="flex-grow-1"
data-qa-selector="dashboards_filter_dropdown"
defaultbranch="master"
id="monitor-dashboards-dropdown"
+ modalid="duplicateDashboard"
toggle-class="dropdown-menu-toggle"
/>
</div>
+ <span
+ aria-hidden="true"
+ class="gl-pl-3 border-left gl-mb-3 d-none d-sm-block"
+ />
+
<div
class="mb-2 pr-2 d-flex d-sm-block"
>
@@ -121,7 +127,14 @@ exports[`Dashboard template matches the default snapshot 1`] = `
<!---->
<!---->
+
+ <!---->
</div>
+
+ <duplicate-dashboard-modal-stub
+ defaultbranch="master"
+ modalid="duplicateDashboard"
+ />
</div>
<!---->
diff --git a/spec/frontend/monitoring/components/create_dashboard_modal_spec.js b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d1028445638
--- /dev/null
+++ b/spec/frontend/monitoring/components/create_dashboard_modal_spec.js
@@ -0,0 +1,48 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+
+describe('Create dashboard modal', () => {
+ let wrapper;
+
+ const defaultProps = {
+ modalId: 'id',
+ projectPath: 'https://localhost/',
+ addDashboardDocumentationPath: 'https://link/to/docs',
+ };
+
+ const findDocsButton = () => wrapper.find('[data-testid="create-dashboard-modal-docs-button"]');
+ const findRepoButton = () => wrapper.find('[data-testid="create-dashboard-modal-repo-button"]');
+
+ const createWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(CreateDashboardModal, {
+ propsData: { ...defaultProps, ...props },
+ stubs: {
+ GlModal,
+ },
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ createWrapper();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has button that links to the project url', () => {
+ findRepoButton().trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findRepoButton().exists()).toBe(true);
+ expect(findRepoButton().attributes('href')).toBe(defaultProps.projectPath);
+ });
+ });
+
+ it('has button that links to the docs', () => {
+ expect(findDocsButton().exists()).toBe(true);
+ expect(findDocsButton().attributes('href')).toBe(defaultProps.addDashboardDocumentationPath);
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js
new file mode 100644
index 00000000000..5af7b4049d1
--- /dev/null
+++ b/spec/frontend/monitoring/components/dashboard_header_spec.js
@@ -0,0 +1,160 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import DashboardHeader from '~/monitoring/components/dashboard_header.vue';
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import CreateDashboardModal from '~/monitoring/components/create_dashboard_modal.vue';
+import { setupAllDashboards } from '../store_utils';
+import { dashboardGitResponse, dashboardHeaderProps } from '../mock_data';
+import { redirectTo, mergeUrlParams } from '~/lib/utils/url_utility';
+
+jest.mock('~/lib/utils/url_utility', () => ({
+ redirectTo: jest.fn(),
+ queryToObject: jest.fn(),
+ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams,
+}));
+
+describe('Dashboard header', () => {
+ let store;
+ let wrapper;
+
+ const findActionsMenu = () => wrapper.find('[data-testid="actions-menu"]');
+ const findCreateDashboardMenuItem = () =>
+ findActionsMenu().find('[data-testid="action-create-dashboard"]');
+ const findCreateDashboardDuplicateItem = () =>
+ findActionsMenu().find('[data-testid="action-duplicate-dashboard"]');
+ const findDuplicateDashboardModal = () => wrapper.find(DuplicateDashboardModal);
+ const findCreateDashboardModal = () => wrapper.find('[data-testid="create-dashboard-modal"]');
+
+ const createShallowWrapper = (props = {}, options = {}) => {
+ wrapper = shallowMount(DashboardHeader, {
+ propsData: { ...dashboardHeaderProps, ...props },
+ store,
+ ...options,
+ });
+ };
+
+ beforeEach(() => {
+ store = createStore();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('when a dashboard has been duplicated in the duplicate dashboard modal', () => {
+ /**
+ * The duplicate dashboard modal gets called both by a menu item from the
+ * dashboards dropdown and by an item from the actions menu.
+ *
+ * This spec is context agnostic, so it addresses all cases where the
+ * duplicate dashboard modal gets called.
+ */
+ it('redirects to the newly created dashboard', () => {
+ delete window.location;
+ window.location = new URL('https://localhost');
+
+ const newDashboard = dashboardGitResponse[1];
+ const params = {
+ dashboard: encodeURIComponent(newDashboard.path),
+ };
+ const newDashboardUrl = mergeUrlParams(params, window.location.href);
+
+ createShallowWrapper();
+ findDuplicateDashboardModal().vm.$emit('dashboardDuplicated', newDashboard);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(redirectTo).toHaveBeenCalled();
+ expect(redirectTo).toHaveBeenCalledWith(newDashboardUrl);
+ });
+ });
+ });
+
+ describe('actions menu', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = '';
+ createShallowWrapper();
+ });
+
+ it('is rendered if projectPath is set in store', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().exists()).toBe(true);
+ });
+ });
+
+ it('is not rendered if projectPath is not set in store', () => {
+ expect(findActionsMenu().exists()).toBe(false);
+ });
+
+ it('contains a modal', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findActionsMenu().contains(CreateDashboardModal)).toBe(true);
+ });
+ });
+
+ describe('when the selected dashboard is the system dashboard', () => {
+ it('contains a "Create New" menu item and a "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+ setupAllDashboards(store);
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(true);
+ });
+ });
+ });
+
+ describe('when the selected dashboard is not the system dashboard', () => {
+ it('contains a "Create New" menu item and no "Duplicate Dashboard" menu item', () => {
+ store.state.monitoringDashboard.projectPath = 'https://path/to/project';
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(findCreateDashboardMenuItem().exists()).toBe(true);
+ expect(findCreateDashboardDuplicateItem().exists()).toBe(false);
+ });
+ });
+ });
+ });
+
+ describe('actions menu modals', () => {
+ const url = 'https://path/to/project';
+
+ beforeEach(() => {
+ store.state.monitoringDashboard.projectPath = url;
+ setupAllDashboards(store);
+
+ createShallowWrapper();
+ });
+
+ it('Clicking on "Create New" opens up a modal', () => {
+ const modalId = 'createDashboard';
+ const modalTrigger = findCreateDashboardMenuItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+
+ it('"Create new dashboard" modal contains correct buttons', () => {
+ expect(findCreateDashboardModal().props('projectPath')).toBe(url);
+ });
+
+ it('"Duplicate Dashboard" opens up a modal', () => {
+ const modalId = 'duplicateDashboard';
+ const modalTrigger = findCreateDashboardDuplicateItem();
+ const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
+
+ modalTrigger.trigger('click');
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(rootEmit.mock.calls[0]).toContainEqual(modalId);
+ });
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js
index 948f56dabdb..a4fbc0a981a 100644
--- a/spec/frontend/monitoring/components/dashboard_spec.js
+++ b/spec/frontend/monitoring/components/dashboard_spec.js
@@ -1237,7 +1237,7 @@ describe('Dashboard', () => {
it('uses modal for custom metrics form', () => {
expect(wrapper.find(GlModal).exists()).toBe(true);
- expect(wrapper.find(GlModal).attributes().modalid).toBe('add-metric');
+ expect(wrapper.find(GlModal).attributes().modalid).toBe('addMetric');
});
it('adding new metric is tracked', done => {
const submitButton = wrapper
diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
index b29d86cbc5b..9836d3b4bab 100644
--- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
+++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js
@@ -1,14 +1,12 @@
import { shallowMount } from '@vue/test-utils';
-import { GlDropdownItem, GlModal, GlLoadingIcon, GlAlert, GlIcon } from '@gitlab/ui';
-import waitForPromises from 'helpers/wait_for_promises';
+import { GlDropdownItem, GlIcon } from '@gitlab/ui';
import DashboardsDropdown from '~/monitoring/components/dashboards_dropdown.vue';
-import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
import { dashboardGitResponse } from '../mock_data';
const defaultBranch = 'master';
-
+const modalId = 'duplicateDashboardModalId';
const starredDashboards = dashboardGitResponse.filter(({ starred }) => starred);
const notStarredDashboards = dashboardGitResponse.filter(({ starred }) => !starred);
@@ -32,6 +30,7 @@ describe('DashboardsDropdown', () => {
propsData: {
...props,
defaultBranch,
+ modalId,
},
sync: false,
...storeOpts,
@@ -82,7 +81,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'Default';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findItems()).toHaveLength(1);
});
});
@@ -91,7 +90,7 @@ describe('DashboardsDropdown', () => {
const searchTerm = 'does-not-exist';
setSearchTerm(searchTerm);
- return wrapper.vm.$nextTick(() => {
+ return wrapper.vm.$nextTick().then(() => {
expect(findNoItemsMsg().isVisible()).toBe(true);
});
});
@@ -151,7 +150,7 @@ describe('DashboardsDropdown', () => {
});
});
- describe('when a system dashboard is selected', () => {
+ describe('when the selected dashboard can be duplicated', () => {
let duplicateDashboardAction;
let modalDirective;
@@ -172,151 +171,53 @@ describe('DashboardsDropdown', () => {
},
},
);
-
- wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
});
- it('displays an item for each dashboard plus a "duplicate dashboard" item', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
-
+ it('displays a dropdown item for each dashboard', () => {
expect(findItems().length).toEqual(dashboardGitResponse.length + 1);
- expect(item.length).toBe(1);
});
- describe('modal form', () => {
- let okEvent;
-
- const findModal = () => wrapper.find(GlModal);
- const findAlert = () => wrapper.find(GlAlert);
-
- beforeEach(() => {
- okEvent = {
- preventDefault: jest.fn(),
- };
- });
-
- it('exists and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(true);
- expect(findModal().contains(DuplicateDashboardForm)).toBe(true);
- });
-
- it('saves a new dashboard', () => {
- findModal().vm.$emit('ok', okEvent);
-
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
-
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
- expect(wrapper.emitted().selectDashboard).toBeTruthy();
- expect(findAlert().exists()).toBe(false);
- });
- });
-
- describe('when a new dashboard is saved succesfully', () => {
- const newDashboard = {
- can_edit: true,
- default: false,
- display_name: 'A new dashboard',
- system_dashboard: false,
- };
-
- const submitForm = formVals => {
- duplicateDashboardAction.mockResolvedValueOnce(newDashboard);
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- ...formVals,
- });
- findModal().vm.$emit('ok', okEvent);
- };
-
- it('to the default branch, redirects to the new dashboard', () => {
- submitForm({
- branch: defaultBranch,
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(newDashboard);
- });
- });
-
- it('to a new branch refreshes in the current dashboard', () => {
- submitForm({
- branch: 'another-branch',
- });
-
- return waitForPromises().then(() => {
- expect(wrapper.emitted().selectDashboard[0][0]).toEqual(dashboardGitResponse[0]);
- });
- });
- });
-
- it('handles error when a new dashboard is not saved', () => {
- const errMsg = 'An error occurred';
+ it('displays one "duplicate dashboard" dropdown item with a directive attached', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
- duplicateDashboardAction.mockRejectedValueOnce(errMsg);
- findModal().vm.$emit('ok', okEvent);
-
- return waitForPromises().then(() => {
- expect(okEvent.preventDefault).toHaveBeenCalled();
-
- expect(findAlert().exists()).toBe(true);
- expect(findAlert().text()).toBe(errMsg);
+ expect(item.length).toBe(1);
+ });
- expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
- expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
- });
- });
+ it('"duplicate dashboard" dropdown item directive works', () => {
+ const item = wrapper.find('[data-testid="duplicateDashboardItem"]');
- it('id is correct, as the value of modal directive binding matches modal id', () => {
- expect(modalDirective).toHaveBeenCalledTimes(1);
+ item.trigger('click');
- // Binding's second argument contains the modal id
- expect(modalDirective.mock.calls[0][1]).toEqual(
- expect.objectContaining({
- value: findModal().props('modalId'),
- }),
- );
+ return wrapper.vm.$nextTick().then(() => {
+ expect(modalDirective).toHaveBeenCalled();
});
+ });
- it('updates the form on changes', () => {
- const formVals = {
- dashboard: 'common_metrics.yml',
- commitMessage: 'A commit message',
- };
-
- findModal()
- .find(DuplicateDashboardForm)
- .vm.$emit('change', formVals);
+ it('id is correct, as the value of modal directive binding matches modal id', () => {
+ expect(modalDirective).toHaveBeenCalledTimes(1);
- // Binding's second argument contains the modal id
- expect(wrapper.vm.form).toEqual(formVals);
- });
+ // Binding's second argument contains the modal id
+ expect(modalDirective.mock.calls[0][1]).toEqual(
+ expect.objectContaining({
+ value: modalId,
+ }),
+ );
});
});
- describe('when a custom dashboard is selected', () => {
- const findModal = () => wrapper.find(GlModal);
-
+ describe('when the selected dashboard can not be duplicated', () => {
beforeEach(() => {
- wrapper = createComponent({
- selectedDashboard: dashboardGitResponse[1],
- });
+ [, mockSelectedDashboard] = dashboardGitResponse;
+
+ wrapper = createComponent();
});
- it('displays an item for each dashboard', () => {
- const item = wrapper.findAll({ ref: 'duplicateDashboardItem' });
+ it('displays a dropdown list item for each dashboard, but no list item for "duplicate dashboard"', () => {
+ const item = wrapper.findAll('[data-testid="duplicateDashboardItem"]');
expect(findItems()).toHaveLength(dashboardGitResponse.length);
expect(item.length).toBe(0);
});
-
- it('modal form does not exist and contains a form to duplicate a dashboard', () => {
- expect(findModal().exists()).toBe(false);
- });
});
describe('when a dashboard gets selected by the user', () => {
diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
new file mode 100644
index 00000000000..d8ffb4443ac
--- /dev/null
+++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js
@@ -0,0 +1,111 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLoadingIcon, GlModal } from '@gitlab/ui';
+
+import waitForPromises from 'helpers/wait_for_promises';
+
+import DuplicateDashboardModal from '~/monitoring/components/duplicate_dashboard_modal.vue';
+import DuplicateDashboardForm from '~/monitoring/components/duplicate_dashboard_form.vue';
+
+import { dashboardGitResponse } from '../mock_data';
+
+describe('duplicate dashboard modal', () => {
+ let wrapper;
+ let mockDashboards;
+ let mockSelectedDashboard;
+ let duplicateDashboardAction;
+ let okEvent;
+
+ function createComponent(opts = {}) {
+ const storeOpts = {
+ methods: {
+ duplicateSystemDashboard: jest.fn(),
+ },
+ computed: {
+ allDashboards: () => mockDashboards,
+ selectedDashboard: () => mockSelectedDashboard,
+ },
+ };
+
+ return shallowMount(DuplicateDashboardModal, {
+ propsData: {
+ defaultBranch: 'master',
+ modalId: 'id',
+ },
+ sync: false,
+ ...storeOpts,
+ ...opts,
+ });
+ }
+
+ const findAlert = () => wrapper.find(GlAlert);
+ const findModal = () => wrapper.find(GlModal);
+ const findDuplicateDashboardForm = () => wrapper.find(DuplicateDashboardForm);
+
+ beforeEach(() => {
+ mockDashboards = dashboardGitResponse;
+ [mockSelectedDashboard] = dashboardGitResponse;
+
+ duplicateDashboardAction = jest.fn().mockResolvedValue();
+
+ okEvent = {
+ preventDefault: jest.fn(),
+ };
+
+ wrapper = createComponent({
+ methods: {
+ // Mock vuex actions
+ duplicateSystemDashboard: duplicateDashboardAction,
+ },
+ });
+
+ wrapper.vm.$refs.duplicateDashboardModal.hide = jest.fn();
+ });
+
+ it('contains a form to duplicate a dashboard', () => {
+ expect(findDuplicateDashboardForm().exists()).toBe(true);
+ });
+
+ it('saves a new dashboard', () => {
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+ expect(wrapper.emitted().dashboardDuplicated).toBeTruthy();
+ expect(wrapper.emitted().dashboardDuplicated[0]).toEqual([dashboardGitResponse[0]]);
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled();
+ expect(findAlert().exists()).toBe(false);
+ });
+ });
+
+ it('handles error when a new dashboard is not saved', () => {
+ const errMsg = 'An error occurred';
+
+ duplicateDashboardAction.mockRejectedValueOnce(errMsg);
+ findModal().vm.$emit('ok', okEvent);
+
+ return waitForPromises().then(() => {
+ expect(okEvent.preventDefault).toHaveBeenCalled();
+
+ expect(findAlert().exists()).toBe(true);
+ expect(findAlert().text()).toBe(errMsg);
+
+ expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
+ expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled();
+ });
+ });
+
+ it('updates the form on changes', () => {
+ const formVals = {
+ dashboard: 'common_metrics.yml',
+ commitMessage: 'A commit message',
+ };
+
+ findModal()
+ .find(DuplicateDashboardForm)
+ .vm.$emit('change', formVals);
+
+ // Binding's second argument contains the modal id
+ expect(wrapper.vm.form).toEqual(formVals);
+ });
+});
diff --git a/spec/frontend/monitoring/mock_data.js b/spec/frontend/monitoring/mock_data.js
index 50395a3ff89..cffc9a21a63 100644
--- a/spec/frontend/monitoring/mock_data.js
+++ b/spec/frontend/monitoring/mock_data.js
@@ -805,3 +805,13 @@ export const storeVariables = [
...storeCustomVariables,
...storeMetricLabelValuesVariables,
];
+
+export const dashboardHeaderProps = {
+ defaultBranch: 'master',
+ addDashboardDocumentationPath: 'https://path/to/docs',
+ isRearrangingPanels: false,
+ selectedTimeRange: {
+ start: '2020-01-01T00:00:00.000Z',
+ end: '2020-01-01T01:00:00.000Z',
+ },
+};
diff --git a/spec/frontend/monitoring/store/mutations_spec.js b/spec/frontend/monitoring/store/mutations_spec.js
index 37da5ea96d9..86a8793c508 100644
--- a/spec/frontend/monitoring/store/mutations_spec.js
+++ b/spec/frontend/monitoring/store/mutations_spec.js
@@ -386,6 +386,7 @@ describe('Monitoring mutations', () => {
});
});
});
+
describe('SET_ALL_DASHBOARDS', () => {
it('stores `undefined` dashboards as an empty array', () => {
mutations[types.SET_ALL_DASHBOARDS](stateCopy, undefined);
diff --git a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
index cf2e6b00800..1dca65dd862 100644
--- a/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
+++ b/spec/frontend/repository/components/__snapshots__/last_commit_spec.js.snap
@@ -10,7 +10,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
imgcssclasses=""
imgsize="40"
imgsrc="https://test.com"
- linkhref="/test"
+ linkhref="https://test.com/test"
tooltipplacement="top"
tooltiptext=""
username=""
@@ -24,7 +24,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
>
<gl-link-stub
class="commit-row-message item-title"
- href="/commit/123"
+ href="https://test.com/commit/123"
>
Commit title
</gl-link-stub>
@@ -36,7 +36,7 @@ exports[`Repository last commit component renders commit widget 1`] = `
>
<gl-link-stub
class="commit-author-link js-user-link"
- href="/test"
+ href="https://test.com/test"
>
Test
@@ -110,7 +110,7 @@ exports[`Repository last commit component renders the signature HTML as returned
imgcssclasses=""
imgsize="40"
imgsrc="https://test.com"
- linkhref="/test"
+ linkhref="https://test.com/test"
tooltipplacement="top"
tooltiptext=""
username=""
@@ -124,7 +124,7 @@ exports[`Repository last commit component renders the signature HTML as returned
>
<gl-link-stub
class="commit-row-message item-title"
- href="/commit/123"
+ href="https://test.com/commit/123"
>
Commit title
</gl-link-stub>
@@ -136,7 +136,7 @@ exports[`Repository last commit component renders the signature HTML as returned
>
<gl-link-stub
class="commit-author-link js-user-link"
- href="/test"
+ href="https://test.com/test"
>
Test
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index 115784a9201..a5bfeb08fe4 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -12,13 +12,11 @@ function createCommitData(data = {}) {
titleHtml: 'Commit title',
message: 'Commit message',
webUrl: 'https://test.com/commit/123',
- webPath: '/commit/123',
authoredDate: '2019-01-01',
author: {
name: 'Test',
avatarUrl: 'https://test.com',
webUrl: 'https://test.com/test',
- webPath: '/test',
},
pipeline: {
detailedStatus: {
diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
index 34cd8d67081..69b7a3931f8 100644
--- a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
@@ -16,7 +16,7 @@ exports[`Repository file preview component renders file HTML 1`] = `
/>
<gl-link-stub
- href="/test.md"
+ href="http://test.com"
>
<strong>
README.md
diff --git a/spec/frontend/repository/components/preview/index_spec.js b/spec/frontend/repository/components/preview/index_spec.js
index 30b313d6142..6ae323f5c3f 100644
--- a/spec/frontend/repository/components/preview/index_spec.js
+++ b/spec/frontend/repository/components/preview/index_spec.js
@@ -31,7 +31,6 @@ describe('Repository file preview component', () => {
it('renders file HTML', () => {
factory({
webUrl: 'http://test.com',
- webPath: '/test.md',
name: 'README.md',
});
@@ -45,7 +44,6 @@ describe('Repository file preview component', () => {
it('handles hash after render', () => {
factory({
webUrl: 'http://test.com',
- webPath: '/test.md',
name: 'README.md',
});
@@ -62,7 +60,6 @@ describe('Repository file preview component', () => {
it('renders loading icon', () => {
factory({
webUrl: 'http://test.com',
- webPath: '/test.md',
name: 'README.md',
});
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index a0dbb32a677..75984786972 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe GitlabSchema.types['Commit'] do
it 'contains attributes related to commit' do
expect(described_class).to have_graphql_fields(
:id, :sha, :title, :description, :message, :title_html, :authored_date,
- :author_name, :author_gravatar, :author, :web_path, :web_url, :latest_pipeline,
+ :author_name, :author_gravatar, :author, :web_url, :latest_pipeline,
:pipelines, :signature_html
)
end
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index 4f2452683e2..180d13d35d2 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -19,6 +19,7 @@ RSpec.describe GitlabSchema.types['Note'] do
resolved_at
resolved_by
system
+ system_note_icon_name
updated_at
user_permissions
]
diff --git a/spec/graphql/types/tree/blob_type_spec.rb b/spec/graphql/types/tree/blob_type_spec.rb
index b018a8b6555..2c9089de3dd 100644
--- a/spec/graphql/types/tree/blob_type_spec.rb
+++ b/spec/graphql/types/tree/blob_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
RSpec.describe Types::Tree::BlobType do
specify { expect(described_class.graphql_name).to eq('Blob') }
- specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_path, :web_url, :lfs_oid) }
+ specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
end
diff --git a/spec/graphql/types/tree/tree_entry_type_spec.rb b/spec/graphql/types/tree/tree_entry_type_spec.rb
index 97dd09d77ee..0e5caf66854 100644
--- a/spec/graphql/types/tree/tree_entry_type_spec.rb
+++ b/spec/graphql/types/tree/tree_entry_type_spec.rb
@@ -5,5 +5,5 @@ require 'spec_helper'
RSpec.describe Types::Tree::TreeEntryType do
specify { expect(described_class.graphql_name).to eq('TreeEntry') }
- specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_path, :web_url) }
+ specify { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url) }
end
diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb
index 641d43e7dfd..6cc3f7bcaa1 100644
--- a/spec/graphql/types/user_type_spec.rb
+++ b/spec/graphql/types/user_type_spec.rb
@@ -16,7 +16,6 @@ RSpec.describe GitlabSchema.types['User'] do
username
avatarUrl
webUrl
- webPath
todos
state
authoredMergeRequests
diff --git a/spec/helpers/ci/pipeline_schedules_helper_spec.rb b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
new file mode 100644
index 00000000000..2a81c2a44a0
--- /dev/null
+++ b/spec/helpers/ci/pipeline_schedules_helper_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Ci::PipelineSchedulesHelper, :aggregate_failures do
+ describe '#timezone_data' do
+ subject { helper.timezone_data }
+
+ it 'matches schema' do
+ expect(subject).not_to be_empty
+ subject.each_with_index do |timzone_hash, i|
+ expect(timzone_hash.keys).to contain_exactly(:name, :offset, :identifier), "Failed at index #{i}"
+ end
+ end
+
+ it 'formats for display' do
+ first_timezone = ActiveSupport::TimeZone.all[0]
+
+ expect(subject[0][:name]).to eq(first_timezone.name)
+ expect(subject[0][:offset]).to eq(first_timezone.now.utc_offset)
+ expect(subject[0][:identifier]).to eq(first_timezone.tzinfo.identifier)
+ end
+ end
+end
diff --git a/spec/helpers/environments_helper_spec.rb b/spec/helpers/environments_helper_spec.rb
index 03428b43966..c083eb89cc0 100644
--- a/spec/helpers/environments_helper_spec.rb
+++ b/spec/helpers/environments_helper_spec.rb
@@ -23,6 +23,7 @@ RSpec.describe EnvironmentsHelper do
'metrics-dashboard-base-path' => environment_metrics_path(environment),
'current-environment-name' => environment.name,
'documentation-path' => help_page_path('administration/monitoring/prometheus/index.md'),
+ 'add-dashboard-documentation-path' => help_page_path('user/project/integrations/prometheus.md', anchor: 'adding-a-new-dashboard-to-your-project'),
'empty-getting-started-svg-path' => match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path' => match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path' => match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
index f5fd2fd8d94..1350cba119b 100644
--- a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
+++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb
@@ -9,8 +9,8 @@ RSpec.describe 'getting Alert Management Alert Notes' do
let_it_be(:current_user) { create(:user) }
let_it_be(:first_alert) { create(:alert_management_alert, project: project, assignees: [current_user]) }
let_it_be(:second_alert) { create(:alert_management_alert, project: project) }
- let_it_be(:first_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
- let_it_be(:second_system_note) { create(:note_on_alert, noteable: first_alert, project: project) }
+ let_it_be(:first_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
+ let_it_be(:second_system_note) { create(:note_on_alert, :with_system_note_metadata, noteable: first_alert, project: project) }
let(:params) { {} }
@@ -21,6 +21,8 @@ RSpec.describe 'getting Alert Management Alert Notes' do
notes {
nodes {
id
+ body
+ systemNoteIconName
}
}
}
@@ -44,7 +46,17 @@ RSpec.describe 'getting Alert Management Alert Notes' do
project.add_developer(current_user)
end
- it 'returns the notes ordered by createdAt' do
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(first_notes_result.first).to include(
+ 'id' => first_system_note.to_global_id.to_s,
+ 'systemNoteIconName' => 'git-merge',
+ 'body' => first_system_note.note
+ )
+ end
+
+ it 'returns the notes ordered by createdAt with sufficient content' do
post_graphql(query, current_user: current_user)
expect(first_notes_result.length).to eq(2)
@@ -64,4 +76,18 @@ RSpec.describe 'getting Alert Management Alert Notes' do
expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(base_count)
expect(alerts_result.length).to eq(3)
end
+
+ context 'for non-system notes' do
+ let_it_be(:user_note) { create(:note_on_alert, noteable: second_alert, project: project) }
+
+ it 'includes expected data' do
+ post_graphql(query, current_user: current_user)
+
+ expect(second_notes_result.first).to include(
+ 'id' => user_note.to_global_id.to_s,
+ 'systemNoteIconName' => nil,
+ 'body' => user_note.note
+ )
+ end
+ end
end
diff --git a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb b/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
deleted file mode 100644
index c23f2d8c817..00000000000
--- a/spec/services/projects/prometheus/alerts/create_events_service_spec.rb
+++ /dev/null
@@ -1,312 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::Prometheus::Alerts::CreateEventsService do
- let(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: project) }
- let(:service) { described_class.new(project, user, alerts_payload) }
-
- shared_examples 'events persisted' do |expected_count|
- subject { service.execute }
-
- it 'returns proper amount of created events' do
- expect(subject.size).to eq(expected_count)
- end
-
- it 'increments event count' do
- expect { subject }.to change { PrometheusAlertEvent.count }.to(expected_count)
- end
- end
-
- shared_examples 'no events persisted' do
- subject { service.execute }
-
- it 'returns no created events' do
- expect(subject).to be_empty
- end
-
- it 'does not change event count' do
- expect { subject }.not_to change { PrometheusAlertEvent.count }
- end
- end
-
- shared_examples 'self managed events persisted' do
- subject { service.execute }
-
- it 'returns created events' do
- expect(subject).not_to be_empty
- end
-
- it 'does change self managed event count' do
- expect { subject }.to change { SelfManagedPrometheusAlertEvent.count }
- end
- end
-
- context 'with valid alerts_payload' do
- let!(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- let(:events) { service.execute }
-
- context 'with a firing payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:firing_event) { alert_payload(status: 'firing', started_at: started_at) }
- let(:alerts_payload) { { 'alerts' => [firing_event] } }
-
- it_behaves_like 'events persisted', 1
-
- it 'returns created event' do
- event = events.first
-
- expect(event).to be_firing
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to be_nil
- end
-
- context 'with 2 different firing events' do
- let(:another_firing_event) { alert_payload(status: 'firing', started_at: started_at + 1) }
- let(:alerts_payload) { { 'alerts' => [firing_event, another_firing_event] } }
-
- it_behaves_like 'events persisted', 2
- end
-
- context 'with already persisted firing event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [firing_event, firing_event] } }
-
- it_behaves_like 'events persisted', 1
- end
- end
-
- context 'with a resolved payload' do
- let(:started_at) { truncate_to_second(Time.current) }
- let(:ended_at) { started_at + 1 }
- let(:resolved_event) { alert_payload(status: 'resolved', started_at: started_at, ended_at: ended_at) }
- let(:alerts_payload) { { 'alerts' => [resolved_event] } }
- let(:payload_key) { Gitlab::Alerting::Alert.new(project: project, payload: resolved_event).gitlab_fingerprint }
-
- context 'with a matching firing event' do
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved`' do
- expect(events.size).to eq(1)
-
- event = events.first
- expect(event).to be_resolved
- expect(event.started_at).to eq(started_at)
- expect(event.ended_at).to eq(ended_at)
- end
-
- context 'with duplicate payload' do
- let(:alerts_payload) { { 'alerts' => [resolved_event, resolved_event] } }
-
- it 'does not create an additional event' do
- expect { service.execute }.not_to change { PrometheusAlertEvent.count }
- end
-
- it 'marks firing event as `resolved` only once' do
- expect(events.size).to eq(1)
- end
- end
- end
-
- context 'without a matching firing event' do
- context 'due to payload_key' do
- let(:payload_key) { 'some other payload_key' }
-
- before do
- create(:prometheus_alert_event,
- prometheus_alert: alert,
- payload_key: payload_key,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
-
- context 'due to status' do
- before do
- create(:prometheus_alert_event, :resolved,
- prometheus_alert: alert,
- started_at: started_at)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with already resolved event' do
- before do
- service.execute
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with a metric from another project' do
- let(:another_project) { create(:project) }
- let(:metric) { create(:prometheus_metric, project: another_project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload] } }
-
- let!(:alert) do
- create(:prometheus_alert,
- prometheus_metric: metric,
- project: another_project)
- end
-
- it_behaves_like 'no events persisted'
- end
- end
-
- context 'with invalid payload' do
- let(:alert) { create(:prometheus_alert, prometheus_metric: metric, project: project) }
-
- describe '`alerts` key' do
- context 'is missing' do
- let(:alerts_payload) { {} }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is nil' do
- let(:alerts_payload) { { 'alerts' => nil } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is empty' do
- let(:alerts_payload) { { 'alerts' => [] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is not a Hash' do
- let(:alerts_payload) { { 'alerts' => [:not_a_hash] } }
-
- it_behaves_like 'no events persisted'
- end
-
- describe '`status`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(status: 'invalid')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`started_at`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(started_at: 'invalid date')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`ended_at`' do
- context 'is missing and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: nil, status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is invalid and status is resolved' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(ended_at: 'invalid date', status: 'resolved')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
-
- describe '`labels`' do
- describe '`gitlab_alert_id`' do
- context 'is missing' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil)] } }
-
- it_behaves_like 'no events persisted'
- end
-
- context 'is missing but title is given' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert')] } }
-
- it_behaves_like 'self managed events persisted'
- end
-
- context 'is missing and environment name is given' do
- let(:environment) { create(:environment, project: project) }
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: nil, title: 'alert', environment: environment.name)] } }
-
- it_behaves_like 'self managed events persisted'
-
- it 'associates the environment to the alert event' do
- service.execute
-
- expect(SelfManagedPrometheusAlertEvent.last.environment).to eq environment
- end
- end
-
- context 'is invalid' do
- let(:alerts_payload) { { 'alerts' => [alert_payload(gitlab_alert_id: '-1')] } }
-
- it_behaves_like 'no events persisted'
- end
- end
- end
- end
- end
-
- private
-
- def alert_payload(status: 'firing', started_at: Time.current, ended_at: Time.current, gitlab_alert_id: alert.prometheus_metric_id, title: nil, environment: nil)
- payload = {}
-
- payload['status'] = status if status
- payload['startsAt'] = utc_rfc3339(started_at) if started_at
- payload['endsAt'] = utc_rfc3339(ended_at) if ended_at
- payload['labels'] = {}
- payload['labels']['gitlab_alert_id'] = gitlab_alert_id.to_s if gitlab_alert_id
- payload['labels']['alertname'] = title if title
- payload['labels']['gitlab_environment_name'] = environment if environment
-
- payload
- end
-
- # Example: 2018-09-27T18:25:31.079079416Z
- def utc_rfc3339(date)
- date.utc.rfc3339
- rescue
- date
- end
-
- def truncate_to_second(date)
- date.change(usec: 0)
- end
-end
diff --git a/spec/services/projects/prometheus/alerts/notify_service_spec.rb b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
index 557bf216277..aae257e3e3a 100644
--- a/spec/services/projects/prometheus/alerts/notify_service_spec.rb
+++ b/spec/services/projects/prometheus/alerts/notify_service_spec.rb
@@ -36,48 +36,8 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
end
- shared_examples 'processes incident issues' do |amount|
- let(:create_incident_service) { spy }
-
- it 'processes issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .to receive(:perform_async)
- .with(project.id, kind_of(Hash))
- .exactly(amount).times
-
- Sidekiq::Testing.inline! do
- expect(subject).to be_success
- end
- end
- end
-
- shared_examples 'does not process incident issues' do
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- expect(subject).to be_success
- end
- end
-
- shared_examples 'persists events' do
- let(:create_events_service) { spy }
-
- it 'persists events' do
- expect(Projects::Prometheus::Alerts::CreateEventsService)
- .to receive(:new)
- .and_return(create_events_service)
-
- expect(create_events_service)
- .to receive(:execute)
-
- expect(subject).to be_success
- end
- end
-
shared_examples 'notifies alerts' do
it_behaves_like 'sends notification email'
- it_behaves_like 'persists events'
end
shared_examples 'no notifications' do |http_status:|
@@ -257,8 +217,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
context 'when incident_management_setting does not exist' do
let!(:setting) { nil }
- it_behaves_like 'persists events'
-
it 'does not send notification email', :sidekiq_might_not_need_inline do
expect_any_instance_of(NotificationService)
.not_to receive(:async)
@@ -276,8 +234,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
create(:project_incident_management_setting, send_email: false, project: project)
end
- it_behaves_like 'persists events'
-
it 'does not send notification' do
expect(NotificationService).not_to receive(:new)
@@ -311,45 +267,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
end
end
end
-
- context 'process incident issues' do
- before do
- create(:prometheus_service, project: project)
- create(:project_alerting_setting, project: project, token: token)
- end
-
- context 'with create_issue setting enabled' do
- before do
- setting.update!(create_issue: true)
- end
-
- it_behaves_like 'processes incident issues', 2
-
- context 'multiple firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [alert_firing, alert_firing], resolved: [])
- end
-
- it_behaves_like 'processes incident issues', 2
- end
-
- context 'without firing alerts' do
- let(:payload_raw) do
- prometheus_alert_payload(firing: [], resolved: [alert_resolved])
- end
-
- it_behaves_like 'processes incident issues', 1
- end
- end
-
- context 'with create_issue setting disabled' do
- before do
- setting.update!(create_issue: false)
- end
-
- it_behaves_like 'does not process incident issues'
- end
- end
end
context 'with invalid payload' do
@@ -380,13 +297,6 @@ RSpec.describe Projects::Prometheus::Alerts::NotifyService do
subject
end
-
- it 'does not process issues' do
- expect(IncidentManagement::ProcessPrometheusAlertWorker)
- .not_to receive(:perform_async)
-
- subject
- end
end
end
diff --git a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
index 7cd3d6b2494..c294892a66f 100644
--- a/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
+++ b/spec/workers/incident_management/process_prometheus_alert_worker_spec.rb
@@ -19,137 +19,9 @@ RSpec.describe IncidentManagement::ProcessPrometheusAlertWorker do
}.with_indifferent_access
end
- it 'creates an issue' do
+ it 'does nothing' do
expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'resolved event' do
- let(:issue) { create(:issue, project: project) }
-
- before do
- prometheus_alert_event.related_issues << issue
- prometheus_alert_event.resolve
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'closes the existing issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change { issue.reload.state }
- .from('opened')
- .to('closed')
- end
-
- it 'leaves a system note on the issue' do
- expect(SystemNoteService)
- .to receive(:auto_resolve_prometheus_alert)
-
- subject.perform(project.id, alert_params)
- end
- end
-
- context 'when project could not be found' do
- let(:non_existing_project_id) { non_existing_record_id }
-
- it 'does not create an issue' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(non_existing_project_id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:labels][:gitlab_alert_id] = non_existing_record_id
- end
-
- it 'does not create an issue' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(Issue, :count)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'when issue could not be created' do
- before do
- allow_next_instance_of(IncidentManagement::CreateIssueService) do |instance|
- allow(instance).to receive(:execute).and_return( { error: true } )
- end
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
-
- context 'self-managed alert' do
- let(:alert_name) { 'alert' }
- let(:starts_at) { Time.now.rfc3339 }
-
- let!(:prometheus_alert_event) do
- create(:self_managed_prometheus_alert_event, project: project, payload_key: payload_key)
- end
-
- let(:alert_params) do
- {
- startsAt: starts_at,
- generatorURL: 'http://localhost:9090/graph?g0.expr=vector%281%29&g0.tab=1',
- labels: {
- alertname: alert_name
- }
- }.with_indifferent_access
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'relates issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .to change(prometheus_alert_event.related_issues, :count)
- .from(0)
- .to(1)
- end
-
- context 'when event could not be found' do
- before do
- alert_params[:generatorURL] = 'http://somethingelse.com'
- end
-
- it 'creates an issue' do
- expect { subject.perform(project.id, alert_params) }
- .to change(Issue, :count)
- .by(1)
- end
-
- it 'does not relate issue to an event' do
- expect { subject.perform(project.id, alert_params) }
- .not_to change(prometheus_alert.related_issues, :count)
- end
- end
+ .not_to change(Issue, :count)
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 524b5692796..a7b2cfac381 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.151.0.tgz#099905295d33eb31033f4a48eb3652da2f686239"
integrity sha512-2PTSM8CFhUjeTFKfcq6E/YwPpOVdSVWupf3NhKO/bz/cisSBS5P7aWxaXKIaxy28ySyBKEfKaAT6b4rXTwvVgg==
-"@gitlab/ui@17.18.1":
- version "17.18.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.18.1.tgz#212b4560310919cc405a157da21a47b75981546c"
- integrity sha512-1VRPg5YnDuEs7SiDdYrT2kkNUHJhbD0PobnME1QW2bjCFjgbVHc9SvKNq9cbb0ao/SAyCefG3iC/aKJsQVhUmQ==
+"@gitlab/ui@17.19.0":
+ version "17.19.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.19.0.tgz#892e96375191f2937c48b393a4d0359f32541701"
+ integrity sha512-ysaOe/q5VJ/xM0G7sI9DX/mIzTBNppg6fH8R9D3Ste2shV0uwdZyaQmqsxUXFajuDvoLc2xrRBREYRIuDUPTqA==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"