Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/layout/argument_alignment.yml16
-rw-r--r--.rubocop_todo/layout/extra_spacing.yml8
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/duration_cell.vue9
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue2
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue4
-rw-r--r--app/assets/javascripts/notes/components/notes_app.vue2
-rw-r--r--app/assets/javascripts/notes/stores/actions.js112
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue31
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/index.js14
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/router.js14
-rw-r--r--app/assets/javascripts/packages_and_registries/dependency_proxy/utils.js24
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue2
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue13
-rw-r--r--app/assets/javascripts/security_configuration/components/constants.js2
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue13
-rw-r--r--app/assets/javascripts/sidebar/components/participants/participants.vue11
-rw-r--r--app/assets/javascripts/tracking/constants.js5
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue3
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss23
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss5
-rw-r--r--app/channels/noteable/notes_channel.rb1
-rw-r--r--app/controllers/concerns/preferred_language_switcher.rb30
-rw-r--r--app/controllers/projects/environments/sample_metrics_controller.rb16
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/finders/group_members_finder.rb16
-rw-r--r--app/models/concerns/noteable.rb2
-rw-r--r--app/models/metrics/users_starred_dashboard.rb18
-rw-r--r--app/models/project.rb1
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/compare_service.rb5
-rw-r--r--app/services/concerns/rate_limited_service.rb8
-rw-r--r--app/services/design_management/copy_design_collection/copy_service.rb4
-rw-r--r--app/services/design_management/delete_designs_service.rb6
-rw-r--r--app/services/design_management/runs_design_actions.rb10
-rw-r--r--app/services/design_management/save_designs_service.rb8
-rw-r--r--app/services/error_tracking/base_service.rb6
-rw-r--r--app/services/event_create_service.rb12
-rw-r--r--app/services/files/update_service.rb22
-rw-r--r--app/services/google_cloud/create_cloudsql_instance_service.rb38
-rw-r--r--app/services/google_cloud/fetch_google_ip_list_service.rb8
-rw-r--r--app/services/issuable_base_service.rb32
-rw-r--r--app/services/issues/close_service.rb10
-rw-r--r--app/services/issues/create_service.rb11
-rw-r--r--app/services/issues/move_service.rb20
-rw-r--r--app/services/metrics/sample_metrics_service.rb36
-rw-r--r--app/views/groups/dependency_proxies/show.html.haml1
-rw-r--r--app/views/profiles/preferences/show.html.haml2
-rw-r--r--config/feature_flags/development/custom_roles_in_members_page.yml (renamed from config/feature_flags/development/action_cable_notes.yml)8
-rw-r--r--config/routes/project.rb2
-rw-r--r--data/deprecations/15-6-deprecate-runner-reg-token-helm.yml2
-rw-r--r--data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml2
-rw-r--r--db/docs/packages_packages.yml1
-rw-r--r--doc/ci/runners/new_creation_workflow.md12
-rw-r--r--doc/security/token_overview.md4
-rw-r--r--doc/tutorials/build_application.md1
-rw-r--r--doc/update/deprecations.md4
-rw-r--r--doc/user/profile/preferences.md58
-rw-r--r--lib/tasks/gitlab/generate_sample_prometheus_data.rake27
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock6
-rw-r--r--spec/controllers/concerns/preferred_language_switcher_spec.rb41
-rw-r--r--spec/controllers/projects/environments/sample_metrics_controller_spec.rb55
-rw-r--r--spec/factories/metrics/users_starred_dashboards.rb9
-rw-r--r--spec/features/issues/note_polling_spec.rb16
-rw-r--r--spec/finders/group_members_finder_spec.rb35
-rw-r--r--spec/frontend/notes/components/comment_form_spec.js5
-rw-r--r--spec/frontend/notes/components/notes_app_spec.js1
-rw-r--r--spec/frontend/notes/stores/actions_spec.js174
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/app_spec.js61
-rw-r--r--spec/frontend/packages_and_registries/dependency_proxy/utils_spec.js25
-rw-r--r--spec/frontend/sidebar/components/assignees/assignees_spec.js2
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js2
-rw-r--r--spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js289
-rw-r--r--spec/models/metrics/users_starred_dashboard_spec.rb39
-rw-r--r--spec/models/note_spec.rb12
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/requests/api/metrics/user_starred_dashboards_spec.rb5
-rw-r--r--spec/services/bulk_imports/create_service_spec.rb2
-rw-r--r--spec/services/metrics/sample_metrics_service_spec.rb45
-rw-r--r--spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb12
-rw-r--r--spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb34
87 files changed, 658 insertions, 990 deletions
diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml
index ac0a8f8c8b7..6effb73e885 100644
--- a/.rubocop_todo/layout/argument_alignment.yml
+++ b/.rubocop_todo/layout/argument_alignment.yml
@@ -491,22 +491,6 @@ Layout/ArgumentAlignment:
- 'app/graphql/types/work_items/widgets/start_and_due_date_update_input_type.rb'
- 'app/graphql/types/x509_certificate_type.rb'
- 'app/graphql/types/x509_issuer_type.rb'
- - 'app/services/compare_service.rb'
- - 'app/services/concerns/rate_limited_service.rb'
- - 'app/services/design_management/copy_design_collection/copy_service.rb'
- - 'app/services/design_management/delete_designs_service.rb'
- - 'app/services/design_management/runs_design_actions.rb'
- - 'app/services/design_management/save_designs_service.rb'
- - 'app/services/error_tracking/base_service.rb'
- - 'app/services/event_create_service.rb'
- - 'app/services/files/update_service.rb'
- - 'app/services/google_cloud/create_cloudsql_instance_service.rb'
- - 'app/services/google_cloud/fetch_google_ip_list_service.rb'
- - 'app/services/issuable_base_service.rb'
- - 'app/services/issues/close_service.rb'
- - 'app/services/issues/create_service.rb'
- - 'app/services/issues/move_service.rb'
- - 'app/services/issues/referenced_merge_requests_service.rb'
- 'app/services/lfs/lock_file_service.rb'
- 'app/services/markdown_content_rewriter_service.rb'
- 'app/services/members/base_service.rb'
diff --git a/.rubocop_todo/layout/extra_spacing.yml b/.rubocop_todo/layout/extra_spacing.yml
deleted file mode 100644
index 6da17195834..00000000000
--- a/.rubocop_todo/layout/extra_spacing.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-# Cop supports --autocorrect.
-Layout/ExtraSpacing:
- Details: grace period
- Exclude:
- - 'ee/spec/requests/api/debian_project_packages_spec.rb'
- - 'spec/serializers/admin/abuse_report_details_entity_spec.rb'
- - 'spec/services/bulk_imports/create_service_spec.rb'
diff --git a/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
index 11593fa355a..dbf1dfe7a29 100644
--- a/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/duration_cell.vue
@@ -27,6 +27,9 @@ export default {
durationFormatted() {
return formatTime(this.duration * 1000);
},
+ hasDurationAndFinishedTime() {
+ return this.finishedTime && this.duration;
+ },
},
};
</script>
@@ -37,7 +40,11 @@ export default {
<gl-icon name="timer" :size="$options.iconSize" data-testid="duration-icon" />
{{ durationFormatted }}
</div>
- <div v-if="finishedTime" data-testid="job-finished-time">
+ <div
+ v-if="finishedTime"
+ :class="{ 'gl-mt-2': hasDurationAndFinishedTime }"
+ data-testid="job-finished-time"
+ >
<gl-icon name="calendar" :size="$options.iconSize" data-testid="finished-time-icon" />
<time-ago-tooltip :time="finishedTime" />
</div>
diff --git a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
index 1a6d1a341b0..c8f0fdd4439 100644
--- a/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/pipeline_cell.vue
@@ -41,7 +41,7 @@ export default {
{{ pipelineId }}
</gl-link>
</div>
- <div>
+ <div class="gl-font-sm gl-text-secondary gl-mt-2">
<span>{{ __('created by') }}</span>
<gl-link v-if="showAvatar" :href="userPath" data-testid="pipeline-user-link">
<gl-avatar :src="pipelineUserAvatar" :size="16" />
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index a009f2975bb..1a4add30f9f 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -210,8 +210,6 @@ export default {
methods: {
...mapActions([
'saveNote',
- 'stopPolling',
- 'restartPolling',
'removePlaceholderNotes',
'closeIssuable',
'reopenIssuable',
@@ -253,7 +251,6 @@ export default {
}
this.note = ''; // Empty textarea while being requested. Repopulate in catch
- this.stopPolling();
this.isSubmitting = true;
@@ -264,7 +261,6 @@ export default {
this.saveNote(noteData)
.then(() => {
- this.restartPolling();
this.discard();
if (withIssueAction) {
diff --git a/app/assets/javascripts/notes/components/notes_app.vue b/app/assets/javascripts/notes/components/notes_app.vue
index 6fb958e810b..2524b9efdb6 100644
--- a/app/assets/javascripts/notes/components/notes_app.vue
+++ b/app/assets/javascripts/notes/components/notes_app.vue
@@ -169,7 +169,6 @@ export default {
});
},
beforeDestroy() {
- this.stopPolling();
window.removeEventListener('hashchange', this.handleHashChanged);
eventHub.$off('notesApp.updateIssuableConfidentiality', this.setConfidentiality);
},
@@ -182,7 +181,6 @@ export default {
'expandDiscussion',
'startTaskList',
'convertToDiscussion',
- 'stopPolling',
'setConfidentiality',
'fetchNotes',
]),
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 0444eca9aa7..a20bce2e53a 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -1,5 +1,4 @@
import $ from 'jquery';
-import Visibility from 'visibilityjs';
import Vue from 'vue';
import actionCable from '~/actioncable_consumer';
import Api from '~/api';
@@ -14,8 +13,6 @@ import updateIssueLockMutation from '~/sidebar/queries/update_issue_lock.mutatio
import updateMergeRequestLockMutation from '~/sidebar/queries/update_merge_request_lock.mutation.graphql';
import loadAwardsHandler from '~/awards_handler';
import { isInViewport, scrollToElement, isInMRPage } from '~/lib/utils/common_utils';
-import Poll from '~/lib/utils/poll';
-import { create } from '~/lib/utils/recurrence';
import { mergeUrlParams } from '~/lib/utils/url_utility';
import sidebarTimeTrackingEventHub from '~/sidebar/event_hub';
import TaskList from '~/task_list';
@@ -30,9 +27,6 @@ import * as constants from '../constants';
import * as types from './mutation_types';
import * as utils from './utils';
-const NOTES_POLLING_INTERVAL = 6000;
-let eTagPoll;
-
export const updateLockedAttribute = ({ commit, getters }, { locked, fullPath }) => {
const { iid, targetType } = getters.getNoteableData;
@@ -152,29 +146,25 @@ export const initPolling = ({ state, dispatch, getters, commit }) => {
dispatch('setLastFetchedAt', getters.getNotesDataByProp('lastFetchedAt'));
- if (gon.features?.actionCableNotes) {
- actionCable.subscriptions.create(
- {
- channel: 'Noteable::NotesChannel',
- project_id: state.notesData.projectId,
- group_id: state.notesData.groupId,
- noteable_type: state.notesData.noteableType,
- noteable_id: state.notesData.noteableId,
+ actionCable.subscriptions.create(
+ {
+ channel: 'Noteable::NotesChannel',
+ project_id: state.notesData.projectId,
+ group_id: state.notesData.groupId,
+ noteable_type: state.notesData.noteableType,
+ noteable_id: state.notesData.noteableId,
+ },
+ {
+ connected() {
+ dispatch('fetchUpdatedNotes');
},
- {
- connected() {
+ received(data) {
+ if (data.event === 'updated') {
dispatch('fetchUpdatedNotes');
- },
- received(data) {
- if (data.event === 'updated') {
- dispatch('fetchUpdatedNotes');
- }
- },
+ }
},
- );
- } else {
- dispatch('poll');
- }
+ },
+ );
commit(types.SET_IS_POLLING_INITIALIZED, true);
};
@@ -515,8 +505,6 @@ export const saveNote = ({ commit, dispatch }, noteData) => {
{"commands_changes":{},"valid":false,"errors":{"commands_only":["Commands applied"]}}
*/
if (hasQuickActions && message) {
- if (eTagPoll) eTagPoll.makeRequest();
-
// synchronizing the quick action with the sidebar widget
// this is a temporary solution until we have confidentiality real-time updates
if (
@@ -624,69 +612,7 @@ export const fetchUpdatedNotes = ({ commit, state, getters, dispatch }) => {
.then(({ data }) => {
pollSuccessCallBack(data, commit, state, getters, dispatch);
})
- .catch(() => {
- createAlert({
- message: __('Something went wrong while fetching latest comments.'),
- });
- });
-};
-
-export const poll = ({ commit, state, getters, dispatch }) => {
- const notePollOccurrenceTracking = create();
- let alert;
-
- notePollOccurrenceTracking.handle(1, () => {
- // Since polling halts internally after 1 failure, we manually try one more time
- setTimeout(() => eTagPoll.restart(), NOTES_POLLING_INTERVAL);
- });
- notePollOccurrenceTracking.handle(2, () => {
- // On the second failure in a row, show the alert and try one more time (hoping to succeed and clear the error)
- alert = createAlert({
- message: __('Something went wrong while fetching latest comments.'),
- });
- setTimeout(() => eTagPoll.restart(), NOTES_POLLING_INTERVAL);
- });
-
- eTagPoll = new Poll({
- resource: {
- poll: () => {
- const { endpoint, options } = getFetchDataParams(state);
- return axios.get(endpoint, options);
- },
- },
- method: 'poll',
- successCallback: ({ data }) => {
- pollSuccessCallBack(data, commit, state, getters, dispatch);
-
- if (notePollOccurrenceTracking.count) {
- notePollOccurrenceTracking.reset();
- }
- alert?.dismiss();
- },
- errorCallback: () => notePollOccurrenceTracking.occur(),
- });
-
- if (!Visibility.hidden()) {
- eTagPoll.makeDelayedRequest(2500);
- } else {
- eTagPoll.makeRequest();
- }
-
- Visibility.change(() => {
- if (!Visibility.hidden()) {
- eTagPoll.restart();
- } else {
- eTagPoll.stop();
- }
- });
-};
-
-export const stopPolling = () => {
- if (eTagPoll) eTagPoll.stop();
-};
-
-export const restartPolling = () => {
- if (eTagPoll) eTagPoll.restart();
+ .catch(() => {});
};
export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
@@ -766,7 +692,6 @@ export const submitSuggestion = (
dispatch('resolveDiscussion', { discussionId }).catch(() => {});
commit(types.SET_RESOLVING_DISCUSSION, true);
- dispatch('stopPolling');
return Api.applySuggestion(suggestionId, message)
.then(dispatchResolveDiscussion)
@@ -786,7 +711,6 @@ export const submitSuggestion = (
})
.finally(() => {
commit(types.SET_RESOLVING_DISCUSSION, false);
- dispatch('restartPolling');
});
};
@@ -801,7 +725,6 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { message, fl
commit(types.SET_APPLYING_BATCH_STATE, true);
commit(types.SET_RESOLVING_DISCUSSION, true);
- dispatch('stopPolling');
return Api.applySuggestionBatch(suggestionIds, message)
.then(() => Promise.all(resolveAllDiscussions()))
@@ -823,7 +746,6 @@ export const submitSuggestionBatch = ({ commit, dispatch, state }, { message, fl
.finally(() => {
commit(types.SET_APPLYING_BATCH_STATE, false);
commit(types.SET_RESOLVING_DISCUSSION, false);
- dispatch('restartPolling');
});
};
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
index e18e6f7ed1a..6e0d1043502 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/app.vue
@@ -18,6 +18,8 @@ import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
+import { getPageParams } from '~/packages_and_registries/dependency_proxy/utils';
+import { extractPageInfo } from '~/packages_and_registries/shared/utils';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
@@ -79,11 +81,15 @@ export default {
},
computed: {
queryVariables() {
- return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE };
+ return { fullPath: this.groupPath, first: GRAPHQL_PAGE_SIZE, ...this.pageParams };
},
pageInfo() {
return this.group.dependencyProxyManifests?.pageInfo;
},
+ pageParams() {
+ const pageInfo = extractPageInfo(this.$route.query);
+ return getPageParams(pageInfo);
+ },
manifests() {
return this.group.dependencyProxyManifests?.nodes ?? [];
},
@@ -123,25 +129,10 @@ export default {
},
methods: {
fetchNextPage() {
- this.fetchMore({
- first: GRAPHQL_PAGE_SIZE,
- after: this.pageInfo?.endCursor,
- });
+ this.$router.push({ query: { after: this.pageInfo?.endCursor } });
},
fetchPreviousPage() {
- this.fetchMore({
- first: null,
- last: GRAPHQL_PAGE_SIZE,
- before: this.pageInfo?.startCursor,
- });
- },
- fetchMore(variables) {
- this.$apollo.queries.group.fetchMore({
- variables: { ...this.queryVariables, ...variables },
- updateQuery(_, { fetchMoreResult }) {
- return fetchMoreResult;
- },
- });
+ this.$router.push({ query: { before: this.pageInfo?.startCursor } });
},
async submit() {
try {
@@ -198,14 +189,14 @@ export default {
<gl-form-input-group
id="proxy-url"
readonly
- :value="group.dependencyProxyImagePrefix"
+ :value="dependencyProxyImagePrefix"
select-on-click
class="gl-layout-w-limited"
data-testid="proxy-url"
>
<template #append>
<clipboard-button
- :text="group.dependencyProxyImagePrefix"
+ :text="dependencyProxyImagePrefix"
:title="$options.i18n.copyImagePrefixText"
/>
</template>
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
index 74444d2c7ec..c115898c75b 100644
--- a/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/index.js
@@ -1,8 +1,8 @@
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
-import app from '~/packages_and_registries/dependency_proxy/app.vue';
import { apolloProvider } from '~/packages_and_registries/dependency_proxy/graphql';
import Translate from '~/vue_shared/translate';
+import createRouter from './router';
Vue.use(Translate);
@@ -11,10 +11,18 @@ export const initDependencyProxyApp = () => {
if (!el) {
return null;
}
- const { groupPath, groupId, noManifestsIllustration, canClearCache, settingsPath } = el.dataset;
+ const {
+ endpoint,
+ groupPath,
+ groupId,
+ noManifestsIllustration,
+ canClearCache,
+ settingsPath,
+ } = el.dataset;
return new Vue({
el,
apolloProvider,
+ router: createRouter(endpoint),
provide: {
groupPath,
groupId,
@@ -23,7 +31,7 @@ export const initDependencyProxyApp = () => {
settingsPath,
},
render(createElement) {
- return createElement(app);
+ return createElement('router-view');
},
});
};
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/router.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/router.js
new file mode 100644
index 00000000000..087d8c189c4
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/router.js
@@ -0,0 +1,14 @@
+import Vue from 'vue';
+import VueRouter from 'vue-router';
+import App from '~/packages_and_registries/dependency_proxy/app.vue';
+
+Vue.use(VueRouter);
+
+export default function createRouter(base) {
+ const routes = [{ path: '/', name: 'dependencyProxyApp', component: App }];
+ return new VueRouter({
+ mode: 'history',
+ base,
+ routes,
+ });
+}
diff --git a/app/assets/javascripts/packages_and_registries/dependency_proxy/utils.js b/app/assets/javascripts/packages_and_registries/dependency_proxy/utils.js
new file mode 100644
index 00000000000..e6b97fac896
--- /dev/null
+++ b/app/assets/javascripts/packages_and_registries/dependency_proxy/utils.js
@@ -0,0 +1,24 @@
+import { GRAPHQL_PAGE_SIZE } from './constants';
+
+const getNextPageParams = (cursor) => ({
+ after: cursor,
+ first: GRAPHQL_PAGE_SIZE,
+});
+
+const getPreviousPageParams = (cursor) => ({
+ first: null,
+ before: cursor,
+ last: GRAPHQL_PAGE_SIZE,
+});
+
+export const getPageParams = (pageInfo = {}) => {
+ if (pageInfo.before) {
+ return getPreviousPageParams(pageInfo.before);
+ }
+
+ if (pageInfo.after) {
+ return getNextPageParams(pageInfo.after);
+ }
+
+ return {};
+};
diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue
index a5c6dc98694..8567654a89e 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/linked_pipelines_mini_list.vue
@@ -105,7 +105,7 @@ export default {
v-gl-tooltip="{ title: pipelineTooltipText(pipeline) }"
:href="pipeline.path"
:class="triggerButtonClass(pipeline)"
- class="linked-pipeline-mini-item gl-display-inline-block gl-h-6 gl-mr-2 gl-my-2 gl-rounded-full gl-vertical-align-middle"
+ class="linked-pipeline-mini-item gl-display-inline-flex gl-mr-2 gl-my-2 gl-rounded-full gl-vertical-align-middle"
data-testid="linked-pipeline-mini-item"
>
<ci-icon
diff --git a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue
index 02dba9ba30f..f883833f7ea 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_mini_graph/pipeline_stages.vue
@@ -42,7 +42,7 @@ export default {
<div
v-for="stage in stages"
:key="stage.name"
- class="pipeline-mini-graph-stage-container dropdown gl-display-inline-block gl-mr-2 gl-my-2 gl-vertical-align-middle"
+ class="pipeline-mini-graph-stage-container dropdown gl-display-inline-flex gl-mr-2 gl-my-2 gl-vertical-align-middle"
>
<pipeline-stage
v-if="isGraphql"
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
index 60c429459bf..c01037e9791 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue
@@ -91,7 +91,7 @@ export default {
<template #header>
<gl-button
variant="link"
- class="gl-text-gray-700! gl-font-weight-semibold"
+ class="gl-text-gray-500! gl-font-weight-semibold"
@click="toggleWidget"
>
<gl-icon :name="iconName" />
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index c7d89113895..32d46a0d4af 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -1,13 +1,18 @@
<script>
import { GlTab, GlTabs, GlSprintf, GlLink, GlAlert } from '@gitlab/ui';
+import Api from '~/api';
import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import SectionLayout from '~/vue_shared/security_configuration/components/section_layout.vue';
import SafeHtml from '~/vue_shared/directives/safe_html';
+import { SERVICE_PING_SECURITY_CONFIGURATION_THREAT_MANAGEMENT_VISIT } from '~/tracking/constants';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
-import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
+import {
+ AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
+ TAB_VULNERABILITY_MANAGEMENT_INDEX,
+} from './constants';
import FeatureCard from './feature_card.vue';
import TrainingProviderList from './training_provider_list.vue';
@@ -123,6 +128,11 @@ export default {
dismissAlert() {
this.errorMessage = '';
},
+ tabChange(value) {
+ if (value === TAB_VULNERABILITY_MANAGEMENT_INDEX) {
+ Api.trackRedisHllUserEvent(SERVICE_PING_SECURITY_CONFIGURATION_THREAT_MANAGEMENT_VISIT);
+ }
+ },
},
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
};
@@ -167,6 +177,7 @@ export default {
data-testid="security-configuration-container"
sync-active-tab-with-query-params
lazy
+ @input="tabChange"
>
<gl-tab
data-testid="security-testing-tab"
diff --git a/app/assets/javascripts/security_configuration/components/constants.js b/app/assets/javascripts/security_configuration/components/constants.js
index b427820144d..fd713a7a504 100644
--- a/app/assets/javascripts/security_configuration/components/constants.js
+++ b/app/assets/javascripts/security_configuration/components/constants.js
@@ -326,3 +326,5 @@ export const TEMP_PROVIDER_URLS = {
[__('Secure Code Warrior')]: 'https://www.securecodewarrior.com/',
SecureFlag: 'https://www.secureflag.com/',
};
+
+export const TAB_VULNERABILITY_MANAGEMENT_INDEX = 1;
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
index 930e7ff12d9..ef7f12f273f 100644
--- a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -1,4 +1,5 @@
<script>
+import { GlButton } from '@gitlab/ui';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __, sprintf } from '~/locale';
@@ -9,6 +10,7 @@ const DEFAULT_RENDER_COUNT = 5;
export default {
components: {
+ GlButton,
AssigneeAvatarLink,
UserNameWithStatus,
},
@@ -97,10 +99,11 @@ export default {
</assignee-avatar-link>
</div>
</div>
- <div v-if="renderShowMoreSection" class="user-list-more gl-hover-text-blue-800">
- <button
- type="button"
- class="btn-link gl-button gl-reset-color!"
+ <div v-if="renderShowMoreSection" class="gl-hover-text-blue-800" data-testid="user-list-more">
+ <gl-button
+ category="tertiary"
+ size="small"
+ data-testid="user-list-more-button"
data-qa-selector="more_assignees_link"
@click="toggleShowLess"
>
@@ -108,7 +111,7 @@ export default {
{{ hiddenAssigneesLabel }}
</template>
<template v-else>{{ __('- show less') }}</template>
- </button>
+ </gl-button>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/participants/participants.vue b/app/assets/javascripts/sidebar/components/participants/participants.vue
index 7b288e15a3e..99d36a61632 100644
--- a/app/assets/javascripts/sidebar/components/participants/participants.vue
+++ b/app/assets/javascripts/sidebar/components/participants/participants.vue
@@ -138,13 +138,10 @@ export default {
</a>
</div>
</div>
- <div v-if="hasMoreParticipants" class="participants-more hide-collapsed">
- <gl-button
- variant="link"
- button-text-classes="gl-text-secondary"
- @click="toggleMoreParticipants"
- >{{ toggleLabel }}</gl-button
- >
+ <div v-if="hasMoreParticipants" class="hide-collapsed">
+ <gl-button category="tertiary" size="small" @click="toggleMoreParticipants">{{
+ toggleLabel
+ }}</gl-button>
</div>
</div>
</template>
diff --git a/app/assets/javascripts/tracking/constants.js b/app/assets/javascripts/tracking/constants.js
index 114587bb363..7c4bff18272 100644
--- a/app/assets/javascripts/tracking/constants.js
+++ b/app/assets/javascripts/tracking/constants.js
@@ -31,3 +31,8 @@ export const GOOGLE_ANALYTICS_ID_COOKIE_NAME = '_ga';
export const GITLAB_INTERNAL_EVENT_CATEGORY = 'InternalEventTracking';
export const SERVICE_PING_SCHEMA = 'iglu:com.gitlab/gitlab_service_ping/jsonschema/1-0-0';
+
+export const SERVICE_PING_SECURITY_CONFIGURATION_THREAT_MANAGEMENT_VISIT =
+ 'users_visiting_security_configuration_threat_management';
+
+export const SERVICE_PING_PIPELINE_SECURITY_VISIT = 'users_visiting_pipeline_security';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
index e94e0fbe6dc..eec51ddde53 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue
@@ -205,9 +205,7 @@ export default {
data-qa-selector="merge_request_pipeline_info_content"
class="gl-display-flex gl-flex-wrap gl-align-items-center gl-justify-content-space-between"
>
- <p
- class="mr-pipeline-title gl-m-0! gl-mr-3! gl-font-weight-bold gl-line-height-32 gl-text-gray-900"
- >
+ <p class="mr-pipeline-title gl-m-0! gl-mr-3! gl-font-weight-bold gl-text-gray-900">
{{ pipeline.details.event_type_name }}
<gl-link
:href="pipeline.path"
@@ -253,7 +251,7 @@ export default {
v-safe-html="sourceBranchLink"
:title="sourceBranch"
truncate-target="child"
- class="label-branch label-truncate gl-font-weight-normal gl-vertical-align-text-bottom"
+ class="label-branch label-truncate gl-font-weight-normal"
/>
</template>
<template v-if="finishedAt">
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index acdcbf7afd7..537fdf8cdf8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -569,7 +569,7 @@ export default {
v-if="hasMergeError"
type="danger"
dismissible
- data-testid="merge_error"
+ data-testid="merge-error"
>
<span v-safe-html="mergeError"></span>
</mr-widget-alert-message>
@@ -577,6 +577,7 @@ export default {
v-if="showMergePipelineForkWarning"
type="warning"
:help-path="mr.mergeRequestPipelinesHelpPath"
+ data-testid="merge-pipeline-fork-warning"
>
{{
s__(
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 5f90dd62426..600a40da98b 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -799,29 +799,6 @@
}
}
-.participants-more,
-.user-list-more {
- margin-left: 5px;
-
- a,
- .btn-link {
- color: $gl-text-color-secondary;
- }
-
- .btn-link {
- padding: 0;
- }
-
- .btn-link:hover {
- color: $blue-800;
- text-decoration: none;
- }
-
- .btn-link:focus {
- text-decoration: none;
- }
-}
-
.sidebar-help-wrap {
.sidebar-help-state {
margin: 16px -20px -20px;
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index c2c38de9aca..14c23c2b7a9 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -416,7 +416,7 @@ $tabs-holder-z-index: 250;
.media-body {
min-width: 0;
- font-size: 12px;
+ font-size: $gl-font-size-sm;
margin-left: 32px;
}
@@ -648,7 +648,6 @@ $tabs-holder-z-index: 250;
.label-branch {
@include gl-font-monospace;
- font-size: 95%;
overflow: hidden;
word-break: break-all;
}
@@ -662,7 +661,7 @@ $tabs-holder-z-index: 250;
> span {
display: inline-block;
max-width: 12.5em;
- margin-bottom: -5px;
+ margin-bottom: px-to-rem(-5px);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/app/channels/noteable/notes_channel.rb b/app/channels/noteable/notes_channel.rb
index 021bc3ccd1b..8ce6c15e123 100644
--- a/app/channels/noteable/notes_channel.rb
+++ b/app/channels/noteable/notes_channel.rb
@@ -13,7 +13,6 @@ module Noteable
}).target
return reject if noteable.nil?
- return reject if Feature.disabled?(:action_cable_notes, project || noteable.try(:group))
stream_for noteable
rescue ActiveRecord::RecordNotFound
diff --git a/app/controllers/concerns/preferred_language_switcher.rb b/app/controllers/concerns/preferred_language_switcher.rb
index 872652100c9..5ac75e40b04 100644
--- a/app/controllers/concerns/preferred_language_switcher.rb
+++ b/app/controllers/concerns/preferred_language_switcher.rb
@@ -2,6 +2,8 @@
module PreferredLanguageSwitcher
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
+ include PreferredLanguageSwitcherHelper
private
@@ -11,8 +13,36 @@ module PreferredLanguageSwitcher
def preferred_language
cookies[:preferred_language].presence_in(Gitlab::I18n.available_locales) ||
+ selectable_language(marketing_site_language) ||
+ selectable_language(browser_languages) ||
Gitlab::CurrentSettings.default_preferred_language
end
+
+ def selectable_language(language_options)
+ language_options.find { |lan| ordered_selectable_locales_codes.include?(lan) }
+ end
+
+ def browser_languages
+ http_language_header = request.env['HTTP_ACCEPT_LANGUAGE']
+ return [] unless http_language_header
+
+ http_language_header.tr!('-', '_').split(%r{[;,]}).reject { |str| str.start_with?('q') }
+ end
+ strong_memoize_attr :browser_languages
+
+ def marketing_site_language
+ return [] unless params[:glm_source]
+
+ locale = params[:glm_source].scan(%r{(\w{2})-(\w{2})/}).flatten
+
+ return [] if locale.empty?
+
+ [locale[0], "#{locale[0]}_#{locale[1]}"]
+ end
+
+ def ordered_selectable_locales_codes
+ ordered_selectable_locales.map { |locale| locale[:value] } # rubocop:disable Rails/Pluck
+ end
end
PreferredLanguageSwitcher.prepend_mod
diff --git a/app/controllers/projects/environments/sample_metrics_controller.rb b/app/controllers/projects/environments/sample_metrics_controller.rb
deleted file mode 100644
index 80344c83ab7..00000000000
--- a/app/controllers/projects/environments/sample_metrics_controller.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-# frozen_string_literal: true
-
-class Projects::Environments::SampleMetricsController < Projects::ApplicationController
- feature_category :metrics
- urgency :low
-
- def query
- result = Metrics::SampleMetricsService.new(params[:identifier], range_start: params[:start], range_end: params[:end]).query
-
- if result
- render json: { "status": "success", "data": { "resultType": "matrix", "result": result } }
- else
- render_404
- end
- end
-end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 99055d5c6b7..620f900a751 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -73,7 +73,6 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:move_close_into_dropdown, project)
- push_frontend_feature_flag(:action_cable_notes, project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index cfa7117b272..f5a9e9e6fd3 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -49,7 +49,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_activity_filters, current_user)
push_frontend_feature_flag(:review_apps_redeploy_mr_widget, project)
push_frontend_feature_flag(:ci_job_failures_in_mr, project)
- push_frontend_feature_flag(:action_cable_notes, project)
push_frontend_feature_flag(:mr_pipelines_graphql, project)
end
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index 639db58b00d..0336135835a 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -20,6 +20,8 @@ class GroupMembersFinder < UnionFinder
# search: string
# created_after: datetime
# created_before: datetime
+ # non_invite: boolean
+ # with_custom_role: boolean
attr_reader :params
def initialize(group, user = nil, params: {})
@@ -34,7 +36,10 @@ class GroupMembersFinder < UnionFinder
Group.shared_into_ancestors(group).public_or_visible_to_user(user)
end
- members = all_group_members(groups, shared_from_groups).distinct_on_user_with_max_access_level
+ members = all_group_members(groups, shared_from_groups)
+ if static_roles_only?
+ members = members.distinct_on_user_with_max_access_level
+ end
filter_members(members)
end
@@ -70,7 +75,10 @@ class GroupMembersFinder < UnionFinder
members = filter_by_user_type(members)
members = apply_additional_filters(members)
- by_created_at(members)
+ members = by_created_at(members)
+ members = members.non_invite if params[:non_invite]
+
+ members
end
def can_manage_members
@@ -137,6 +145,10 @@ class GroupMembersFinder < UnionFinder
# overridden in EE to include additional filtering conditions.
members
end
+
+ def static_roles_only?
+ true
+ end
end
GroupMembersFinder.prepend_mod_with('GroupMembersFinder')
diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb
index 40a91c8ac94..411c343a7d7 100644
--- a/app/models/concerns/noteable.rb
+++ b/app/models/concerns/noteable.rb
@@ -175,7 +175,7 @@ module Noteable
# TODO: We need to figure out a way to make ETag caching work for group-level work items
Gitlab::EtagCaching::Store.new.touch(note_etag_key) unless is_a?(Issue) && project.nil?
- Noteable::NotesChannel.broadcast_to(self, event: 'updated') if Feature.enabled?(:action_cable_notes, project || try(:group))
+ Noteable::NotesChannel.broadcast_to(self, event: 'updated')
end
def note_etag_key
diff --git a/app/models/metrics/users_starred_dashboard.rb b/app/models/metrics/users_starred_dashboard.rb
deleted file mode 100644
index 07748eb1431..00000000000
--- a/app/models/metrics/users_starred_dashboard.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-# frozen_string_literal: true
-
-module Metrics
- class UsersStarredDashboard < ApplicationRecord
- self.table_name = 'metrics_users_starred_dashboards'
-
- belongs_to :user, inverse_of: :metrics_users_starred_dashboards
- belongs_to :project, inverse_of: :metrics_users_starred_dashboards
-
- validates :user_id, presence: true
- validates :project_id, presence: true
- validates :dashboard_path, presence: true, length: { maximum: 255 }
- validates :dashboard_path, uniqueness: { scope: %i[user_id project_id] }
-
- scope :for_project, ->(project) { where(project: project) }
- scope :for_project_dashboard, ->(project, path) { for_project(project).where(dashboard_path: path) }
- end
-end
diff --git a/app/models/project.rb b/app/models/project.rb
index a57a3247c8a..3d97628c216 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -373,7 +373,6 @@ class Project < ApplicationRecord
has_many :prometheus_alerts, inverse_of: :project
has_many :prometheus_alert_events, inverse_of: :project
has_many :self_managed_prometheus_alert_events, inverse_of: :project
- has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :project
has_many :alert_management_alerts, class_name: 'AlertManagement::Alert', inverse_of: :project
has_many :alert_management_http_integrations, class_name: 'AlertManagement::HttpIntegration', inverse_of: :project
diff --git a/app/models/user.rb b/app/models/user.rb
index 343034b1e38..2d679a6b614 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -258,8 +258,6 @@ class User < MainClusterwide::ApplicationRecord
has_many :organization_users, class_name: 'Organizations::OrganizationUser', inverse_of: :user
has_many :organizations, through: :organization_users, class_name: 'Organizations::Organization', inverse_of: :users
- has_many :metrics_users_starred_dashboards, class_name: 'Metrics::UsersStarredDashboard', inverse_of: :user
-
has_one :status, class_name: 'UserStatus'
has_one :user_preference
has_one :user_detail
diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb
index 569b91de73e..b02cfea151d 100644
--- a/app/services/compare_service.rb
+++ b/app/services/compare_service.rb
@@ -17,9 +17,6 @@ class CompareService
return unless raw_compare && raw_compare.base && raw_compare.head
- Compare.new(raw_compare,
- start_project,
- base_sha: base_sha,
- straight: straight)
+ Compare.new(raw_compare, start_project, base_sha: base_sha, straight: straight)
end
end
diff --git a/app/services/concerns/rate_limited_service.rb b/app/services/concerns/rate_limited_service.rb
index fa366c1ccd0..79be952ac14 100644
--- a/app/services/concerns/rate_limited_service.rb
+++ b/app/services/concerns/rate_limited_service.rb
@@ -61,9 +61,11 @@ module RateLimitedService
cattr_accessor :rate_limiter_scoped_and_keyed
def self.rate_limit(key:, opts:, rate_limiter: ::Gitlab::ApplicationRateLimiter)
- self.rate_limiter_scoped_and_keyed = RateLimiterScopedAndKeyed.new(key: key,
- opts: opts,
- rate_limiter: rate_limiter)
+ self.rate_limiter_scoped_and_keyed = RateLimiterScopedAndKeyed.new(
+ key: key,
+ opts: opts,
+ rate_limiter: rate_limiter
+ )
end
end
diff --git a/app/services/design_management/copy_design_collection/copy_service.rb b/app/services/design_management/copy_design_collection/copy_service.rb
index 8074a193bbf..d391c13696f 100644
--- a/app/services/design_management/copy_design_collection/copy_service.rb
+++ b/app/services/design_management/copy_design_collection/copy_service.rb
@@ -58,8 +58,8 @@ module DesignManagement
private
attr_reader :designs, :event_enum_map, :git_user, :sha_attribute, :shas,
- :temporary_branch, :target_design_collection, :target_issue,
- :target_repository, :target_project, :versions
+ :temporary_branch, :target_design_collection, :target_issue,
+ :target_repository, :target_project, :versions
alias_method :merge_branch, :target_branch
diff --git a/app/services/design_management/delete_designs_service.rb b/app/services/design_management/delete_designs_service.rb
index 921c904d8de..a6a0f5e0252 100644
--- a/app/services/design_management/delete_designs_service.rb
+++ b/app/services/design_management/delete_designs_service.rb
@@ -16,8 +16,10 @@ module DesignManagement
version = delete_designs!
EventCreateService.new.destroy_designs(designs, current_user)
- Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_designs_removed_action(author: current_user,
- project: project)
+ Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_designs_removed_action(
+ author: current_user,
+ project: project
+ )
TodosDestroyer::DestroyedDesignsWorker.perform_async(designs.map(&:id))
success(version: version)
diff --git a/app/services/design_management/runs_design_actions.rb b/app/services/design_management/runs_design_actions.rb
index 267ed6bf29f..62db7824592 100644
--- a/app/services/design_management/runs_design_actions.rb
+++ b/app/services/design_management/runs_design_actions.rb
@@ -15,10 +15,12 @@ module DesignManagement
def run_actions(actions, skip_system_notes: false)
raise NoActions if actions.empty?
- sha = repository.commit_files(current_user,
- branch_name: target_branch,
- message: commit_message,
- actions: actions.map(&:gitaly_action))
+ sha = repository.commit_files(
+ current_user,
+ branch_name: target_branch,
+ message: commit_message,
+ actions: actions.map(&:gitaly_action)
+ )
::DesignManagement::Version
.create_for_designs(actions, sha, current_user)
diff --git a/app/services/design_management/save_designs_service.rb b/app/services/design_management/save_designs_service.rb
index ea5675c6ddd..4c4e34862e8 100644
--- a/app/services/design_management/save_designs_service.rb
+++ b/app/services/design_management/save_designs_service.rb
@@ -131,11 +131,11 @@ module DesignManagement
def track_usage_metrics(action)
if action == :update
- ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_designs_modified_action(author: current_user,
- project: project)
+ ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
+ .track_issue_designs_modified_action(author: current_user, project: project)
else
- ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter.track_issue_designs_added_action(author: current_user,
- project: project)
+ ::Gitlab::UsageDataCounters::IssueActivityUniqueCounter
+ .track_issue_designs_added_action(author: current_user, project: project)
end
::Gitlab::UsageDataCounters::DesignsCounter.count(action)
diff --git a/app/services/error_tracking/base_service.rb b/app/services/error_tracking/base_service.rb
index 8458eb1f3b8..e95d4eec3c8 100644
--- a/app/services/error_tracking/base_service.rb
+++ b/app/services/error_tracking/base_service.rb
@@ -17,8 +17,7 @@ module ErrorTracking
private
def perform
- raise NotImplementedError,
- "#{self.class} does not implement #{__method__}"
+ raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def compose_response(response, &block)
@@ -33,8 +32,7 @@ module ErrorTracking
end
def parse_response(response)
- raise NotImplementedError,
- "#{self.class} does not implement #{__method__}"
+ raise NotImplementedError, "#{self.class} does not implement #{__method__}"
end
def unauthorized
diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb
index 1893cfcfcff..b755f512772 100644
--- a/app/services/event_create_service.rb
+++ b/app/services/event_create_service.rb
@@ -190,10 +190,14 @@ class EventCreateService
private
def create_record_event(record, current_user, status, fingerprint = nil)
- create_event(record.resource_parent, current_user, status,
- fingerprint: fingerprint,
- target_id: record.id,
- target_type: record.class.name)
+ create_event(
+ record.resource_parent,
+ current_user,
+ status,
+ fingerprint: fingerprint,
+ target_id: record.id,
+ target_type: record.class.name
+ )
end
# If creating several events, this method will insert them all in a single
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 9fa966bb8a8..c11917b92ec 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,15 +3,19 @@
module Files
class UpdateService < Files::BaseService
def create_commit!
- repository.update_file(current_user, @file_path, @file_content,
- message: @commit_message,
- branch_name: @branch_name,
- previous_path: @previous_path,
- author_email: @author_email,
- author_name: @author_name,
- start_project: @start_project,
- start_branch_name: @start_branch,
- execute_filemode: @execute_filemode)
+ repository.update_file(
+ current_user,
+ @file_path,
+ @file_content,
+ message: @commit_message,
+ branch_name: @branch_name,
+ previous_path: @previous_path,
+ author_email: @author_email,
+ author_name: @author_name,
+ start_project: @start_project,
+ start_branch_name: @start_branch,
+ execute_filemode: @execute_filemode
+ )
end
private
diff --git a/app/services/google_cloud/create_cloudsql_instance_service.rb b/app/services/google_cloud/create_cloudsql_instance_service.rb
index 8d040c6c908..9a1263f0796 100644
--- a/app/services/google_cloud/create_cloudsql_instance_service.rb
+++ b/app/services/google_cloud/create_cloudsql_instance_service.rb
@@ -17,26 +17,30 @@ module GoogleCloud
private
def create_cloud_instance
- google_api_client.create_cloudsql_instance(gcp_project_id,
- instance_name,
- root_password,
- database_version,
- region,
- tier)
+ google_api_client.create_cloudsql_instance(
+ gcp_project_id,
+ instance_name,
+ root_password,
+ database_version,
+ region,
+ tier
+ )
end
def trigger_instance_setup_worker
- GoogleCloud::CreateCloudsqlInstanceWorker.perform_in(WORKER_INTERVAL,
- current_user.id,
- project.id,
- {
- 'google_oauth2_token': google_oauth2_token,
- 'gcp_project_id': gcp_project_id,
- 'instance_name': instance_name,
- 'database_version': database_version,
- 'environment_name': environment_name,
- 'is_protected': protected?
- })
+ GoogleCloud::CreateCloudsqlInstanceWorker.perform_in(
+ WORKER_INTERVAL,
+ current_user.id,
+ project.id,
+ {
+ 'google_oauth2_token': google_oauth2_token,
+ 'gcp_project_id': gcp_project_id,
+ 'instance_name': instance_name,
+ 'database_version': database_version,
+ 'environment_name': environment_name,
+ 'is_protected': protected?
+ }
+ )
end
def protected?
diff --git a/app/services/google_cloud/fetch_google_ip_list_service.rb b/app/services/google_cloud/fetch_google_ip_list_service.rb
index f7739971603..54af841d002 100644
--- a/app/services/google_cloud/fetch_google_ip_list_service.rb
+++ b/app/services/google_cloud/fetch_google_ip_list_service.rb
@@ -18,9 +18,11 @@ module GoogleCloud
subnets = fetch_and_update_cache!
- Gitlab::AppJsonLogger.info(class: self.class.name,
- message: 'Successfully retrieved Google IP list',
- subnet_count: subnets.count)
+ Gitlab::AppJsonLogger.info(
+ class: self.class.name,
+ message: 'Successfully retrieved Google IP list',
+ subnet_count: subnets.count
+ )
success({ subnets: subnets })
rescue IpListNotRetrievedError => err
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 6c229dca58c..f546bd5951a 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -184,11 +184,14 @@ class IssuableBaseService < ::BaseContainerService
end
def process_assignee_ids(attributes, existing_assignee_ids: nil, extra_assignee_ids: [])
- process = Issuable::ProcessAssignees.new(assignee_ids: attributes.delete(:assignee_ids),
- add_assignee_ids: attributes.delete(:add_assignee_ids),
- remove_assignee_ids: attributes.delete(:remove_assignee_ids),
- existing_assignee_ids: existing_assignee_ids,
- extra_assignee_ids: extra_assignee_ids)
+ process = Issuable::ProcessAssignees.new(
+ assignee_ids: attributes.delete(:assignee_ids),
+ add_assignee_ids: attributes.delete(:add_assignee_ids),
+ remove_assignee_ids: attributes.delete(:remove_assignee_ids),
+ existing_assignee_ids: existing_assignee_ids,
+ extra_assignee_ids: extra_assignee_ids
+ )
+
process.execute
end
@@ -373,9 +376,11 @@ class IssuableBaseService < ::BaseContainerService
filter_params(issuable)
if issuable.changed? || params.present?
- issuable.assign_attributes(params.merge(updated_by: current_user,
- last_edited_at: Time.current,
- last_edited_by: current_user))
+ issuable.assign_attributes(params.merge(
+ updated_by: current_user,
+ last_edited_at: Time.current,
+ last_edited_by: current_user
+ ))
before_update(issuable, skip_spam_check: true)
@@ -404,10 +409,13 @@ class IssuableBaseService < ::BaseContainerService
update_task_params = params.delete(:update_task)
return unless update_task_params
- tasklist_toggler = TaskListToggleService.new(issuable.description, issuable.description_html,
- line_source: update_task_params[:line_source],
- line_number: update_task_params[:line_number].to_i,
- toggle_as_checked: update_task_params[:checked])
+ tasklist_toggler = TaskListToggleService.new(
+ issuable.description,
+ issuable.description_html,
+ line_source: update_task_params[:line_source],
+ line_number: update_task_params[:line_number].to_i,
+ toggle_as_checked: update_task_params[:checked]
+ )
unless tasklist_toggler.execute
# if we make it here, the data is much newer than we thought it was - fail fast
diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb
index 28f4c6d5904..ef43e707a21 100644
--- a/app/services/issues/close_service.rb
+++ b/app/services/issues/close_service.rb
@@ -6,10 +6,12 @@ module Issues
def execute(issue, commit: nil, notifications: true, system_note: true, skip_authorization: false)
return issue unless can_close?(issue, skip_authorization: skip_authorization)
- close_issue(issue,
- closed_via: commit,
- notifications: notifications,
- system_note: system_note)
+ close_issue(
+ issue,
+ closed_via: commit,
+ notifications: notifications,
+ system_note: system_note
+ )
end
# Closes the supplied issue without checking if the user is authorized to
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index e1ddfe47439..c828c156d50 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -7,7 +7,7 @@ module Issues
include ::Services::ReturnServiceResponses
rate_limit key: :issues_create,
- opts: { scope: [:project, :current_user, :external_author] }
+ opts: { scope: [:project, :current_user, :external_author] }
def initialize(container:, current_user: nil, params: {}, build_service: nil, perform_spam_check: true)
@extra_params = params.delete(:extra_params) || {}
@@ -90,9 +90,12 @@ module Issues
def resolve_discussions_with_issue(issue)
return if discussions_to_resolve.empty?
- Discussions::ResolveService.new(project, current_user,
- one_or_more_discussions: discussions_to_resolve,
- follow_up_issue: issue).execute
+ Discussions::ResolveService.new(
+ project,
+ current_user,
+ one_or_more_discussions: discussions_to_resolve,
+ follow_up_issue: issue
+ ).execute
end
private
diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb
index e26e3d0835b..c3ddf7b6709 100644
--- a/app/services/issues/move_service.rb
+++ b/app/services/issues/move_service.rb
@@ -141,15 +141,23 @@ module Issues
end
def add_note_from
- SystemNoteService.noteable_moved(new_entity, target_project,
- original_entity, current_user,
- direction: :from)
+ SystemNoteService.noteable_moved(
+ new_entity,
+ target_project,
+ original_entity,
+ current_user,
+ direction: :from
+ )
end
def add_note_to
- SystemNoteService.noteable_moved(original_entity, old_project,
- new_entity, current_user,
- direction: :to)
+ SystemNoteService.noteable_moved(
+ original_entity,
+ old_project,
+ new_entity,
+ current_user,
+ direction: :to
+ )
end
end
end
diff --git a/app/services/metrics/sample_metrics_service.rb b/app/services/metrics/sample_metrics_service.rb
deleted file mode 100644
index 9bf32b295e2..00000000000
--- a/app/services/metrics/sample_metrics_service.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-module Metrics
- class SampleMetricsService
- DIRECTORY = "sample_metrics"
-
- attr_reader :identifier, :range_minutes
-
- def initialize(identifier, range_start:, range_end:)
- @identifier = identifier
- @range_minutes = convert_range_minutes(range_start, range_end)
- end
-
- def query
- return unless identifier && File.exist?(file_location)
-
- query_interval
- end
-
- private
-
- def file_location
- sanitized_string = identifier.gsub(/[^0-9A-Za-z_]/, '')
- File.join(Rails.root, DIRECTORY, "#{sanitized_string}.yml")
- end
-
- def query_interval
- result = YAML.load_file(File.expand_path(file_location, __dir__))
- result[range_minutes]
- end
-
- def convert_range_minutes(range_start, range_end)
- ((range_end.to_time - range_start.to_time) / 1.minute).to_i
- end
- end
-end
diff --git a/app/views/groups/dependency_proxies/show.html.haml b/app/views/groups/dependency_proxies/show.html.haml
index 8416cb81c95..02a024ed3b5 100644
--- a/app/views/groups/dependency_proxies/show.html.haml
+++ b/app/views/groups/dependency_proxies/show.html.haml
@@ -1,6 +1,7 @@
- page_title _("Dependency Proxy")
#js-dependency-proxy{ data: { group_path: @group.full_path,
+ endpoint: group_dependency_proxy_path(@group),
no_manifests_illustration: image_path('illustrations/docker-empty-state.svg'),
group_id: @group.id,
settings_path: group_settings_packages_and_registries_path(@group),
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index 681d4e087f3..51010ec326e 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -140,7 +140,7 @@
%p.gl-text-secondary
= s_('Preferences|Configure how dates and times display for you.')
= succeed '.' do
- = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'time-preferences'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'show-exact-times-instead-of-relative-times'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.gitlab_ui_checkbox_component :time_display_relative,
s_('Preferences|Use relative times'),
diff --git a/config/feature_flags/development/action_cable_notes.yml b/config/feature_flags/development/custom_roles_in_members_page.yml
index d41b1e444eb..cb6bea5ca42 100644
--- a/config/feature_flags/development/action_cable_notes.yml
+++ b/config/feature_flags/development/custom_roles_in_members_page.yml
@@ -1,8 +1,8 @@
---
-name: action_cable_notes
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/127964
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/412823
+name: custom_roles_in_members_page
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128491
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/422897
milestone: '16.3'
type: development
-group: group::project management
+group: group::authentication and authorization
default_enabled: false
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 199c9c99b74..a4b513fc33e 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -320,8 +320,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false
get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#prometheus_proxy', as: :prometheus_api
-
- get '/sample_metrics', to: 'environments/sample_metrics#query'
end
collection do
diff --git a/data/deprecations/15-6-deprecate-runner-reg-token-helm.yml b/data/deprecations/15-6-deprecate-runner-reg-token-helm.yml
index 0882d8ac894..42cf2a08ba7 100644
--- a/data/deprecations/15-6-deprecate-runner-reg-token-helm.yml
+++ b/data/deprecations/15-6-deprecate-runner-reg-token-helm.yml
@@ -10,7 +10,7 @@
The [`runnerRegistrationToken`](https://docs.gitlab.com/runner/install/kubernetes.html#required-configuration) parameter to use the GitLab Helm Chart to install a runner on Kubernetes is deprecated.
We plan to implement a new method to bind runners to a GitLab instance leveraging `runnerToken`
- as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/).
+ as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/ci/runners/new_creation_workflow.html).
The work is planned in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7633).
From GitLab 17.0 and later, the methods to register runners introduced by the new GitLab Runner token architecture will be the only supported methods.
diff --git a/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml b/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
index d617b5de531..30606b830c7 100644
--- a/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
+++ b/data/deprecations/15-7-deprecate-api-v4-runner-registration-token-reset-endpoints.yml
@@ -16,7 +16,7 @@
- `POST /groups/:id/runners/reset_registration_token`
We plan to implement a new method to bind runners to a GitLab instance
- as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/).
+ as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/ci/runners/new_creation_workflow.html).
The work is planned in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7633).
This new architecture introduces a new method for registering runners and will eliminate the legacy
[runner registration token](https://docs.gitlab.com/ee/security/token_overview.html#runner-registration-tokens).
diff --git a/db/docs/packages_packages.yml b/db/docs/packages_packages.yml
index d2e08350ab7..b7abcf73ab2 100644
--- a/db/docs/packages_packages.yml
+++ b/db/docs/packages_packages.yml
@@ -1,6 +1,7 @@
---
table_name: packages_packages
classes:
+- Packages::MlModel::Package
- Packages::Package
feature_categories:
- package_registry
diff --git a/doc/ci/runners/new_creation_workflow.md b/doc/ci/runners/new_creation_workflow.md
index 55ff5165ff7..02bedccef6b 100644
--- a/doc/ci/runners/new_creation_workflow.md
+++ b/doc/ci/runners/new_creation_workflow.md
@@ -21,8 +21,6 @@ For information about the current development status of the new workflow, see [e
For information about the technical design and reasons for the new architecture, see [Next GitLab Runner Token Architecture](../../architecture/blueprints/runner_tokens/index.md).
-## Feedback
-
If you experience problems or have concerns about the new runner registration workflow,
or if the following information is not sufficient,
you can let us know in the [feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/387993).
@@ -138,10 +136,8 @@ Existing runners will continue to work as usual. This change only affects regist
## Creating runners programmatically
-A new [POST /user/runners REST API](../../api/users.md#create-a-runner) was introduced in
-GitLab 15.11, which allows a runner to be created in the context of an authenticated user. This should only be used in
-scenarios where the runner configuration is dynamic, or not reusable. If the runner configuration is static, it is
-preferable to reuse the authentication token of an existing runner.
+In GitLab 15.11 and later, you can use the [POST /user/runners REST API](../../api/users.md#create-a-runner) to create a runner as an authenticated user. This should only be used if the runner configuration is dynamic or not reusable. If the runner configuration is static, you should
+reuse the authentication token of an existing runner. For more information, see [How to automate the creation of GitLab Runners](https://about.gitlab.com/blog/2023/07/06/how-to-automate-creation-of-runners/).
The following snippet shows how a group runner could be created and registered with a
[Group Access Token](../../user/group/settings/group_access_tokens.md) using the new creation flow.
@@ -212,3 +208,7 @@ data:
runner-registration-token: "" # need to leave as an empty string for compatibility reasons
runner-token: "REDACTED"
```
+
+NOTE:
+If your secret management solution doesn't allow you to set an empty string for `runner-registration-token`,
+you can set it to any string - it will be ignored when `runner-token` is present.
diff --git a/doc/security/token_overview.md b/doc/security/token_overview.md
index cb01c7d5160..e081ac61fb5 100644
--- a/doc/security/token_overview.md
+++ b/doc/security/token_overview.md
@@ -90,8 +90,8 @@ Project maintainers and owners can add or enable a deploy key for a project repo
WARNING:
The ability to pass a runner registration token has been [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/380872) and is
-planned for removal in 17.0, along with support for certain configuration arguments. This change is a breaking change. GitLab plans to introduce a new
-[GitLab Runner token architecture](../architecture/blueprints/runner_tokens/index.md), which introduces
+planned for removal in 17.0, along with support for certain configuration arguments. This change is a breaking change. GitLab has implemented a new
+[GitLab Runner token architecture](../ci/runners/new_creation_workflow.md), which introduces
a new method for registering runners and eliminates the
runner registration token.
diff --git a/doc/tutorials/build_application.md b/doc/tutorials/build_application.md
index c22cba7e0e8..5a1a23f2269 100644
--- a/doc/tutorials/build_application.md
+++ b/doc/tutorials/build_application.md
@@ -30,6 +30,7 @@ Set up runners to run jobs in a pipeline.
|-------|-------------|--------------------|
| [Create, register, and run your own project runner](create_register_first_runner/index.md) | Learn the basics of how to create and register a project runner that runs jobs for your project. | **{star}** |
| [Configure GitLab Runner to use the Google Kubernetes Engine](configure_gitlab_runner_to_use_gke/index.md) | Learn how to configure GitLab Runner to use the GKE to run jobs. | |
+| [Automate the creation of runners](https://about.gitlab.com/blog/2023/07/06/how-to-automate-creation-of-runners/) | Learn how to automate runner creation as an authenticated user to optimize your runner fleet. | |
## Publish a static website
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index 22934e62d03..29074a1e3d9 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -695,7 +695,7 @@ The deprecated endpoints are:
- `POST /groups/:id/runners/reset_registration_token`
We plan to implement a new method to bind runners to a GitLab instance
-as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/).
+as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/ci/runners/new_creation_workflow.html).
The work is planned in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7633).
This new architecture introduces a new method for registering runners and will eliminate the legacy
[runner registration token](https://docs.gitlab.com/ee/security/token_overview.html#runner-registration-tokens).
@@ -841,7 +841,7 @@ removed in 17.0.
The [`runnerRegistrationToken`](https://docs.gitlab.com/runner/install/kubernetes.html#required-configuration) parameter to use the GitLab Helm Chart to install a runner on Kubernetes is deprecated.
We plan to implement a new method to bind runners to a GitLab instance leveraging `runnerToken`
-as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/architecture/blueprints/runner_tokens/).
+as part of the new [GitLab Runner token architecture](https://docs.gitlab.com/ee/ci/runners/new_creation_workflow.html).
The work is planned in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/7633).
From GitLab 17.0 and later, the methods to register runners introduced by the new GitLab Runner token architecture will be the only supported methods.
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index b945f598759..6cbf4132621 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -219,48 +219,54 @@ To adjust the default tab width:
## Localization
-### Language
+Change localization settings such as your language, calendar start day, and time preferences.
-Select your preferred language from a list of supported languages.
+### Change your display language on the GitLab UI
-*This feature is experimental and translations are not complete yet.*
+GitLab supports multiple languages on the UI. To help improve translations or request support for an unlisted language, view [Translating GitLab](../../development/i18n/translation.md).
-### First day of the week
+To choose a language for the GitLab UI:
-The first day of the week can be customized for calendar views and date pickers.
+1. On the left sidebar, select your avatar.
+1. Select **Preferences**.
+1. Go to the **Localization** section.
+1. Under **Language**, select an option.
+1. Select **Save changes**.
-You can choose one of the following options as the first day of the week:
+You might need to refresh your page to view the updated language.
-- Saturday
-- Sunday
-- Monday
+### Customize your contribution calendar start day
-If you select **System Default**, the first day of the week is set to the
-[instance default](../../administration/settings/index.md#change-the-default-first-day-of-the-week).
+Choose which day of the week the contribution calendar starts with. The contribution calendar shows project contributions over the past year. You can view this calendar on each user profile. To access your user profile:
-## Time preferences
+- On the left sidebar, select your avatar > select your name or username.
-### Use relative times
+To change your contribution calendar start day:
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65570) in GitLab 14.1.
+1. On the left sidebar, select your avatar.
+1. Select **Preferences**.
+1. Go to the **Localization** section.
+1. Under **First day of the week**, select an option.
+1. Select **Save changes**.
-You can select your preferred time format for the GitLab user interface:
+After you change your calendar start day, refresh your user profile page.
-- Relative times, for example, `30 minutes ago`.
-- Absolute times, for example, `May 18, 2021, 3:57 PM`.
+### Show exact times instead of relative times
-The times are formatted depending on your chosen language and browser locale.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65570) in GitLab 14.1.
-To set your time preference:
+Customize the format used to display times of activities on your group and project overview pages and user profiles. You can display times in a:
-1. On the **Preferences** page, go to **Time preferences**.
-1. Select the **Use relative times** checkbox to use relative times,
- or clear the checkbox to use absolute times.
-1. Select **Save changes**.
+- Relative format, for example `30 minutes ago`.
+- Absolute format, for example `September 3, 2022, 3:57 PM`.
-NOTE:
-This feature is experimental, and choosing absolute times might break certain layouts.
-Open an issue if you notice that using absolute times breaks a layout.
+To use relative times on the GitLab UI:
+
+1. On the left sidebar, select your avatar.
+1. Select **Preferences**.
+1. Go to the **Time preferences** section.
+1. Clear the **Use relative times** checkbox.
+1. Select **Save changes**.
## User identities in CI job JSON web tokens
diff --git a/lib/tasks/gitlab/generate_sample_prometheus_data.rake b/lib/tasks/gitlab/generate_sample_prometheus_data.rake
deleted file mode 100644
index 4cd75af9d00..00000000000
--- a/lib/tasks/gitlab/generate_sample_prometheus_data.rake
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-namespace :gitlab do
- desc "GitLab | Generate Sample Prometheus Data"
- task :generate_sample_prometheus_data, [:environment_id] => :gitlab_environment do |_, args|
- environment = Environment.find(args[:environment_id])
- metrics = PrometheusMetric.where(project_id: [environment.project.id, nil])
- query_variables = Gitlab::Prometheus::QueryVariables.call(environment)
-
- sample_metrics_directory_name = Metrics::SampleMetricsService::DIRECTORY
- FileUtils.mkdir_p(sample_metrics_directory_name)
-
- sample_metrics_intervals = [30.minutes, 180.minutes, 8.hours, 24.hours, 72.hours, 7.days]
-
- metrics.each do |metric|
- query = metric.query % query_variables
-
- next unless metric.identifier
-
- result = sample_metrics_intervals.each_with_object({}) do |interval, memo|
- memo[interval.to_i / 60] = environment.prometheus_adapter.prometheus_client.query_range(query, start: interval.ago)
- end
-
- File.write("#{sample_metrics_directory_name}/#{metric.identifier}.yml", result.to_yaml)
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e519f58c3a6..1149ab86453 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -44849,9 +44849,6 @@ msgstr ""
msgid "Something went wrong while fetching details"
msgstr ""
-msgid "Something went wrong while fetching latest comments."
-msgstr ""
-
msgid "Something went wrong while fetching projects"
msgstr ""
diff --git a/qa/Gemfile b/qa/Gemfile
index 0dfcb09c8e0..4698f3a0db0 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -24,7 +24,7 @@ gem 'rotp', '~> 6.2.2'
gem 'parallel', '~> 1.23'
gem 'rainbow', '~> 3.1.1'
gem 'rspec-parameterized', '~> 1.0.0'
-gem 'octokit', '~> 7.0.0'
+gem 'octokit', '~> 7.1.0'
gem "faraday-retry", "~> 2.2"
gem 'zeitwerk', '~> 2.6', '>= 2.6.8'
gem 'influxdb-client', '~> 2.9'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index c02f82402bc..8a8bff90499 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -215,7 +215,7 @@ GEM
nokogiri (1.15.4)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
- octokit (7.0.0)
+ octokit (7.1.0)
faraday (>= 1, < 3)
sawyer (~> 0.9)
oj (3.13.23)
@@ -357,7 +357,7 @@ DEPENDENCIES
influxdb-client (~> 2.9)
knapsack (~> 4.0)
nokogiri (~> 1.15, >= 1.15.4)
- octokit (~> 7.0.0)
+ octokit (~> 7.1.0)
parallel (~> 1.23)
parallel_tests (~> 4.2, >= 4.2.1)
pry-byebug (~> 3.10.1)
@@ -377,4 +377,4 @@ DEPENDENCIES
zeitwerk (~> 2.6, >= 2.6.8)
BUNDLED WITH
- 2.4.18
+ 2.4.19
diff --git a/spec/controllers/concerns/preferred_language_switcher_spec.rb b/spec/controllers/concerns/preferred_language_switcher_spec.rb
index 40d6ac10c37..1cec62a5f34 100644
--- a/spec/controllers/concerns/preferred_language_switcher_spec.rb
+++ b/spec/controllers/concerns/preferred_language_switcher_spec.rb
@@ -14,13 +14,52 @@ RSpec.describe PreferredLanguageSwitcher, type: :controller do
end
context 'when first visit' do
+ let(:glm_source) { 'about.gitlab.com' }
+ let(:accept_language_header) { nil }
+
before do
- get :new
+ request.env['HTTP_ACCEPT_LANGUAGE'] = accept_language_header.dup
+
+ get :new, params: { glm_source: glm_source }
end
it 'sets preferred_language to default' do
expect(cookies[:preferred_language]).to eq Gitlab::CurrentSettings.default_preferred_language
end
+
+ context 'when language param is valid' do
+ let(:glm_source) { 'about.gitlab.com/fr-fr/' }
+
+ it 'sets preferred_language accordingly' do
+ expect(cookies[:preferred_language]).to eq 'fr'
+ end
+
+ context 'when language param is invalid' do
+ let(:glm_source) { 'about.gitlab.com/ko-ko/' }
+
+ it 'sets preferred_language to default' do
+ expect(cookies[:preferred_language]).to eq Gitlab::CurrentSettings.default_preferred_language
+ end
+ end
+ end
+
+ context 'when browser preferred language is not english' do
+ context 'with selectable language' do
+ let(:accept_language_header) { 'zh-CN,zh;q=0.8,zh-TW;q=0.7' }
+
+ it 'sets preferred_language accordingly' do
+ expect(cookies[:preferred_language]).to eq 'zh_CN'
+ end
+ end
+
+ context 'with unselectable language' do
+ let(:accept_language_header) { 'nl-NL;q=0.8' }
+
+ it 'sets preferred_language to default' do
+ expect(cookies[:preferred_language]).to eq Gitlab::CurrentSettings.default_preferred_language
+ end
+ end
+ end
end
context 'when preferred language in cookies has been modified' do
diff --git a/spec/controllers/projects/environments/sample_metrics_controller_spec.rb b/spec/controllers/projects/environments/sample_metrics_controller_spec.rb
deleted file mode 100644
index b266c569edd..00000000000
--- a/spec/controllers/projects/environments/sample_metrics_controller_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Projects::Environments::SampleMetricsController do
- include StubENV
-
- let_it_be(:project) { create(:project) }
- let_it_be(:environment) { create(:environment, project: project) }
- let_it_be(:user) { create(:user) }
-
- before do
- project.add_reporter(user)
- sign_in(user)
- end
-
- describe 'GET #query' do
- context 'when the file is not found' do
- before do
- get :query, params: environment_params
- end
-
- it 'returns a 404' do
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
-
- context 'when the sample data is found' do
- before do
- allow_next_instance_of(Metrics::SampleMetricsService) do |service|
- allow(service).to receive(:query).and_return([])
- end
- get :query, params: environment_params
- end
-
- it 'returns JSON with a message and a 200 status code' do
- expect(json_response.keys).to contain_exactly('status', 'data')
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-
- private
-
- def environment_params(params = {})
- {
- id: environment.id.to_s,
- namespace_id: project.namespace.full_path,
- project_id: project.path,
- identifier: 'sample_metric_query_result',
- start: '2019-12-02T23:31:45.000Z',
- end: '2019-12-03T00:01:45.000Z'
- }.merge(params)
- end
-end
diff --git a/spec/factories/metrics/users_starred_dashboards.rb b/spec/factories/metrics/users_starred_dashboards.rb
deleted file mode 100644
index 06fe7735e9a..00000000000
--- a/spec/factories/metrics/users_starred_dashboards.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-# frozen_string_literal: true
-
-FactoryBot.define do
- factory :metrics_users_starred_dashboard, class: '::Metrics::UsersStarredDashboard' do
- dashboard_path { "custom_dashboard.yml" }
- user
- project
- end
-end
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index a390dca6822..293b6c53eb5 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -19,22 +19,6 @@ RSpec.describe 'Issue notes polling', :js, feature_category: :team_planning do
expect(page).to have_selector("#note_#{note.id}", text: 'Looks good!')
end
-
- context 'when action_cable_notes is disabled' do
- before do
- stub_feature_flags(action_cable_notes: false)
- end
-
- it 'displays the new comment' do
- visit project_issue_path(project, issue)
- close_rich_text_promo_popover_if_present
-
- note = create(:note, noteable: issue, project: project, note: 'Looks good!')
- wait_for_requests
-
- expect(page).to have_selector("#note_#{note.id}", text: 'Looks good!')
- end
- end
end
describe 'updates' do
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 18473a5e70b..b8a5be44241 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -288,4 +288,39 @@ RSpec.describe GroupMembersFinder, '#execute', feature_category: :groups_and_pro
end
end
end
+
+ context 'filter by non-invite' do
+ let_it_be(:member) { group.add_maintainer(user1) }
+ let_it_be(:invited_member) do
+ create(:group_member, :invited, { user: user2, group: group })
+ end
+
+ context 'params is not passed in' do
+ subject { described_class.new(group, user1).execute }
+
+ it 'does not filter members by invite' do
+ expect(subject).to match_array([member, invited_member])
+ end
+ end
+
+ context 'params is passed in' do
+ subject { described_class.new(group, user1, params: { non_invite: non_invite_param }).execute }
+
+ context 'filtering is set to false' do
+ let(:non_invite_param) { false }
+
+ it 'does not filter members by invite' do
+ expect(subject).to match_array([member, invited_member])
+ end
+ end
+
+ context 'filtering is set to true' do
+ let(:non_invite_param) { true }
+
+ it 'filters members by invite' do
+ expect(subject).to match_array([member])
+ end
+ end
+ end
+ end
end
diff --git a/spec/frontend/notes/components/comment_form_spec.js b/spec/frontend/notes/components/comment_form_spec.js
index 0728646246d..9b1678c0a8a 100644
--- a/spec/frontend/notes/components/comment_form_spec.js
+++ b/spec/frontend/notes/components/comment_form_spec.js
@@ -153,21 +153,18 @@ describe('issue_comment_form component', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
- jest.spyOn(wrapper.vm, 'stopPolling');
findCloseReopenButton().trigger('click');
expect(wrapper.vm.isSubmitting).toBe(true);
expect(wrapper.vm.note).toBe('');
expect(wrapper.vm.saveNote).toHaveBeenCalled();
- expect(wrapper.vm.stopPolling).toHaveBeenCalled();
});
it('tracks event', () => {
mountComponent({ mountFunction: mount, initialData: { note: 'hello world' } });
jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
- jest.spyOn(wrapper.vm, 'stopPolling');
findCloseReopenButton().trigger('click');
@@ -302,7 +299,6 @@ describe('issue_comment_form component', () => {
const saveNotePromise = Promise.resolve();
jest.spyOn(wrapper.vm, 'saveNote').mockReturnValue(saveNotePromise);
- jest.spyOn(wrapper.vm, 'stopPolling');
const actionButton = findCloseReopenButton();
@@ -351,7 +347,6 @@ describe('issue_comment_form component', () => {
it('should make textarea disabled while requesting', async () => {
mountComponent({ mountFunction: mount });
- jest.spyOn(wrapper.vm, 'stopPolling');
jest.spyOn(wrapper.vm, 'saveNote').mockResolvedValue();
findMarkdownEditor().vm.$emit('input', 'hello world');
diff --git a/spec/frontend/notes/components/notes_app_spec.js b/spec/frontend/notes/components/notes_app_spec.js
index caf47febedd..d49ab0d71db 100644
--- a/spec/frontend/notes/components/notes_app_spec.js
+++ b/spec/frontend/notes/components/notes_app_spec.js
@@ -288,7 +288,6 @@ describe('note_app', () => {
wrapper.vm.$store.hotUpdate({
actions: {
toggleAward: toggleAwardAction,
- stopPolling() {},
},
});
diff --git a/spec/frontend/notes/stores/actions_spec.js b/spec/frontend/notes/stores/actions_spec.js
index 0205f606297..104c297b44e 100644
--- a/spec/frontend/notes/stores/actions_spec.js
+++ b/spec/frontend/notes/stores/actions_spec.js
@@ -8,11 +8,7 @@ import { createAlert } from '~/alert';
import toast from '~/vue_shared/plugins/global_toast';
import { EVENT_ISSUABLE_VUE_APP_CHANGE } from '~/issuable/constants';
import axios from '~/lib/utils/axios_utils';
-import {
- HTTP_STATUS_INTERNAL_SERVER_ERROR,
- HTTP_STATUS_OK,
- HTTP_STATUS_SERVICE_UNAVAILABLE,
-} from '~/lib/utils/http_status';
+import { HTTP_STATUS_OK, HTTP_STATUS_SERVICE_UNAVAILABLE } from '~/lib/utils/http_status';
import * as notesConstants from '~/notes/constants';
import createStore from '~/notes/stores';
import * as actions from '~/notes/stores/actions';
@@ -24,7 +20,6 @@ import updateMergeRequestLockMutation from '~/sidebar/queries/update_merge_reque
import promoteTimelineEvent from '~/notes/graphql/promote_timeline_event.mutation.graphql';
import mrWidgetEventHub from '~/vue_merge_request_widget/event_hub';
import notesEventHub from '~/notes/event_hub';
-import waitForPromises from 'helpers/wait_for_promises';
import { resetStore } from '../helpers';
import {
discussionMock,
@@ -262,13 +257,7 @@ describe('Actions Notes Store', () => {
});
describe('initPolling', () => {
- afterEach(() => {
- gon.features = {};
- });
-
it('creates the Action Cable subscription', () => {
- gon.features = { actionCableNotes: true };
-
store.dispatch('setNotesData', notesDataMock);
store.dispatch('initPolling');
@@ -290,8 +279,6 @@ describe('Actions Notes Store', () => {
const response = { notes: [], last_fetched_at: '123456' };
const successMock = () =>
axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_OK, response);
- const failureMock = () =>
- axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
beforeEach(() => {
return store.dispatch('setNotesData', notesDataMock);
@@ -304,153 +291,6 @@ describe('Actions Notes Store', () => {
expect(store.state.lastFetchedAt).toBe('123456');
});
-
- it('shows an alert when fetching fails', async () => {
- failureMock();
-
- await store.dispatch('fetchUpdatedNotes');
-
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
- });
-
- describe('poll', () => {
- const pollInterval = 6000;
- const pollResponse = { notes: [], last_fetched_at: '123456' };
- const pollHeaders = { 'poll-interval': `${pollInterval}` };
- const successMock = () =>
- axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_OK, pollResponse, pollHeaders);
- const failureMock = () =>
- axiosMock.onGet(notesDataMock.notesPath).reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
- const advanceAndRAF = (time) => {
- if (time) {
- jest.advanceTimersByTime(time);
- }
-
- return waitForPromises();
- };
- const advanceXMoreIntervals = (number) => {
- const timeoutLength = pollInterval * number;
-
- return advanceAndRAF(timeoutLength);
- };
- const startPolling = async () => {
- await store.dispatch('poll');
- await advanceAndRAF(2);
- };
- const cleanUp = () => {
- jest.clearAllTimers();
-
- return store.dispatch('stopPolling');
- };
-
- beforeEach(() => {
- return store.dispatch('setNotesData', notesDataMock);
- });
-
- afterEach(() => {
- return cleanUp();
- });
-
- it('calls service with last fetched state', async () => {
- successMock();
-
- await startPolling();
-
- expect(store.state.lastFetchedAt).toBe('123456');
-
- await advanceXMoreIntervals(1);
-
- expect(axiosMock.history.get).toHaveLength(2);
- expect(axiosMock.history.get[1].headers).toMatchObject({
- 'X-Last-Fetched-At': '123456',
- });
- });
-
- describe('polling side effects', () => {
- it('retries twice', async () => {
- failureMock();
-
- await startPolling();
-
- // This is the first request, not a retry
- expect(axiosMock.history.get).toHaveLength(1);
-
- await advanceXMoreIntervals(1);
-
- // Retry #1
- expect(axiosMock.history.get).toHaveLength(2);
-
- await advanceXMoreIntervals(1);
-
- // Retry #2
- expect(axiosMock.history.get).toHaveLength(3);
-
- await advanceXMoreIntervals(10);
-
- // There are no more retries
- expect(axiosMock.history.get).toHaveLength(3);
- });
-
- it('shows the error display on the second failure', async () => {
- failureMock();
-
- await startPolling();
-
- expect(axiosMock.history.get).toHaveLength(1);
- expect(createAlert).not.toHaveBeenCalled();
-
- await advanceXMoreIntervals(1);
-
- expect(axiosMock.history.get).toHaveLength(2);
- expect(createAlert).toHaveBeenCalled();
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
-
- it('resets the failure counter on success', async () => {
- // We can't get access to the actual counter in the polling closure.
- // So we can infer that it's reset by ensuring that the error is only
- // shown when we cause two failures in a row - no successes between
-
- axiosMock
- .onGet(notesDataMock.notesPath)
- .replyOnce(HTTP_STATUS_INTERNAL_SERVER_ERROR) // cause one error
- .onGet(notesDataMock.notesPath)
- .replyOnce(HTTP_STATUS_OK, pollResponse, pollHeaders) // then a success
- .onGet(notesDataMock.notesPath)
- .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); // and then more errors
-
- await startPolling(); // Failure #1
- await advanceXMoreIntervals(1); // Success #1
- await advanceXMoreIntervals(1); // Failure #2
-
- // That was the first failure AFTER a success, so we should NOT see the error displayed
- expect(createAlert).not.toHaveBeenCalled();
-
- // Now we'll allow another failure
- await advanceXMoreIntervals(1); // Failure #3
-
- // Since this is the second failure in a row, the error should happen
- expect(createAlert).toHaveBeenCalledTimes(1);
- });
-
- it('hides the error display if it exists on success', async () => {
- failureMock();
-
- await startPolling();
- await advanceXMoreIntervals(2);
-
- // After two errors, the error should be displayed
- expect(createAlert).toHaveBeenCalledTimes(1);
-
- axiosMock.reset();
- successMock();
-
- await advanceXMoreIntervals(1);
-
- expect(mockAlertDismiss).toHaveBeenCalledTimes(1);
- });
- });
});
describe('setNotesFetchedState', () => {
@@ -996,11 +836,7 @@ describe('Actions Notes Store', () => {
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([
- ['stopPolling'],
- ['resolveDiscussion', { discussionId }],
- ['restartPolling'],
- ]);
+ expect(dispatch.mock.calls).toEqual([['resolveDiscussion', { discussionId }]]);
expect(createAlert).not.toHaveBeenCalled();
});
});
@@ -1015,7 +851,6 @@ describe('Actions Notes Store', () => {
[mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(createAlert).toHaveBeenCalledWith({
message: TEST_ERROR_MESSAGE,
parent: flashContainer,
@@ -1033,7 +868,6 @@ describe('Actions Notes Store', () => {
[mutationTypes.SET_RESOLVING_DISCUSSION, true],
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(createAlert).toHaveBeenCalledWith({
message: 'Something went wrong while applying the suggestion. Please try again.',
parent: flashContainer,
@@ -1081,10 +915,8 @@ describe('Actions Notes Store', () => {
]);
expect(dispatch.mock.calls).toEqual([
- ['stopPolling'],
['resolveDiscussion', { discussionId: discussionIds[0] }],
['resolveDiscussion', { discussionId: discussionIds[1] }],
- ['restartPolling'],
]);
expect(createAlert).not.toHaveBeenCalled();
@@ -1104,7 +936,6 @@ describe('Actions Notes Store', () => {
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(createAlert).toHaveBeenCalledWith({
message: TEST_ERROR_MESSAGE,
parent: flashContainer,
@@ -1125,7 +956,6 @@ describe('Actions Notes Store', () => {
[mutationTypes.SET_RESOLVING_DISCUSSION, false],
]);
- expect(dispatch.mock.calls).toEqual([['stopPolling'], ['restartPolling']]);
expect(createAlert).toHaveBeenCalledWith({
message:
'Something went wrong while applying the batch of suggestions. Please try again.',
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
index f590cff0312..4fd7957674d 100644
--- a/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
+++ b/spec/frontend/packages_and_registries/dependency_proxy/app_spec.js
@@ -18,12 +18,13 @@ import waitForPromises from 'helpers/wait_for_promises';
import { GRAPHQL_PAGE_SIZE } from '~/packages_and_registries/dependency_proxy/constants';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_ACCEPTED } from '~/lib/utils/http_status';
-
+import setWindowLocation from 'helpers/set_window_location_helper';
+import { TEST_HOST } from 'helpers/test_constants';
import DependencyProxyApp from '~/packages_and_registries/dependency_proxy/app.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import ManifestsList from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
-
+import createRouter from '~/packages_and_registries/dependency_proxy/router';
import getDependencyProxyDetailsQuery from '~/packages_and_registries/dependency_proxy/graphql/queries/get_dependency_proxy_details.query.graphql';
import { proxyDetailsQuery, proxyData, pagination, proxyManifests } from './mock_data';
@@ -37,6 +38,7 @@ Vue.use(VueApollo);
describe('DependencyProxyApp', () => {
let wrapper;
+ let router;
let apolloProvider;
let resolver;
let mock;
@@ -53,15 +55,16 @@ describe('DependencyProxyApp', () => {
const requestHandlers = [[getDependencyProxyDetailsQuery, resolver]];
apolloProvider = createMockApollo(requestHandlers);
+ router = createRouter('/');
wrapper = shallowMountExtended(DependencyProxyApp, {
apolloProvider,
provide,
+ router,
stubs: {
GlAlert,
GlDropdown,
GlDropdownItem,
- GlFormInputGroup,
GlFormGroup,
GlModal,
GlSprintf,
@@ -94,6 +97,7 @@ describe('DependencyProxyApp', () => {
mock = new MockAdapter(axios);
mock.onDelete(expectedUrl).reply(HTTP_STATUS_ACCEPTED, {});
+ setWindowLocation(TEST_HOST);
});
afterEach(() => {
@@ -123,6 +127,13 @@ describe('DependencyProxyApp', () => {
return waitForPromises();
});
+ it('resolver is called with right arguments', () => {
+ expect(resolver).toHaveBeenCalledWith({
+ first: GRAPHQL_PAGE_SIZE,
+ fullPath: provideDefaults.groupPath,
+ });
+ });
+
it('renders a form group with a label', () => {
expect(findFormGroup().attributes('label')).toBe(
DependencyProxyApp.i18n.proxyImagePrefix,
@@ -225,6 +236,7 @@ describe('DependencyProxyApp', () => {
fullPath: provideDefaults.groupPath,
last: GRAPHQL_PAGE_SIZE,
});
+ expect(window.location.search).toBe(`?before=${pagination().startCursor}`);
});
});
@@ -252,6 +264,7 @@ describe('DependencyProxyApp', () => {
first: GRAPHQL_PAGE_SIZE,
fullPath: provideDefaults.groupPath,
});
+ expect(window.location.search).toBe(`?after=${pagination().endCursor}`);
});
});
@@ -315,6 +328,48 @@ describe('DependencyProxyApp', () => {
});
});
});
+
+ describe('pagination params', () => {
+ it('after is set from the url params', async () => {
+ setWindowLocation('?after=1234');
+ createComponent();
+ await waitForPromises();
+
+ expect(resolver).toHaveBeenCalledWith({
+ first: GRAPHQL_PAGE_SIZE,
+ after: '1234',
+ fullPath: provideDefaults.groupPath,
+ });
+ });
+
+ it('before is set from the url params', async () => {
+ setWindowLocation('?before=1234');
+ createComponent();
+ await waitForPromises();
+
+ expect(resolver).toHaveBeenCalledWith({
+ first: null,
+ last: GRAPHQL_PAGE_SIZE,
+ before: '1234',
+ fullPath: provideDefaults.groupPath,
+ });
+ });
+
+ describe('when url params are changed', () => {
+ it('after is set from the url params', async () => {
+ createComponent();
+ await waitForPromises();
+ router.push('?after=1234');
+ await waitForPromises();
+
+ expect(resolver).toHaveBeenCalledWith({
+ first: GRAPHQL_PAGE_SIZE,
+ after: '1234',
+ fullPath: provideDefaults.groupPath,
+ });
+ });
+ });
+ });
});
});
});
diff --git a/spec/frontend/packages_and_registries/dependency_proxy/utils_spec.js b/spec/frontend/packages_and_registries/dependency_proxy/utils_spec.js
new file mode 100644
index 00000000000..72072c08537
--- /dev/null
+++ b/spec/frontend/packages_and_registries/dependency_proxy/utils_spec.js
@@ -0,0 +1,25 @@
+import { getPageParams } from '~/packages_and_registries/dependency_proxy/utils';
+
+describe('getPageParams', () => {
+ it('should return the previous page params if before cursor is available', () => {
+ const pageInfo = { before: 'abc123' };
+ expect(getPageParams(pageInfo)).toEqual({
+ first: null,
+ before: pageInfo.before,
+ last: 20,
+ });
+ });
+
+ it('should return the next page params if after cursor is available', () => {
+ const pageInfo = { after: 'abc123' };
+ expect(getPageParams(pageInfo)).toEqual({
+ after: pageInfo.after,
+ first: 20,
+ });
+ });
+
+ it('should return an empty object if both before and after cursors are not available', () => {
+ const pageInfo = {};
+ expect(getPageParams(pageInfo)).toEqual({});
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/assignees_spec.js b/spec/frontend/sidebar/components/assignees/assignees_spec.js
index 65a07382ebc..2767d36ac3d 100644
--- a/spec/frontend/sidebar/components/assignees/assignees_spec.js
+++ b/spec/frontend/sidebar/components/assignees/assignees_spec.js
@@ -145,7 +145,7 @@ describe('Assignee component', () => {
});
expect(findAllAvatarLinks()).toHaveLength(users.length);
- expect(wrapper.find('.user-list-more').exists()).toBe(false);
+ expect(wrapper.find('[data-testid="user-list-more"]').exists()).toBe(false);
});
it('shows sorted assignee where "can merge" users are sorted first', () => {
diff --git a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
index c74a714cca4..9e7a198d32c 100644
--- a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -24,7 +24,7 @@ describe('UncollapsedAssigneeList component', () => {
});
}
- const findMoreButton = () => wrapper.find('.user-list-more button');
+ const findMoreButton = () => wrapper.find('[data-testid="user-list-more-button"]');
describe('One assignee/user', () => {
let user;
diff --git a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
index 2f72c3baa17..0f0c4a8b47e 100644
--- a/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
+++ b/spec/frontend/vue_merge_request_widget/mr_widget_options_spec.js
@@ -21,12 +21,15 @@ import {
registerExtension,
registeredExtensions,
} from '~/vue_merge_request_widget/components/extensions';
+import { STATUS_CLOSED, STATUS_OPEN } from '~/issues/constants';
import { STATE_QUERY_POLLING_INTERVAL_BACKOFF } from '~/vue_merge_request_widget/constants';
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import eventHub from '~/vue_merge_request_widget/event_hub';
import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import Approvals from '~/vue_merge_request_widget/components/approvals/approvals.vue';
+import ConflictsState from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import Preparing from '~/vue_merge_request_widget/components/states/mr_widget_preparing.vue';
+import ShaMismatch from '~/vue_merge_request_widget/components/states/sha_mismatch.vue';
import WidgetContainer from '~/vue_merge_request_widget/components/widget/app.vue';
import WidgetSuggestPipeline from '~/vue_merge_request_widget/components/mr_widget_suggest_pipeline.vue';
import MrWidgetAlertMessage from '~/vue_merge_request_widget/components/mr_widget_alert_message.vue';
@@ -81,6 +84,7 @@ describe('MrWidgetOptions', () => {
const findMergedPipelineContainer = () => wrapper.findByTestId('merged-pipeline-container');
const findPipelineContainer = () => wrapper.findByTestId('pipeline-container');
const findAlertMessage = () => wrapper.findComponent(MrWidgetAlertMessage);
+ const findMergePipelineForkAlert = () => wrapper.findByTestId('merge-pipeline-fork-warning');
beforeEach(() => {
gl.mrWidgetData = { ...mockData };
@@ -101,11 +105,12 @@ describe('MrWidgetOptions', () => {
});
const createComponent = ({
- mrData = mockData,
+ updatedMrData = {},
options = {},
data = {},
mountFn = shallowMountExtended,
} = {}) => {
+ const mrData = { ...mockData, ...updatedMrData };
const mockedApprovalsSubscription = createMockApolloSubscription();
queryResponse = {
data: {
@@ -153,9 +158,7 @@ describe('MrWidgetOptions', () => {
});
wrapper = mountFn(MrWidgetOptions, {
- propsData: {
- mrData: { ...mrData },
- },
+ propsData: { mrData },
data() {
return {
loading: false,
@@ -180,12 +183,15 @@ describe('MrWidgetOptions', () => {
describe('default', () => {
beforeEach(() => {
jest.spyOn(document, 'dispatchEvent');
- return createComponent();
});
// quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/385238
// eslint-disable-next-line jest/no-disabled-tests
describe.skip('data', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
it('should instantiate Store and Service', () => {
expect(wrapper.vm.mr).toBeDefined();
expect(wrapper.vm.service).toBeDefined();
@@ -194,6 +200,10 @@ describe('MrWidgetOptions', () => {
describe('computed', () => {
describe('componentName', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
// quarantine: https://gitlab.com/gitlab-org/gitlab/-/issues/409365
// eslint-disable-next-line jest/no-disabled-tests
it.skip.each`
@@ -205,143 +215,144 @@ describe('MrWidgetOptions', () => {
});
it.each`
- state | componentName
- ${'conflicts'} | ${'mr-widget-conflicts'}
- ${'shaMismatch'} | ${'sha-mismatch'}
- `('should translate $state into $componentName', ({ state, componentName }) => {
- wrapper.vm.mr.state = state;
-
- expect(wrapper.vm.componentName).toEqual(componentName);
+ state | componentName | component
+ ${'conflicts'} | ${'ConflictsState'} | ${ConflictsState}
+ ${'shaMismatch'} | ${'ShaMismatch'} | ${ShaMismatch}
+ `('should translate $state into $componentName component', async ({ state, component }) => {
+ Vue.set(wrapper.vm.mr, 'state', state);
+ await nextTick();
+ expect(wrapper.findComponent(component).exists()).toBe(true);
});
});
describe('MrWidgetPipelineContainer', () => {
- it('should return true when hasCI is true', async () => {
- wrapper.vm.mr.hasCI = true;
- await nextTick();
+ it('renders the pipeline container when it has CI', () => {
+ createComponent({ updatedMrData: { has_ci: true } });
expect(findPipelineContainer().exists()).toBe(true);
});
- it('should return false when hasCI is false', async () => {
- wrapper.vm.mr.hasCI = false;
- await nextTick();
-
+ it('does not render the pipeline container when it does not have CI', () => {
+ createComponent({ updatedMrData: { has_ci: false } });
expect(findPipelineContainer().exists()).toBe(false);
});
});
describe('shouldRenderCollaborationStatus', () => {
- describe('when collaboration is allowed', () => {
- beforeEach(() => {
- wrapper.vm.mr.allowCollaboration = true;
- });
-
- describe('when merge request is opened', () => {
- beforeEach(() => {
- wrapper.vm.mr.isOpen = true;
- return nextTick();
- });
-
- it('should render collaboration status', () => {
- expect(wrapper.text()).toContain(COLLABORATION_MESSAGE);
- });
+ it('renders collaboration message when collaboration is allowed and the MR is open', () => {
+ createComponent({
+ updatedMrData: { allow_collaboration: true, state: STATUS_OPEN, not: false },
});
-
- describe('when merge request is not opened', () => {
- beforeEach(() => {
- wrapper.vm.mr.isOpen = false;
- return nextTick();
- });
-
- it('should not render collaboration status', () => {
- expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
- });
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ allowCollaboration: true,
+ isOpen: true,
});
+ expect(wrapper.text()).toContain(COLLABORATION_MESSAGE);
});
- describe('when collaboration is not allowed', () => {
- beforeEach(() => {
- wrapper.vm.mr.allowCollaboration = false;
+ it('does not render collaboration message when collaboration is allowed and the MR is closed', () => {
+ createComponent({
+ updatedMrData: { allow_collaboration: true, state: STATUS_CLOSED, not: true },
});
-
- describe('when merge request is opened', () => {
- beforeEach(() => {
- wrapper.vm.mr.isOpen = true;
- return nextTick();
- });
-
- it('should not render collaboration status', () => {
- expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
- });
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ allowCollaboration: true,
+ isOpen: false,
});
+ expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
});
- });
- describe('showMergePipelineForkWarning', () => {
- describe('when the source project and target project are the same', () => {
- beforeEach(() => {
- Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
- Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
- Vue.set(wrapper.vm.mr, 'targetProjectId', 1);
- return nextTick();
+ it('does not render collaboration message when collaboration is not allowed and the MR is closed', () => {
+ createComponent({
+ updatedMrData: { allow_collaboration: undefined, state: STATUS_CLOSED, not: true },
});
-
- it('should be false', () => {
- expect(findAlertMessage().exists()).toBe(false);
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ allowCollaboration: undefined,
+ isOpen: false,
});
+ expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
});
- describe('when merge pipelines are not enabled', () => {
- beforeEach(() => {
- Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', false);
- Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
- Vue.set(wrapper.vm.mr, 'targetProjectId', 2);
- return nextTick();
+ it('does not render collaboration message when collaboration is not allowed and the MR is open', () => {
+ createComponent({
+ updatedMrData: { allow_collaboration: undefined, state: STATUS_OPEN, not: true },
+ });
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ allowCollaboration: undefined,
+ isOpen: true,
});
+ expect(wrapper.text()).not.toContain(COLLABORATION_MESSAGE);
+ });
+ });
- it('should be false', () => {
- expect(findAlertMessage().exists()).toBe(false);
+ describe('showMergePipelineForkWarning', () => {
+ it('hides the alert when the source project and target project are the same', async () => {
+ createComponent({
+ updatedMrData: {
+ source_project_id: 1,
+ target_project_id: 1,
+ },
});
+ await nextTick();
+ Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
+ await nextTick();
+ expect(findMergePipelineForkAlert().exists()).toBe(false);
});
- describe('when merge pipelines are enabled _and_ the source project and target project are different', () => {
- beforeEach(() => {
- Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
- Vue.set(wrapper.vm.mr, 'sourceProjectId', 1);
- Vue.set(wrapper.vm.mr, 'targetProjectId', 2);
- return nextTick();
+ it('hides the alert when merge pipelines are not enabled', async () => {
+ createComponent({
+ updatedMrData: {
+ source_project_id: 1,
+ target_project_id: 2,
+ },
});
+ await nextTick();
+ expect(findMergePipelineForkAlert().exists()).toBe(false);
+ });
- it('should be true', () => {
- expect(findAlertMessage().exists()).toBe(true);
+ it('shows the alert when merge pipelines are enabled and the source project and target project are different', async () => {
+ createComponent({
+ updatedMrData: {
+ source_project_id: 1,
+ target_project_id: 2,
+ },
});
+ await nextTick();
+ Vue.set(wrapper.vm.mr, 'mergePipelinesEnabled', true);
+ await nextTick();
+ expect(findMergePipelineForkAlert().exists()).toBe(true);
});
});
describe('formattedHumanAccess', () => {
- it('when user is a tool admin but not a member of project', async () => {
- wrapper.vm.mr.humanAccess = null;
- wrapper.vm.mr.mergeRequestAddCiConfigPath = 'test';
- wrapper.vm.mr.hasCI = false;
- wrapper.vm.mr.isDismissedSuggestPipeline = false;
- await nextTick();
-
+ it('renders empty string when user is a tool admin but not a member of project', () => {
+ createComponent({
+ updatedMrData: {
+ human_access: null,
+ merge_request_add_ci_config_path: 'test',
+ has_ci: false,
+ is_dismissed_suggest_pipeline: false,
+ },
+ });
expect(findSuggestPipeline().props('humanAccess')).toBe('');
});
-
- it('when user a member of the project', async () => {
- wrapper.vm.mr.humanAccess = 'Owner';
- wrapper.vm.mr.mergeRequestAddCiConfigPath = 'test';
- wrapper.vm.mr.hasCI = false;
- wrapper.vm.mr.isDismissedSuggestPipeline = false;
- await nextTick();
-
+ it('renders human access when user is a member of the project', () => {
+ createComponent({
+ updatedMrData: {
+ human_access: 'Owner',
+ merge_request_add_ci_config_path: 'test',
+ has_ci: false,
+ is_dismissed_suggest_pipeline: false,
+ },
+ });
expect(findSuggestPipeline().props('humanAccess')).toBe('owner');
});
});
});
describe('methods', () => {
+ beforeEach(async () => {
+ await createComponent();
+ });
+
describe('checkStatus', () => {
let cb;
let isCbExecuted;
@@ -434,22 +445,30 @@ describe('MrWidgetOptions', () => {
});
it('should bind to SetBranchRemoveFlag', () => {
- expect(wrapper.vm.mr.isRemovingSourceBranch).toBe(false);
-
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ isRemovingSourceBranch: false,
+ });
eventHub.$emit('SetBranchRemoveFlag', [true]);
-
- expect(wrapper.vm.mr.isRemovingSourceBranch).toBe(true);
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ isRemovingSourceBranch: true,
+ });
});
- it('should bind to FailedToMerge', () => {
- wrapper.vm.mr.state = '';
- wrapper.vm.mr.mergeError = '';
-
+ it('should bind to FailedToMerge', async () => {
+ expect(findAlertMessage().exists()).toBe(false);
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ mergeError: null,
+ state: 'merged',
+ });
const mergeError = 'Something bad happened!';
- eventHub.$emit('FailedToMerge', mergeError);
+ await eventHub.$emit('FailedToMerge', mergeError);
- expect(wrapper.vm.mr.state).toBe('failedToMerge');
- expect(wrapper.vm.mr.mergeError).toBe(mergeError);
+ expect(findAlertMessage().exists()).toBe(true);
+ expect(findAlertMessage().text()).toBe(`${mergeError}. Try again.`);
+ expect(findPipelineContainer().props('mr')).toMatchObject({
+ mergeError,
+ state: 'failedToMerge',
+ });
});
it('should bind to UpdateWidgetData', () => {
@@ -546,7 +565,6 @@ describe('MrWidgetOptions', () => {
wrapper.destroy();
return createComponent({
- mrData: mockData,
options: {},
data: {
pollInterval: interval,
@@ -628,16 +646,17 @@ describe('MrWidgetOptions', () => {
};
it('renders multiple deployments', async () => {
- wrapper.vm.mr.deployments.push(
- {
- ...deploymentMockData,
- },
- {
- ...deploymentMockData,
- id: deploymentMockData.id + 1,
+ await createComponent({
+ updatedMrData: {
+ deployments: [
+ deploymentMockData,
+ {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ },
+ ],
},
- );
- await nextTick();
+ });
expect(findPipelineContainer().props('isPostMerge')).toBe(false);
expect(findPipelineContainer().props('mr').deployments).toHaveLength(2);
expect(findPipelineContainer().props('mr').postMergeDeployments).toHaveLength(0);
@@ -646,7 +665,8 @@ describe('MrWidgetOptions', () => {
describe('pipeline for target branch after merge', () => {
describe('with information for target branch pipeline', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ await createComponent();
wrapper.vm.mr.state = 'merged';
wrapper.vm.mr.mergePipeline = {
id: 127,
@@ -804,7 +824,8 @@ describe('MrWidgetOptions', () => {
});
describe('without information for target branch pipeline', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ await createComponent();
wrapper.vm.mr.state = 'merged';
return nextTick();
@@ -816,7 +837,8 @@ describe('MrWidgetOptions', () => {
});
describe('when state is not merged', () => {
- beforeEach(() => {
+ beforeEach(async () => {
+ await createComponent();
wrapper.vm.mr.state = 'archived';
return nextTick();
@@ -829,6 +851,7 @@ describe('MrWidgetOptions', () => {
});
it('should not suggest pipelines when feature flag is not present', () => {
+ createComponent();
expect(findSuggestPipeline().exists()).toBe(false);
});
});
@@ -875,11 +898,11 @@ describe('MrWidgetOptions', () => {
${'merged'} | ${true} | ${'shows'}
${'open'} | ${true} | ${'shows'}
`('$showText merge error when state is $state', async ({ state, show }) => {
- createComponent({ mrData: { ...mockData, state, mergeError: 'Error!' } });
+ createComponent({ updatedMrData: { state, mergeError: 'Error!' } });
await waitForPromises();
- expect(wrapper.find('[data-testid="merge_error"]').exists()).toBe(show);
+ expect(wrapper.findByTestId('merge-error').exists()).toBe(show);
});
});
@@ -1278,11 +1301,7 @@ describe('MrWidgetOptions', () => {
it('renders the Preparing state component when the MR state is initially "preparing"', async () => {
await createComponent({
- mrData: {
- ...mockData,
- state: 'opened',
- detailedMergeStatus: 'PREPARING',
- },
+ updatedMrData: { state: 'opened', detailedMergeStatus: 'PREPARING' },
});
expect(findApprovalsWidget().exists()).toBe(false);
@@ -1296,11 +1315,7 @@ describe('MrWidgetOptions', () => {
it("shows the Preparing widget when the MR reports it's not ready yet", async () => {
await createComponent({
- mrData: {
- ...mockData,
- state: 'opened',
- detailedMergeStatus: 'PREPARING',
- },
+ updatedMrData: { state: 'opened', detailedMergeStatus: 'PREPARING' },
options: {},
data: {},
});
@@ -1310,11 +1325,7 @@ describe('MrWidgetOptions', () => {
it('removes the Preparing widget when the MR indicates it has been prepared', async () => {
await createComponent({
- mrData: {
- ...mockData,
- state: 'opened',
- detailedMergeStatus: 'PREPARING',
- },
+ updatedMrData: { state: 'opened', detailedMergeStatus: 'PREPARING' },
options: {},
data: {},
});
diff --git a/spec/models/metrics/users_starred_dashboard_spec.rb b/spec/models/metrics/users_starred_dashboard_spec.rb
deleted file mode 100644
index c89344c0a1c..00000000000
--- a/spec/models/metrics/users_starred_dashboard_spec.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Metrics::UsersStarredDashboard do
- describe 'associations' do
- it { is_expected.to belong_to(:project).inverse_of(:metrics_users_starred_dashboards) }
- it { is_expected.to belong_to(:user).inverse_of(:metrics_users_starred_dashboards) }
- end
-
- describe 'validation' do
- subject { build(:metrics_users_starred_dashboard) }
-
- it { is_expected.to validate_presence_of(:user_id) }
- it { is_expected.to validate_presence_of(:project_id) }
- it { is_expected.to validate_presence_of(:dashboard_path) }
- it { is_expected.to validate_length_of(:dashboard_path).is_at_most(255) }
- it { is_expected.to validate_uniqueness_of(:dashboard_path).scoped_to(%i[user_id project_id]) }
- end
-
- context 'scopes' do
- let_it_be(:project) { create(:project) }
- let_it_be(:starred_dashboard_a) { create(:metrics_users_starred_dashboard, project: project, dashboard_path: 'path_a') }
- let_it_be(:starred_dashboard_b) { create(:metrics_users_starred_dashboard, project: project, dashboard_path: 'path_b') }
- let_it_be(:starred_dashboard_c) { create(:metrics_users_starred_dashboard, dashboard_path: 'path_b') }
-
- describe '#for_project' do
- it 'selects only starred dashboards belonging to project' do
- expect(described_class.for_project(project)).to contain_exactly starred_dashboard_a, starred_dashboard_b
- end
- end
-
- describe '#for_project_dashboard' do
- it 'selects only starred dashboards belonging to project with given dashboard path' do
- expect(described_class.for_project_dashboard(project, 'path_b')).to contain_exactly starred_dashboard_b
- end
- end
- end
-end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 0fc689b9f6c..b2f0da97c7a 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -1612,18 +1612,6 @@ RSpec.describe Note, feature_category: :team_planning do
note.save!
end
- context 'when action_cable_notes is disabled' do
- before do
- stub_feature_flags(action_cable_notes: false)
- end
-
- it 'does not broadcast an Action Cable event' do
- expect(Noteable::NotesChannel).not_to receive(:broadcast_to)
-
- note.save!
- end
- end
-
it "expires cache for note's issue when note is saved" do
expect_expiration(note.noteable)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index aee491fd68d..0cb29b492e0 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -143,7 +143,6 @@ RSpec.describe Project, factory_default: :keep, feature_category: :groups_and_pr
it { is_expected.to have_many(:alert_management_alerts) }
it { is_expected.to have_many(:alert_management_http_integrations) }
it { is_expected.to have_many(:jira_imports) }
- it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:project) }
it { is_expected.to have_many(:repository_storage_moves) }
it { is_expected.to have_many(:reviews).inverse_of(:project) }
it { is_expected.to have_many(:packages).class_name('Packages::Package') }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index ded024462b5..bd407c38f2a 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -168,7 +168,6 @@ RSpec.describe User, feature_category: :user_profile do
it { is_expected.to have_many(:abuse_events).class_name('Abuse::Event').inverse_of(:user) }
it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') }
it { is_expected.to have_many(:releases).dependent(:nullify) }
- it { is_expected.to have_many(:metrics_users_starred_dashboards).inverse_of(:user) }
it { is_expected.to have_many(:reviews).inverse_of(:author) }
it { is_expected.to have_many(:merge_request_assignees).inverse_of(:assignee) }
it { is_expected.to have_many(:merge_request_reviewers).inverse_of(:reviewer) }
diff --git a/spec/requests/api/metrics/user_starred_dashboards_spec.rb b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
index bdeba777350..d42cec7af30 100644
--- a/spec/requests/api/metrics/user_starred_dashboards_spec.rb
+++ b/spec/requests/api/metrics/user_starred_dashboards_spec.rb
@@ -58,11 +58,6 @@ RSpec.describe API::Metrics::UserStarredDashboards, feature_category: :metrics d
end
describe 'DELETE /projects/:id/metrics/user_starred_dashboards' do
- let_it_be(:user_starred_dashboard_1) { create(:metrics_users_starred_dashboard, user: user, project: project, dashboard_path: dashboard) }
- let_it_be(:user_starred_dashboard_2) { create(:metrics_users_starred_dashboard, user: user, project: project) }
- let_it_be(:other_user_starred_dashboard) { create(:metrics_users_starred_dashboard, project: project) }
- let_it_be(:other_project_starred_dashboard) { create(:metrics_users_starred_dashboard, user: user) }
-
before do
project.add_reporter(user)
end
diff --git a/spec/services/bulk_imports/create_service_spec.rb b/spec/services/bulk_imports/create_service_spec.rb
index 93feab97f44..b1606f1f944 100644
--- a/spec/services/bulk_imports/create_service_spec.rb
+++ b/spec/services/bulk_imports/create_service_spec.rb
@@ -470,7 +470,7 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do
context 'when the source_full_path contains only integer characters' do
let(:query_string) { BulkImports::Projects::Graphql::GetProjectQuery.new(context: nil).to_s }
let(:graphql_response) do
- double(original_hash: { 'data' => { 'project' => { 'id' => entity_source_id } } }) # rubocop:disable RSpec/VerifiedDoubles
+ double(original_hash: { 'data' => { 'project' => { 'id' => entity_source_id } } }) # rubocop:disable RSpec/VerifiedDoubles
end
let(:params) do
diff --git a/spec/services/metrics/sample_metrics_service_spec.rb b/spec/services/metrics/sample_metrics_service_spec.rb
deleted file mode 100644
index 3442b4303db..00000000000
--- a/spec/services/metrics/sample_metrics_service_spec.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Metrics::SampleMetricsService, feature_category: :metrics do
- describe 'query' do
- let(:range_start) { '2019-12-02T23:31:45.000Z' }
- let(:range_end) { '2019-12-03T00:01:45.000Z' }
-
- subject { described_class.new(identifier, range_start: range_start, range_end: range_end).query }
-
- context 'when the file is not found' do
- let(:identifier) { nil }
-
- it { is_expected.to be_nil }
- end
-
- context 'when the file is found' do
- let(:identifier) { 'sample_metric_query_result' }
- let(:source) { File.join(Rails.root, 'spec/fixtures/gitlab/sample_metrics', "#{identifier}.yml") }
- let(:destination) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, "#{identifier}.yml") }
-
- around do |example|
- FileUtils.mkdir_p(Metrics::SampleMetricsService::DIRECTORY)
- FileUtils.cp(source, destination)
-
- example.run
- ensure
- FileUtils.rm(destination)
- end
-
- subject { described_class.new(identifier, range_start: range_start, range_end: range_end).query }
-
- it 'loads data from the sample file correctly' do
- expect(subject).to eq(YAML.load_file(source)[30])
- end
- end
-
- context 'when the identifier is for a path outside of sample_metrics' do
- let(:identifier) { '../config/secrets' }
-
- it { is_expected.to be_nil }
- end
- end
-end
diff --git a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
index cb7001a9faf..21bba14f3e6 100644
--- a/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
+++ b/spec/support/shared_examples/channels/noteable/notes_channel_shared_examples.rb
@@ -15,16 +15,4 @@ RSpec.shared_examples 'handle subscription based on user access' do
expect(subscription).to be_rejected
end
-
- context 'when action_cable_notes is disabled' do
- before do
- stub_feature_flags(action_cable_notes: false)
- end
-
- it 'rejects the subscription' do
- subscribe(subscribe_params)
-
- expect(subscription).to be_rejected
- end
- end
end
diff --git a/spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb b/spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb
deleted file mode 100644
index 67bf512c6da..00000000000
--- a/spec/tasks/gitlab/generate_sample_prometheus_data_rake_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require 'rake_helper'
-
-RSpec.describe 'gitlab:generate_sample_prometheus_data rake task', :silence_stdout do
- let(:cluster) { create(:cluster, :provided_by_user, :project) }
- let(:environment) { create(:environment, project: cluster.project) }
- let(:sample_query_file) { File.join(Rails.root, Metrics::SampleMetricsService::DIRECTORY, 'test_query_result.yml') }
- let!(:metric) { create(:prometheus_metric, project: cluster.project, identifier: 'test_query_result') }
-
- around do |example|
- example.run
- ensure
- FileUtils.rm(sample_query_file)
- end
-
- it 'creates the file correctly' do
- Rake.application.rake_require 'tasks/gitlab/generate_sample_prometheus_data'
- allow(Environment).to receive(:find).and_return(environment)
- allow(environment).to receive_message_chain(:prometheus_adapter, :prometheus_client, :query_range) { sample_query_result[30] }
- run_rake_task('gitlab:generate_sample_prometheus_data', [environment.id])
-
- expect(File.exist?(sample_query_file)).to be true
-
- query_file_content = YAML.load_file(sample_query_file)
-
- expect(query_file_content).to eq(sample_query_result)
- end
-end
-
-def sample_query_result
- file = File.join(Rails.root, 'spec/fixtures/gitlab/sample_metrics', 'sample_metric_query_result.yml')
- YAML.load_file(File.expand_path(file, __dir__))
-end