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:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-19 00:08:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-19 00:08:58 +0300
commitacc1c1c468a8a75b4ab75133e95d41005543bb32 (patch)
treed6193050b3ba4fff230c53e7c449bc9b77f6c0e6
parente69b62ceed2f15335d9526923f8f72f5ca5ea6aa (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml1
-rw-r--r--app/assets/javascripts/clusters_list/components/agent_table.vue274
-rw-r--r--app/assets/javascripts/clusters_list/components/agents.vue74
-rw-r--r--app/assets/javascripts/clusters_list/components/delete_agent_button.vue7
-rw-r--r--app/assets/javascripts/clusters_list/components/install_agent_modal.vue11
-rw-r--r--app/assets/javascripts/clusters_list/constants.js4
-rw-r--r--app/assets/javascripts/clusters_list/graphql/cache_update.js28
-rw-r--r--app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql1
-rw-r--r--app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql28
-rw-r--r--app/assets/javascripts/diffs/store/actions.js1
-rw-r--r--app/assets/javascripts/diffs/store/utils.js2
-rw-r--r--app/assets/javascripts/security_configuration/components/app.vue11
-rw-r--r--app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue2
-rw-r--r--app/assets/javascripts/security_configuration/components/feature_card.vue6
-rw-r--r--app/models/diff_note_position.rb3
-rw-r--r--app/validators/json_schemas/position.json6
-rw-r--r--app/views/protected_branches/_create_protected_branch.html.haml4
-rw-r--r--config/feature_flags/development/log_response_length.yml2
-rw-r--r--doc/ci/environments/deployment_approvals.md35
-rw-r--r--doc/ci/environments/img/multiple_approval_rules_v16_0.pngbin0 -> 109289 bytes
-rw-r--r--doc/ci/environments/img/unified_approval_rules_v16_0.pngbin0 -> 123940 bytes
-rw-r--r--doc/user/project/merge_requests/drafts.md9
-rw-r--r--doc/user/project/merge_requests/img/auto_merge_ready_v16_0.pngbin0 -> 8743 bytes
-rw-r--r--doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_10.pngbin4958 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_draft_merge_requests_v13_10.pngbin3453 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_draft_merge_requests_v16_0.pngbin0 -> 4416 bytes
-rw-r--r--doc/user/project/merge_requests/img/filtering_merge_requests_by_date_v14_6.pngbin4318 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/filtering_merge_requests_by_environment_v14_6.pngbin8053 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_request_assignees_v16_0.pngbin0 -> 4567 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_request_draft_blocked_v16_0.pngbin0 -> 11397 bytes
-rw-r--r--doc/user/project/merge_requests/img/merge_request_pipeline.pngbin31026 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.pngbin7479 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/mwps_v15_4.pngbin11146 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/post_merge_pipeline_v16_0.pngbin0 -> 26636 bytes
-rw-r--r--doc/user/project/merge_requests/index.md20
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md2
-rw-r--r--doc/user/project/merge_requests/widgets.md2
-rw-r--r--lib/api/entities/draft_note.rb2
-rw-r--r--lib/api/entities/note.rb2
-rw-r--r--lib/api/npm_group_packages.rb27
-rw-r--r--lib/gitlab/database/tables_truncate.rb2
-rw-r--r--lib/gitlab/diff/formatters/base_formatter.rb1
-rw-r--r--lib/gitlab/diff/formatters/image_formatter.rb1
-rw-r--r--lib/gitlab/diff/formatters/text_formatter.rb4
-rw-r--r--lib/gitlab/diff/position.rb9
-rw-r--r--lib/gitlab/diff/position_tracer.rb3
-rw-r--r--lib/gitlab/diff/position_tracer/line_strategy.rb5
-rw-r--r--locale/gitlab.pot62
-rw-r--r--qa/qa/page/element.rb15
-rw-r--r--qa/qa/page/file/edit.rb6
-rw-r--r--qa/qa/page/project/secure/configuration_form.rb48
-rw-r--r--qa/qa/page/project/settings/protected_branches.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb18
-rw-r--r--qa/spec/page/element_spec.rb8
-rw-r--r--spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb49
-rw-r--r--spec/frontend/clusters_list/components/agent_table_spec.js47
-rw-r--r--spec/frontend/clusters_list/components/agents_spec.js117
-rw-r--r--spec/frontend/clusters_list/components/delete_agent_button_spec.js6
-rw-r--r--spec/frontend/clusters_list/components/mock_data.js10
-rw-r--r--spec/frontend/clusters_list/mocks/apollo.js14
-rw-r--r--spec/frontend/diffs/store/utils_spec.js2
-rw-r--r--spec/lib/api/entities/draft_note_spec.rb4
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb3
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb3
-rw-r--r--spec/lib/gitlab/github_import/representation/diff_note_spec.rb6
-rw-r--r--spec/requests/api/npm_group_packages_spec.rb44
-rw-r--r--spec/support/helpers/test_env.rb3
-rw-r--r--spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb8
72 files changed, 606 insertions, 476 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index 4d03aebee02..bb16caa68c5 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -885,7 +885,6 @@
- <<: *if-merge-request
- <<: *if-default-branch-refs
variables:
- ARCH: amd64,arm64
BUILD_GDK_BASE: "true"
.build-images:rules:build-assets-image:
diff --git a/app/assets/javascripts/clusters_list/components/agent_table.vue b/app/assets/javascripts/clusters_list/components/agent_table.vue
index d7e98638a11..529be7169db 100644
--- a/app/assets/javascripts/clusters_list/components/agent_table.vue
+++ b/app/assets/javascripts/clusters_list/components/agent_table.vue
@@ -7,6 +7,8 @@ import {
GlTooltip,
GlTooltipDirective,
GlPopover,
+ GlBadge,
+ GlPagination,
} from '@gitlab/ui';
import semverLt from 'semver/functions/lt';
import semverInc from 'semver/functions/inc';
@@ -15,7 +17,7 @@ import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import timeagoMixin from '~/vue_shared/mixins/timeago';
import { helpPagePath } from '~/helpers/help_page_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { AGENT_STATUSES, I18N_AGENT_TABLE } from '../constants';
+import { MAX_LIST_COUNT, AGENT_STATUSES, I18N_AGENT_TABLE } from '../constants';
import { getAgentConfigPath } from '../clusters_util';
import DeleteAgentButton from './delete_agent_button.vue';
@@ -28,6 +30,8 @@ export default {
GlSprintf,
GlTooltip,
GlPopover,
+ GlBadge,
+ GlPagination,
TimeAgoTooltip,
DeleteAgentButton,
},
@@ -60,6 +64,12 @@ export default {
type: Number,
},
},
+ data() {
+ return {
+ currentPage: 1,
+ limit: this.maxAgents ?? MAX_LIST_COUNT,
+ };
+ },
computed: {
fields() {
const tdClass = 'gl-pt-3! gl-pb-4! gl-vertical-align-middle!';
@@ -114,6 +124,16 @@ export default {
serverVersion() {
return this.kasVersion || this.gitlabVersion;
},
+ showPagination() {
+ return !this.maxAgents && this.agents.length > this.limit;
+ },
+ prevPage() {
+ return Math.max(this.currentPage - 1, 0);
+ },
+ nextPage() {
+ const nextPage = this.currentPage + 1;
+ return nextPage > Math.ceil(this.agents.length / this.limit) ? null : nextPage;
+ },
},
methods: {
getStatusCellId(item) {
@@ -184,84 +204,105 @@ export default {
</script>
<template>
- <gl-table
- :items="agentsList"
- :fields="fields"
- stacked="md"
- class="gl-mb-4!"
- data-testid="cluster-agent-list-table"
- >
- <template #cell(name)="{ item }">
- <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">
- {{ item.name }}
- </gl-link>
- </template>
+ <div>
+ <gl-table
+ :items="agentsList"
+ :fields="fields"
+ :per-page="limit"
+ :current-page="currentPage"
+ stacked="md"
+ class="gl-mb-4!"
+ data-testid="cluster-agent-list-table"
+ >
+ <template #cell(name)="{ item }">
+ <gl-link :href="item.webPath" data-testid="cluster-agent-name-link">{{ item.name }}</gl-link
+ ><gl-badge v-if="item.isShared" class="gl-ml-3">{{
+ $options.i18n.sharedBadgeText
+ }}</gl-badge>
+ </template>
- <template #cell(status)="{ item }">
- <span
- :id="getStatusCellId(item)"
- class="gl-md-pr-5"
- data-testid="cluster-agent-connection-status"
- >
- <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
- <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="16" /></span
- >{{ $options.AGENT_STATUSES[item.status].name }}
- </span>
- <gl-tooltip v-if="item.status === 'active'" :target="getStatusCellId(item)" placement="right">
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
- </gl-sprintf>
- </gl-tooltip>
- <gl-popover
- v-else
- :target="getStatusCellId(item)"
- :title="$options.AGENT_STATUSES[item.status].tooltip.title"
- placement="right"
- container="viewport"
- >
- <p>
- <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
- ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
- >
- </p>
- <p class="gl-mb-0">
- <gl-link :href="$options.troubleshootingLink" target="_blank" class="gl-font-sm">
- {{ $options.i18n.troubleshootingText }}</gl-link
- >
- </p>
- </gl-popover>
- </template>
+ <template #cell(status)="{ item }">
+ <span
+ :id="getStatusCellId(item)"
+ class="gl-md-pr-5"
+ data-testid="cluster-agent-connection-status"
+ >
+ <span :class="$options.AGENT_STATUSES[item.status].class" class="gl-mr-3">
+ <gl-icon :name="$options.AGENT_STATUSES[item.status].icon" :size="16" /></span
+ >{{ $options.AGENT_STATUSES[item.status].name }}
+ </span>
+ <gl-tooltip
+ v-if="item.status === 'active'"
+ :target="getStatusCellId(item)"
+ placement="right"
+ >
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.title"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template>
+ </gl-sprintf>
+ </gl-tooltip>
+ <gl-popover
+ v-else
+ :target="getStatusCellId(item)"
+ :title="$options.AGENT_STATUSES[item.status].tooltip.title"
+ placement="right"
+ container="viewport"
+ >
+ <p>
+ <gl-sprintf :message="$options.AGENT_STATUSES[item.status].tooltip.body"
+ ><template #timeAgo>{{ timeFormatted(item.lastContact) }}</template></gl-sprintf
+ >
+ </p>
+ <p class="gl-mb-0">
+ <gl-link :href="$options.troubleshootingLink" target="_blank" class="gl-font-sm">
+ {{ $options.i18n.troubleshootingText }}</gl-link
+ >
+ </p>
+ </gl-popover>
+ </template>
+
+ <template #cell(lastContact)="{ item }">
+ <span data-testid="cluster-agent-last-contact">
+ <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
+ <span v-else>{{ $options.i18n.neverConnectedText }}</span>
+ </span>
+ </template>
- <template #cell(lastContact)="{ item }">
- <span data-testid="cluster-agent-last-contact">
- <time-ago-tooltip v-if="item.lastContact" :time="item.lastContact" />
- <span v-else>{{ $options.i18n.neverConnectedText }}</span>
- </span>
- </template>
+ <template #cell(version)="{ item }">
+ <span :id="getVersionCellId(item)" data-testid="cluster-agent-version">
+ {{ getAgentVersionString(item) }}
- <template #cell(version)="{ item }">
- <span :id="getVersionCellId(item)" data-testid="cluster-agent-version">
- {{ getAgentVersionString(item) }}
+ <gl-icon
+ v-if="isVersionMismatch(item) || isVersionOutdated(item)"
+ name="warning"
+ class="gl-text-orange-500 gl-ml-2"
+ />
+ </span>
- <gl-icon
+ <gl-popover
v-if="isVersionMismatch(item) || isVersionOutdated(item)"
- name="warning"
- class="gl-text-orange-500 gl-ml-2"
- />
- </span>
+ :target="getVersionCellId(item)"
+ :title="getVersionPopoverTitle(item)"
+ :data-testid="getPopoverTestId(item)"
+ placement="right"
+ container="viewport"
+ >
+ <div v-if="isVersionMismatch(item) && isVersionOutdated(item)">
+ <p>{{ $options.i18n.versionMismatchText }}</p>
- <gl-popover
- v-if="isVersionMismatch(item) || isVersionOutdated(item)"
- :target="getVersionCellId(item)"
- :title="getVersionPopoverTitle(item)"
- :data-testid="getPopoverTestId(item)"
- placement="right"
- container="viewport"
- >
- <div v-if="isVersionMismatch(item) && isVersionOutdated(item)">
- <p>{{ $options.i18n.versionMismatchText }}</p>
+ <p class="gl-mb-0">
+ <gl-sprintf :message="$options.i18n.versionOutdatedText">
+ <template #version>{{ serverVersion }}</template>
+ </gl-sprintf>
+ <gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
+ {{ $options.i18n.viewDocsText }}</gl-link
+ >
+ </p>
+ </div>
+ <p v-else-if="isVersionMismatch(item)" class="gl-mb-0">
+ {{ $options.i18n.versionMismatchText }}
+ </p>
- <p class="gl-mb-0">
+ <p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
<gl-sprintf :message="$options.i18n.versionOutdatedText">
<template #version>{{ serverVersion }}</template>
</gl-sprintf>
@@ -269,53 +310,54 @@ export default {
{{ $options.i18n.viewDocsText }}</gl-link
>
</p>
- </div>
- <p v-else-if="isVersionMismatch(item)" class="gl-mb-0">
- {{ $options.i18n.versionMismatchText }}
- </p>
+ </gl-popover>
+ </template>
- <p v-else-if="isVersionOutdated(item)" class="gl-mb-0">
- <gl-sprintf :message="$options.i18n.versionOutdatedText">
- <template #version>{{ serverVersion }}</template>
- </gl-sprintf>
- <gl-link :href="$options.versionUpdateLink" class="gl-font-sm">
- {{ $options.i18n.viewDocsText }}</gl-link
- >
- </p>
- </gl-popover>
- </template>
+ <template #cell(agentID)="{ item }">
+ <span data-testid="cluster-agent-id">
+ {{ getAgentId(item) }}
+ </span>
+ </template>
- <template #cell(agentID)="{ item }">
- <span data-testid="cluster-agent-id">
- {{ getAgentId(item) }}
- </span>
- </template>
+ <template #cell(configuration)="{ item }">
+ <span data-testid="cluster-agent-configuration-link">
+ <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
+ {{ getAgentConfigPath(item.name) }}
+ </gl-link>
- <template #cell(configuration)="{ item }">
- <span data-testid="cluster-agent-configuration-link">
- <gl-link v-if="item.configFolder" :href="item.configFolder.webPath">
- {{ getAgentConfigPath(item.name) }}
- </gl-link>
+ <span v-else-if="item.isShared">
+ {{ $options.i18n.externalConfigText }}
+ </span>
- <span v-else
- >{{ $options.i18n.defaultConfigText }}
- <gl-link
- v-gl-tooltip
- :href="$options.configHelpLink"
- :title="$options.i18n.defaultConfigTooltip"
- :aria-label="$options.i18n.defaultConfigTooltip"
- class="gl-vertical-align-middle"
- ><gl-icon name="question-o" :size="14" /></gl-link
- ></span>
- </span>
- </template>
+ <span v-else
+ >{{ $options.i18n.defaultConfigText }}
+ <gl-link
+ v-gl-tooltip
+ :href="$options.configHelpLink"
+ :title="$options.i18n.defaultConfigTooltip"
+ :aria-label="$options.i18n.defaultConfigTooltip"
+ class="gl-vertical-align-middle"
+ ><gl-icon name="question-o" :size="14" /></gl-link
+ ></span>
+ </span>
+ </template>
+
+ <template #cell(options)="{ item }">
+ <delete-agent-button
+ v-if="!item.isShared"
+ :agent="item"
+ :default-branch-name="defaultBranchName"
+ />
+ </template>
+ </gl-table>
- <template #cell(options)="{ item }">
- <delete-agent-button
- :agent="item"
- :default-branch-name="defaultBranchName"
- :max-agents="maxAgents"
- />
- </template>
- </gl-table>
+ <gl-pagination
+ v-if="showPagination"
+ v-model="currentPage"
+ :prev-page="prevPage"
+ :next-page="nextPage"
+ align="center"
+ class="gl-mt-5"
+ />
+ </div>
</template>
diff --git a/app/assets/javascripts/clusters_list/components/agents.vue b/app/assets/javascripts/clusters_list/components/agents.vue
index 36f0f8e61ba..b1765d336c8 100644
--- a/app/assets/javascripts/clusters_list/components/agents.vue
+++ b/app/assets/javascripts/clusters_list/components/agents.vue
@@ -1,9 +1,9 @@
<script>
-import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon, GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
-import { MAX_LIST_COUNT, AGENT_FEEDBACK_ISSUE, AGENT_FEEDBACK_KEY } from '../constants';
+import { AGENT_FEEDBACK_ISSUE, AGENT_FEEDBACK_KEY } from '../constants';
import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import { getAgentLastContact, getAgentStatus } from '../clusters_util';
import AgentEmptyState from './agent_empty_state.vue';
@@ -27,7 +27,6 @@ export default {
return {
defaultBranchName: this.defaultBranchName,
projectPath: this.projectPath,
- ...this.cursor,
};
},
update(data) {
@@ -37,13 +36,15 @@ export default {
result() {
this.emitAgentsLoaded();
},
+ error() {
+ this.queryErrored = true;
+ },
},
},
components: {
AgentEmptyState,
AgentTable,
GlAlert,
- GlKeysetPagination,
GlLoadingIcon,
GlBanner,
LocalStorageSync,
@@ -69,41 +70,41 @@ export default {
},
data() {
return {
- cursor: {
- first: this.limit ? this.limit : MAX_LIST_COUNT,
- last: null,
- },
folderList: {},
feedbackBannerDismissed: false,
+ queryErrored: false,
};
},
computed: {
agentList() {
- let list = this.agents?.project?.clusterAgents?.nodes;
+ const localAgents = this.agents?.project?.clusterAgents?.nodes || [];
+ const sharedAgents = [
+ ...(this.agents?.project?.ciAccessAuthorizedAgents?.nodes || []),
+ ...(this.agents?.project?.userAccessAuthorizedAgents?.nodes || []),
+ ].map((node) => {
+ return {
+ ...node.agent,
+ isShared: true,
+ };
+ });
- if (list) {
- list = list.map((agent) => {
+ const filteredList = [...localAgents, ...sharedAgents]
+ .filter((node, index, list) => {
+ return node && index === list.findIndex((agent) => agent.id === node.id);
+ })
+ .map((agent) => {
const configFolder = this.folderList[agent.name];
const lastContact = getAgentLastContact(agent?.tokens?.nodes);
const status = getAgentStatus(lastContact);
return { ...agent, configFolder, lastContact, status };
- });
- }
+ })
+ .sort((a, b) => b.lastUsedAt - a.lastUsedAt);
- return list;
- },
- agentPageInfo() {
- return this.agents?.project?.clusterAgents?.pageInfo || {};
+ return filteredList;
},
isLoading() {
return this.$apollo.queries.agents.loading;
},
- showPagination() {
- return !this.limit && (this.agentPageInfo.hasPreviousPage || this.agentPageInfo.hasNextPage);
- },
- treePageInfo() {
- return this.agents?.project?.repository?.tree?.trees?.pageInfo || {};
- },
feedbackBannerEnabled() {
return this.glFeatures.showGitlabAgentFeedback;
},
@@ -112,22 +113,6 @@ export default {
},
},
methods: {
- nextPage() {
- this.cursor = {
- first: MAX_LIST_COUNT,
- last: null,
- afterAgent: this.agentPageInfo.endCursor,
- afterTree: this.treePageInfo.endCursor,
- };
- },
- prevPage() {
- this.cursor = {
- first: null,
- last: MAX_LIST_COUNT,
- beforeAgent: this.agentPageInfo.startCursor,
- beforeTree: this.treePageInfo.endCursor,
- };
- },
updateTreeList(data) {
const configFolders = data?.project?.repository?.tree?.trees?.nodes;
@@ -138,8 +123,7 @@ export default {
}
},
emitAgentsLoaded() {
- const count = this.agents?.project?.clusterAgents?.count;
- this.$emit('onAgentsLoad', count);
+ this.$emit('onAgentsLoad', this.agentList?.length);
},
handleBannerClose() {
this.feedbackBannerDismissed = true;
@@ -151,7 +135,7 @@ export default {
<template>
<gl-loading-icon v-if="isLoading" size="lg" />
- <section v-else-if="agentList">
+ <section v-else-if="!queryErrored">
<div v-if="agentList.length">
<local-storage-sync
v-if="feedbackBannerEnabled"
@@ -174,12 +158,8 @@ export default {
<agent-table
:agents="agentList"
:default-branch-name="defaultBranchName"
- :max-agents="cursor.first"
+ :max-agents="limit"
/>
-
- <div v-if="showPagination" class="gl-display-flex gl-justify-content-center gl-mt-5">
- <gl-keyset-pagination v-bind="agentPageInfo" @prev="prevPage" @next="nextPage" />
- </div>
</div>
<agent-empty-state v-else />
diff --git a/app/assets/javascripts/clusters_list/components/delete_agent_button.vue b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue
index 913db87f019..4088d5c79f7 100644
--- a/app/assets/javascripts/clusters_list/components/delete_agent_button.vue
+++ b/app/assets/javascripts/clusters_list/components/delete_agent_button.vue
@@ -39,11 +39,6 @@ export default {
required: false,
type: String,
},
- maxAgents: {
- default: null,
- required: false,
- type: Number,
- },
},
data() {
return {
@@ -64,8 +59,6 @@ export default {
getAgentsQueryVariables() {
return {
defaultBranchName: this.defaultBranchName,
- first: this.maxAgents,
- last: null,
projectPath: this.projectPath,
};
},
diff --git a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
index 444b9ac2a14..55e62d1c698 100644
--- a/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
+++ b/app/assets/javascripts/clusters_list/components/install_agent_modal.vue
@@ -13,10 +13,9 @@ import {
MODAL_TYPE_EMPTY,
MODAL_TYPE_REGISTER,
} from '../constants';
-import { addAgentToStore, addAgentConfigToStore } from '../graphql/cache_update';
+import { addAgentConfigToStore } from '../graphql/cache_update';
import createAgent from '../graphql/mutations/create_agent.mutation.graphql';
import createAgentToken from '../graphql/mutations/create_agent_token.mutation.graphql';
-import getAgentsQuery from '../graphql/queries/get_agents.query.graphql';
import agentConfigurations from '../graphql/queries/agent_configurations.query.graphql';
import AvailableAgentsDropdown from './available_agents_dropdown.vue';
import AgentToken from './agent_token.vue';
@@ -148,14 +147,6 @@ export default {
projectPath: this.projectPath,
},
},
- update: (store, { data: { createClusterAgent } }) => {
- addAgentToStore(
- store,
- createClusterAgent,
- getAgentsQuery,
- this.getAgentsQueryVariables,
- );
- },
})
.then(({ data: { createClusterAgent } }) => {
return createClusterAgent;
diff --git a/app/assets/javascripts/clusters_list/constants.js b/app/assets/javascripts/clusters_list/constants.js
index fe3fa22fea3..3ce10f7c3a2 100644
--- a/app/assets/javascripts/clusters_list/constants.js
+++ b/app/assets/javascripts/clusters_list/constants.js
@@ -1,7 +1,7 @@
import { __, s__, sprintf } from '~/locale';
import { helpPagePath } from '~/helpers/help_page_helper';
-export const MAX_LIST_COUNT = 25;
+export const MAX_LIST_COUNT = 20;
export const INSTALL_AGENT_MODAL_ID = 'install-agent';
export const ACTIVE_CONNECTION_TIME = 480000;
export const NAME_MAX_LENGTH = 50;
@@ -86,6 +86,8 @@ export const I18N_AGENT_TABLE = {
viewDocsText: s__('ClusterAgents|How to update an agent?'),
defaultConfigText: s__('ClusterAgents|Default configuration'),
defaultConfigTooltip: s__('ClusterAgents|What is default configuration?'),
+ sharedBadgeText: s__('ClusterAgents|shared'),
+ externalConfigText: s__('ClusterAgents|External project'),
};
export const I18N_AGENT_TOKEN = {
diff --git a/app/assets/javascripts/clusters_list/graphql/cache_update.js b/app/assets/javascripts/clusters_list/graphql/cache_update.js
index e68f6a378c0..1c58652744d 100644
--- a/app/assets/javascripts/clusters_list/graphql/cache_update.js
+++ b/app/assets/javascripts/clusters_list/graphql/cache_update.js
@@ -2,27 +2,6 @@ import produce from 'immer';
export const hasErrors = ({ errors = [] }) => errors?.length;
-export function addAgentToStore(store, createClusterAgent, query, variables) {
- if (!hasErrors(createClusterAgent)) {
- const { clusterAgent } = createClusterAgent;
- const sourceData = store.readQuery({
- query,
- variables,
- });
-
- const data = produce(sourceData, (draftData) => {
- draftData.project.clusterAgents.nodes.push(clusterAgent);
- draftData.project.clusterAgents.count += 1;
- });
-
- store.writeQuery({
- query,
- variables,
- data,
- });
- }
-}
-
export function addAgentConfigToStore(
store,
clusterAgentTokenCreate,
@@ -65,7 +44,12 @@ export function removeAgentFromStore(store, deleteClusterAgent, query, variables
draftData.project.clusterAgents.nodes = draftData.project.clusterAgents.nodes.filter(
({ id }) => id !== deleteClusterAgent.id,
);
- draftData.project.clusterAgents.count -= 1;
+ draftData.project.ciAccessAuthorizedAgents.nodes = draftData.project.ciAccessAuthorizedAgents.nodes.filter(
+ ({ agent }) => agent.id !== deleteClusterAgent.id,
+ );
+ draftData.project.userAccessAuthorizedAgents.nodes = draftData.project.userAccessAuthorizedAgents.nodes.filter(
+ ({ agent }) => agent.id !== deleteClusterAgent.id,
+ );
});
store.writeQuery({
diff --git a/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
index 05d2525ab98..31897b50407 100644
--- a/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
+++ b/app/assets/javascripts/clusters_list/graphql/fragments/cluster_agent.fragment.graphql
@@ -2,6 +2,7 @@ fragment ClusterAgentFragment on ClusterAgent {
id
name
webPath
+ createdAt
connections {
nodes {
metadata {
diff --git a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
index 76920a0aef4..2a4f7b42eff 100644
--- a/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
+++ b/app/assets/javascripts/clusters_list/graphql/queries/get_agents.query.graphql
@@ -1,26 +1,28 @@
-#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "../fragments/cluster_agent.fragment.graphql"
-query getAgents(
- $defaultBranchName: String!
- $projectPath: ID!
- $first: Int
- $last: Int
- $afterAgent: String
- $beforeAgent: String
-) {
+query getAgents($defaultBranchName: String!, $projectPath: ID!) {
project(fullPath: $projectPath) {
id
- clusterAgents(first: $first, last: $last, before: $beforeAgent, after: $afterAgent) {
+ clusterAgents {
nodes {
...ClusterAgentFragment
}
+ }
- pageInfo {
- ...PageInfo
+ ciAccessAuthorizedAgents {
+ nodes {
+ agent {
+ ...ClusterAgentFragment
+ }
}
+ }
- count
+ userAccessAuthorizedAgents {
+ nodes {
+ agent {
+ ...ClusterAgentFragment
+ }
+ }
}
repository {
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 0668551902a..3be6562f586 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -577,6 +577,7 @@ export const saveDiffDiscussion = async ({ state, dispatch }, { note, formData }
const postData = getNoteFormData({
commit: state.commit,
note,
+ showWhitespace: state.showWhitespace,
...formData,
});
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 4ca353333b7..1ad8091448c 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -140,6 +140,7 @@ export function getFormData(params) {
linePosition,
positionType,
lineRange,
+ showWhitespace,
} = params;
const position = JSON.stringify({
@@ -156,6 +157,7 @@ export function getFormData(params) {
width: params.width,
height: params.height,
line_range: lineRange,
+ ignore_whitespace_change: !showWhitespace,
});
const postData = {
diff --git a/app/assets/javascripts/security_configuration/components/app.vue b/app/assets/javascripts/security_configuration/components/app.vue
index e7d97989195..d57b3fda342 100644
--- a/app/assets/javascripts/security_configuration/components/app.vue
+++ b/app/assets/javascripts/security_configuration/components/app.vue
@@ -164,7 +164,7 @@ export default {
<gl-tabs
content-class="gl-pt-0"
- data-testid="security-configuration-container"
+ data-qa-selector="security_configuration_container"
sync-active-tab-with-query-params
lazy
>
@@ -196,9 +196,12 @@ export default {
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
- <gl-link data-testid="security-view-history-link" :href="gitlabCiHistoryPath">{{
- $options.i18n.configurationHistory
- }}</gl-link>
+ <gl-link
+ data-testid="security-view-history-link"
+ data-qa-selector="security_configuration_history_link"
+ :href="gitlabCiHistoryPath"
+ >{{ $options.i18n.configurationHistory }}</gl-link
+ >
</p>
</template>
diff --git a/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
index c01df3573c5..315f676e659 100644
--- a/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
+++ b/app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue
@@ -28,7 +28,7 @@ export default {
variant="info"
:primary-button-link="autoDevopsPath"
:primary-button-text="$options.i18n.primaryButtonText"
- data-testid="autodevops-container"
+ data-qa-selector="autodevops_container"
@dismiss="dismissMethod"
>
<gl-sprintf :message="$options.i18n.body">
diff --git a/app/assets/javascripts/security_configuration/components/feature_card.vue b/app/assets/javascripts/security_configuration/components/feature_card.vue
index 467b3e66dd4..d1b705fe2fc 100644
--- a/app/assets/javascripts/security_configuration/components/feature_card.vue
+++ b/app/assets/javascripts/security_configuration/components/feature_card.vue
@@ -122,7 +122,7 @@ export default {
v-if="isNotSastIACTemporaryHack"
:class="statusClasses"
data-testid="feature-status"
- :data-qa-feature="`${feature.type}_${hasEnabledStatus}_status`"
+ :data-qa-selector="`${feature.type}_status`"
>
<feature-card-badge
v-if="hasBadge"
@@ -164,7 +164,7 @@ export default {
:href="feature.configurationPath"
variant="confirm"
:category="configurationButton.category"
- :data-testid="`${feature.type}_enable_button`"
+ :data-qa-selector="`${feature.type}_enable_button`"
class="gl-mt-5"
>
{{ configurationButton.text }}
@@ -176,7 +176,7 @@ export default {
variant="confirm"
:category="manageViaMrButtonCategory"
class="gl-mt-5"
- :data-testid="`${feature.type}_mr_button`"
+ :data-qa-selector="`${feature.type}_mr_button`"
@error="onError"
/>
diff --git a/app/models/diff_note_position.rb b/app/models/diff_note_position.rb
index a25b0def643..5e9f0100e62 100644
--- a/app/models/diff_note_position.rb
+++ b/app/models/diff_note_position.rb
@@ -43,7 +43,6 @@ class DiffNotePosition < ApplicationRecord
def self.position_to_attrs(position)
position_attrs = position.to_h
position_attrs[:diff_content_type] = position_attrs.delete(:position_type)
- position_attrs.delete(:line_range)
- position_attrs
+ position_attrs.except(:line_range, :ignore_whitespace_change)
end
end
diff --git a/app/validators/json_schemas/position.json b/app/validators/json_schemas/position.json
index d2c83be7639..4cbd4196c61 100644
--- a/app/validators/json_schemas/position.json
+++ b/app/validators/json_schemas/position.json
@@ -146,6 +146,12 @@
{ "type": "integer" },
{ "type": "string", "maxLength": 10 }
]
+ },
+ "ignore_whitespace_change": {
+ "oneOf": [
+ { "type": "null" },
+ { "type": "boolean" }
+ ]
}
}
}
diff --git a/app/views/protected_branches/_create_protected_branch.html.haml b/app/views/protected_branches/_create_protected_branch.html.haml
index 799f6aa6031..b4765ab49c2 100644
--- a/app/views/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/protected_branches/_create_protected_branch.html.haml
@@ -3,12 +3,12 @@
= dropdown_tag(_('Select'),
options: { toggle_class: 'js-allowed-to-merge wide',
dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'allowed_to_merge_dropdown_content', dropdown_testid: 'allowed-to-merge-dropdown',
- data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes', qa_selector: 'select_allowed_to_merge_dropdown' }})
+ data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes', qa_selector: 'allowed_to_merge_dropdown' }})
- content_for :push_access_levels do
.push_access_levels-container
= dropdown_tag(_('Select'),
options: { toggle_class: "js-allowed-to-push js-multiselect wide",
dropdown_class: 'dropdown-menu-selectable capitalize-header', dropdown_qa_selector: 'allowed_to_push_dropdown_content' , dropdown_testid: 'allowed-to-push-dropdown',
- data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes', qa_selector: 'select_allowed_to_push_dropdown' }})
+ data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes', qa_selector: 'allowed_to_push_dropdown' }})
= render 'protected_branches/shared/create_protected_branch', protected_branch_entity: protected_branch_entity
diff --git a/config/feature_flags/development/log_response_length.yml b/config/feature_flags/development/log_response_length.yml
index 689a262bec1..83cafe20de9 100644
--- a/config/feature_flags/development/log_response_length.yml
+++ b/config/feature_flags/development/log_response_length.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366854
milestone: '15.3'
type: development
group: group::tenant scale
-default_enabled: false
+default_enabled: true
diff --git a/doc/ci/environments/deployment_approvals.md b/doc/ci/environments/deployment_approvals.md
index 96011d5ddff..87ef9c924ba 100644
--- a/doc/ci/environments/deployment_approvals.md
+++ b/doc/ci/environments/deployment_approvals.md
@@ -119,6 +119,41 @@ NOTE:
To protect, update, or unprotect an environment, you must have at least the
Maintainer role.
+#### Migrate to multiple approval rules
+
+You can migrate a protected environment from unified approval rules to multiple
+approval rules. Unified approval rules allow all entities that can deploy to an
+environment to approve deployment jobs. To migrate to multiple approval rules,
+create a new approval rule for each entity allowed to deploy to the environment.
+
+To migrate with the UI:
+
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Settings > CI/CD**.
+1. Expand **Protected environments**.
+1. From the **Environment** list, select your environment.
+1. For each entity allowed to deploy to the environment:
+ 1. Select **Add approval rules**.
+ 1. In the modal window, select which entity is allowed to approve the
+ deployment job.
+ 1. Enter the number of required approvals.
+ 1. Select **Save**.
+
+Each deployment requires the specified number of approvals from each entity.
+
+For example, the `Production` environment below requires five total approvals,
+and allows deployments from only the group `Very Important Group` and the user
+`Administrator`:
+
+![unified approval rules](img/unified_approval_rules_v16_0.png)
+
+To migrate, create rules for the `Very Important Group` and `Administrator`. To
+preserve the number of required approvals, set the number of required approvals
+for `Very Important Group` to four and `Administrator` to one. The new rules
+require `Administrator` to approve every deployment job in `Production`.
+
+![multiple approval rules](img/multiple_approval_rules_v16_0.png)
+
### Allow self-approval **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/381418) in GitLab 15.8.
diff --git a/doc/ci/environments/img/multiple_approval_rules_v16_0.png b/doc/ci/environments/img/multiple_approval_rules_v16_0.png
new file mode 100644
index 00000000000..2385bea6956
--- /dev/null
+++ b/doc/ci/environments/img/multiple_approval_rules_v16_0.png
Binary files differ
diff --git a/doc/ci/environments/img/unified_approval_rules_v16_0.png b/doc/ci/environments/img/unified_approval_rules_v16_0.png
new file mode 100644
index 00000000000..7f822aedff8
--- /dev/null
+++ b/doc/ci/environments/img/unified_approval_rules_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/drafts.md b/doc/user/project/merge_requests/drafts.md
index 88e5e4a6283..6f309d2db24 100644
--- a/doc/user/project/merge_requests/drafts.md
+++ b/doc/user/project/merge_requests/drafts.md
@@ -12,7 +12,7 @@ or open threads, you can prevent it from being accepted before you
[mark it as ready](#mark-merge-requests-as-ready). Flag it as a draft to disable
the **Merge** button until you remove the **Draft** flag:
-![Blocked Merge Button](img/draft_blocked_merge_button_v13_10.png)
+![Blocked Merge Button](img/merge_request_draft_blocked_v16_0.png)
## Mark merge requests as drafts
@@ -42,10 +42,7 @@ When a merge request is ready to be merged, you can remove the `Draft` flag in s
- **Viewing a merge request**: In the upper-right corner of the merge request, select **Mark as ready**.
Users with at least the Developer role
- can also scroll to the bottom of the merge request description and select **Mark as ready**:
-
- ![Mark as ready](img/draft_blocked_merge_button_v13_10.png)
-
+ can also scroll to the bottom of the merge request description and select **Mark as ready**.
- **Editing an existing merge request**: Remove `[Draft]`, `Draft:` or `(Draft)`
from the beginning of the title, or clear **Mark as draft**
below the **Title** field.
@@ -71,7 +68,7 @@ draft merge requests:
1. Select **Yes** to include drafts, or **No** to exclude, and press **Return**
to update the list of merge requests:
- ![Filter draft merge requests](img/filter_draft_merge_requests_v13_10.png)
+ ![Filter draft merge requests](img/filter_draft_merge_requests_v16_0.png)
## Pipelines for drafts
diff --git a/doc/user/project/merge_requests/img/auto_merge_ready_v16_0.png b/doc/user/project/merge_requests/img/auto_merge_ready_v16_0.png
new file mode 100644
index 00000000000..e68057e73a0
--- /dev/null
+++ b/doc/user/project/merge_requests/img/auto_merge_ready_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_10.png b/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_10.png
deleted file mode 100644
index 3bac9f7fee8..00000000000
--- a/doc/user/project/merge_requests/img/draft_blocked_merge_button_v13_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_draft_merge_requests_v13_10.png b/doc/user/project/merge_requests/img/filter_draft_merge_requests_v13_10.png
deleted file mode 100644
index 4458df987d6..00000000000
--- a/doc/user/project/merge_requests/img/filter_draft_merge_requests_v13_10.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_draft_merge_requests_v16_0.png b/doc/user/project/merge_requests/img/filter_draft_merge_requests_v16_0.png
new file mode 100644
index 00000000000..f4356aade16
--- /dev/null
+++ b/doc/user/project/merge_requests/img/filter_draft_merge_requests_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filtering_merge_requests_by_date_v14_6.png b/doc/user/project/merge_requests/img/filtering_merge_requests_by_date_v14_6.png
deleted file mode 100644
index 398820f7864..00000000000
--- a/doc/user/project/merge_requests/img/filtering_merge_requests_by_date_v14_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filtering_merge_requests_by_environment_v14_6.png b/doc/user/project/merge_requests/img/filtering_merge_requests_by_environment_v14_6.png
deleted file mode 100644
index c35f2c8a58b..00000000000
--- a/doc/user/project/merge_requests/img/filtering_merge_requests_by_environment_v14_6.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_request_assignees_v16_0.png b/doc/user/project/merge_requests/img/merge_request_assignees_v16_0.png
new file mode 100644
index 00000000000..114ddf612e0
--- /dev/null
+++ b/doc/user/project/merge_requests/img/merge_request_assignees_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_request_draft_blocked_v16_0.png b/doc/user/project/merge_requests/img/merge_request_draft_blocked_v16_0.png
new file mode 100644
index 00000000000..88fe1ec34c0
--- /dev/null
+++ b/doc/user/project/merge_requests/img/merge_request_draft_blocked_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/merge_request_pipeline.png b/doc/user/project/merge_requests/img/merge_request_pipeline.png
deleted file mode 100644
index ce1d6bab536..00000000000
--- a/doc/user/project/merge_requests/img/merge_request_pipeline.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
deleted file mode 100644
index dde2680ed74..00000000000
--- a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/mwps_v15_4.png b/doc/user/project/merge_requests/img/mwps_v15_4.png
deleted file mode 100644
index f042912d470..00000000000
--- a/doc/user/project/merge_requests/img/mwps_v15_4.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/post_merge_pipeline_v16_0.png b/doc/user/project/merge_requests/img/post_merge_pipeline_v16_0.png
new file mode 100644
index 00000000000..b14ce645850
--- /dev/null
+++ b/doc/user/project/merge_requests/img/post_merge_pipeline_v16_0.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index b7467843e1d..4090077ed4a 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -93,6 +93,8 @@ or:
To filter the list of merge requests:
+1. On the top bar, select **Main menu > Projects** and find your project.
+1. On the left sidebar, select **Merge requests**.
1. Above the list of merge requests, select **Search or filter results...**.
1. From the dropdown list, select the attribute you wish to filter by. Some examples:
- [**By environment or deployment date**](#by-environment-or-deployment-date).
@@ -132,17 +134,15 @@ Projects using a [fast-forward merge method](methods/index.md#fast-forward-merge
do not return results, as this method does not create a merge commit.
When filtering by an environment, a dropdown list presents all environments that
-you can choose from:
-
-![Filter MRs by their environment](img/filtering_merge_requests_by_environment_v14_6.png)
+you can choose from.
-When filtering by `Deployed-before` or `Deployed-after`, the date refers to when
-the deployment to an environment (triggered by the merge commit) completed successfully.
-You must enter the deploy date manually. Deploy dates
-use the format `YYYY-MM-DD`, and must be quoted if you wish to specify
-both a date and time (`"YYYY-MM-DD HH:MM"`):
+When filtering by `Deployed-before` or `Deployed-after`:
-![Filter MRs by a deploy date](img/filtering_merge_requests_by_date_v14_6.png)
+- The date refers to when the deployment to an environment (triggered by the
+ merge commit) completed successfully.
+- You must enter the deploy date manually.
+- Deploy dates use the format `YYYY-MM-DD`, and must be wrapped in double quotes (`"`)
+ if you want to specify both a date and time (`"YYYY-MM-DD HH:MM"`).
## Add changes to a merge request
@@ -182,7 +182,7 @@ The merge request is added to the user's assigned merge request list.
GitLab enables multiple assignees for merge requests, if multiple people are
accountable for it:
-![multiple assignees for merge requests sidebar](img/multiple_assignees_for_merge_requests_sidebar.png)
+![multiple assignees for merge requests sidebar](img/merge_request_assignees_v16_0.png)
To assign multiple assignees to a merge request, use the `/assign @user`
[quick action](../quick_actions.md#issues-merge-requests-and-epics) in a text area, or:
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index 6678bdd2ad0..19416972ae6 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -13,7 +13,7 @@ If you review a merge request and it's ready to merge, but the pipeline hasn't
completed yet, you can set it to auto-merge. You don't
have to remember later to merge the work manually:
-![Auto-merge a merge request](img/mwps_v15_4.png)
+![Auto-merge is ready](img/auto_merge_ready_v16_0.png)
NOTE:
[In GitLab 16.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/359057), **Merge when pipeline succeeds** and **Add to merge train when pipeline succeeds** are renamed **Set to auto-merge**.
diff --git a/doc/user/project/merge_requests/widgets.md b/doc/user/project/merge_requests/widgets.md
index 6663e298a97..2c87aa4e1dd 100644
--- a/doc/user/project/merge_requests/widgets.md
+++ b/doc/user/project/merge_requests/widgets.md
@@ -41,7 +41,7 @@ for environments. If it's the first time the branch is deployed, the link
returns a `404` error until done. During the deployment, the stop button is
disabled. If the pipeline fails to deploy, the deployment information is hidden.
-![Merge request pipeline](img/merge_request_pipeline.png)
+![Merge request pipeline](img/post_merge_pipeline_v16_0.png)
For more information, [read about pipelines](../../../ci/pipelines/index.md).
diff --git a/lib/api/entities/draft_note.rb b/lib/api/entities/draft_note.rb
index 70b32bac502..13852513615 100644
--- a/lib/api/entities/draft_note.rb
+++ b/lib/api/entities/draft_note.rb
@@ -38,7 +38,7 @@ module API
}
}
} do |note|
- note.position.to_h
+ note.position.to_h.except(:ignore_whitespace_change)
end
end
end
diff --git a/lib/api/entities/note.rb b/lib/api/entities/note.rb
index cac4a8280e3..6ed5ca43fbb 100644
--- a/lib/api/entities/note.rb
+++ b/lib/api/entities/note.rb
@@ -18,7 +18,7 @@ module API
expose :commit_id, if: ->(note, options) { note.noteable_type == "MergeRequest" && note.is_a?(DiffNote) }
expose :position, if: ->(note, options) { note.is_a?(DiffNote) } do |note|
- note.position.to_h
+ note.position.to_h.except(:ignore_whitespace_change)
end
expose :resolvable?, as: :resolvable
diff --git a/lib/api/npm_group_packages.rb b/lib/api/npm_group_packages.rb
index 7e6da6b4b02..3a121677bcb 100644
--- a/lib/api/npm_group_packages.rb
+++ b/lib/api/npm_group_packages.rb
@@ -22,33 +22,6 @@ module API
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
namespace ':id/-/packages/npm' do
- params do
- requires :package_name, type: String, desc: 'Package name'
- end
- namespace '-/package/*package_name' do
- get 'dist-tags', format: false do
- not_found!
- end
-
- namespace 'dist-tags/:tag' do
- put format: false do
- not_found!
- end
-
- delete format: false do
- not_found!
- end
- end
- end
-
- post '-/npm/v1/security/audits/quick' do
- not_found!
- end
-
- post '-/npm/v1/security/advisories/bulk' do
- not_found!
- end
-
include ::API::Concerns::Packages::NpmEndpoints
end
end
diff --git a/lib/gitlab/database/tables_truncate.rb b/lib/gitlab/database/tables_truncate.rb
index a6430d1758b..7ae1981fa2b 100644
--- a/lib/gitlab/database/tables_truncate.rb
+++ b/lib/gitlab/database/tables_truncate.rb
@@ -3,7 +3,7 @@
module Gitlab
module Database
class TablesTruncate
- GITLAB_SCHEMAS_TO_IGNORE = %i[gitlab_geo].freeze
+ GITLAB_SCHEMAS_TO_IGNORE = %i[gitlab_geo gitlab_embedding].freeze
def initialize(database_name:, min_batch_size:, logger: nil, until_table: nil, dry_run: false)
@database_name = database_name
diff --git a/lib/gitlab/diff/formatters/base_formatter.rb b/lib/gitlab/diff/formatters/base_formatter.rb
index 19fc028594c..807ce3682ab 100644
--- a/lib/gitlab/diff/formatters/base_formatter.rb
+++ b/lib/gitlab/diff/formatters/base_formatter.rb
@@ -9,6 +9,7 @@ module Gitlab
attr_reader :base_sha
attr_reader :start_sha
attr_reader :head_sha
+ attr_reader :ignore_whitespace_change
def initialize(attrs)
if diff_file = attrs[:diff_file]
diff --git a/lib/gitlab/diff/formatters/image_formatter.rb b/lib/gitlab/diff/formatters/image_formatter.rb
index d0c13dee1aa..f0d25885387 100644
--- a/lib/gitlab/diff/formatters/image_formatter.rb
+++ b/lib/gitlab/diff/formatters/image_formatter.rb
@@ -14,6 +14,7 @@ module Gitlab
@y = attrs[:y]
@width = attrs[:width]
@height = attrs[:height]
+ @ignore_whitespace_change = false
super(attrs)
end
diff --git a/lib/gitlab/diff/formatters/text_formatter.rb b/lib/gitlab/diff/formatters/text_formatter.rb
index 9ea9bdfdf15..bd083720165 100644
--- a/lib/gitlab/diff/formatters/text_formatter.rb
+++ b/lib/gitlab/diff/formatters/text_formatter.rb
@@ -12,6 +12,7 @@ module Gitlab
@old_line = attrs[:old_line]
@new_line = attrs[:new_line]
@line_range = attrs[:line_range]
+ @ignore_whitespace_change = !!attrs[:ignore_whitespace_change]
super(attrs)
end
@@ -25,7 +26,8 @@ module Gitlab
end
def to_h
- super.merge(old_line: old_line, new_line: new_line, line_range: line_range)
+ super.merge(old_line: old_line, new_line: new_line, line_range: line_range,
+ ignore_whitespace_change: ignore_whitespace_change)
end
def line_age
diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb
index 40b6ae2f14e..cbb7231261b 100644
--- a/lib/gitlab/diff/position.rb
+++ b/lib/gitlab/diff/position.rb
@@ -19,7 +19,8 @@ module Gitlab
:x,
:y,
:line_range,
- :position_type, to: :formatter
+ :position_type,
+ :ignore_whitespace_change, to: :formatter
# A position can belong to a text line or to an image coordinate
# it depends of the position_type argument.
@@ -69,11 +70,11 @@ module Gitlab
end
def to_json(opts = nil)
- Gitlab::Json.generate(formatter.to_h, opts)
+ Gitlab::Json.generate(to_h.except(:ignore_whitespace_change), opts)
end
def as_json(opts = nil)
- to_h.as_json(opts)
+ to_h.except(:ignore_whitespace_change).as_json(opts)
end
def type
@@ -134,7 +135,7 @@ module Gitlab
end
def diff_options
- { paths: paths, expanded: true, include_stats: false }
+ { paths: paths, expanded: true, include_stats: false, ignore_whitespace_change: ignore_whitespace_change }
end
def diff_line(repository)
diff --git a/lib/gitlab/diff/position_tracer.rb b/lib/gitlab/diff/position_tracer.rb
index 1c21c35fa60..faa8a46e6ed 100644
--- a/lib/gitlab/diff/position_tracer.rb
+++ b/lib/gitlab/diff/position_tracer.rb
@@ -21,6 +21,7 @@ module Gitlab
return unless old_diff_refs&.complete? && new_diff_refs&.complete?
return unless old_position.diff_refs == old_diff_refs
+ @ignore_whitespace_change = old_position.ignore_whitespace_change
strategy = old_position.on_text? ? LineStrategy : ImageStrategy
strategy.new(self).trace(old_position)
@@ -50,7 +51,7 @@ module Gitlab
def compare(start_sha, head_sha, straight: false)
compare = CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
- compare.diffs(paths: paths, expanded: true)
+ compare.diffs(paths: paths, expanded: true, ignore_whitespace_change: @ignore_whitespace_change)
end
end
end
diff --git a/lib/gitlab/diff/position_tracer/line_strategy.rb b/lib/gitlab/diff/position_tracer/line_strategy.rb
index d7a7e3f5425..0de9aa22008 100644
--- a/lib/gitlab/diff/position_tracer/line_strategy.rb
+++ b/lib/gitlab/diff/position_tracer/line_strategy.rb
@@ -62,6 +62,8 @@ module Gitlab
# The line number as of D can be found by using the LineMapper on diff C->D
# and providing the line number as of C.
+ @ignore_whitespace_change = position.ignore_whitespace_change
+
if position.added?
trace_added_line(position)
elsif position.removed?
@@ -189,7 +191,8 @@ module Gitlab
diff_file: diff_file,
old_line: old_line,
new_line: new_line,
- line_range: line_range
+ line_range: line_range,
+ ignore_whitespace_change: @ignore_whitespace_change
}.compact
Position.new(**params)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 425a37d0fbd..fc0840bfc30 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1853,9 +1853,6 @@ msgstr ""
msgid "AI actions"
msgstr ""
-msgid "AI generated this test"
-msgstr ""
-
msgid "AI-generated test file"
msgstr ""
@@ -10236,6 +10233,9 @@ msgstr ""
msgid "ClusterAgents|Event occurred"
msgstr ""
+msgid "ClusterAgents|External project"
+msgstr ""
+
msgid "ClusterAgents|Failed to create a token"
msgstr ""
@@ -10406,6 +10406,9 @@ msgstr ""
msgid "ClusterAgents|Your instance doesn't have the %{linkStart}GitLab Agent Server (KAS)%{linkEnd} set up. Ask a GitLab Administrator to install it."
msgstr ""
+msgid "ClusterAgents|shared"
+msgstr ""
+
msgid "ClusterAgent|User has insufficient permissions to create a token for this project"
msgstr ""
@@ -29311,36 +29314,54 @@ msgstr ""
msgid "NamespaceLimits|You must select a namespace and add a reason for excluding it"
msgstr ""
-msgid "NamespaceStorageSize|%{namespace_name} contains %{locked_project_count} locked project"
-msgid_plural "NamespaceStorageSize|%{namespace_name} contains %{locked_project_count} locked projects"
-msgstr[0] ""
-msgstr[1] ""
+msgid "NamespaceStorageSize|%{namespace_name} is now read-only. Your ability to write new data to this namespace is restricted. %{read_only_link_start}Which actions are restricted?%{link_end}"
+msgstr ""
-msgid "NamespaceStorageSize|%{namespace_name} is now read-only. Projects under this namespace are locked and actions are restricted. %{actions_restricted_link}"
+msgid "NamespaceStorageSize|For more information about storage limits, see our %{faq_link_start}FAQ%{link_end}."
msgstr ""
-msgid "NamespaceStorageSize|If %{namespace_name} exceeds the storage quota, all projects in the namespace will be locked and actions will be restricted. %{actions_restricted_link}"
+msgid "NamespaceStorageSize|If %{namespace_name} exceeds the storage quota, your ability to write new data to this namespace will be restricted. %{read_only_link_start}Which actions become restricted?%{link_end}"
msgstr ""
-msgid "NamespaceStorageSize|If you reach 100%% storage capacity, you will not be able to: %{repository_limits_description}"
+msgid "NamespaceStorageSize|If a project reaches 100%% of the storage quota (%{free_size_limit}) the project will be in a read-only state, and you won't be able to push to your repository or add large files."
msgstr ""
-msgid "NamespaceStorageSize|Manage your storage usage or, if you are a namespace Owner, purchase additional storage. %{learn_more_link}."
+msgid "NamespaceStorageSize|To prevent your projects from being in a read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
-msgid "NamespaceStorageSize|Please purchase additional storage to unlock your projects over the free %{free_size_limit} project limit. You can't %{repository_limits_description}"
+msgid "NamespaceStorageSize|To prevent your projects from being in a read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
-msgid "NamespaceStorageSize|You have consumed all of your additional storage, please purchase more to unlock your projects over the free %{free_size_limit} limit. You can't %{repository_limits_description}"
+msgid "NamespaceStorageSize|To reduce storage usage, reduce git repository and git LFS storage."
msgstr ""
-msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on one or more projects"
+msgid "NamespaceStorageSize|To remove the read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or %{purchase_more_link_start}purchase more storage%{link_end}."
msgstr ""
-msgid "NamespaceStorageSize|You have used %{usage_in_percent} of the storage quota for %{namespace_name} (%{used_storage} of %{storage_limit})"
+msgid "NamespaceStorageSize|To remove the read-only state %{manage_storage_link_start}manage your storage usage%{link_end}, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
+msgstr ""
+
+msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or %{purchase_more_link_start}purchase more storage%{link_end}."
+msgstr ""
+
+msgid "NamespaceStorageSize|To remove the read-only state, reduce git repository and git LFS storage, or contact a user with the %{group_member_link_start}owner role for this namespace%{link_end} and ask them to %{purchase_more_link_start}purchase more storage%{link_end}."
+msgstr ""
+
+msgid "NamespaceStorageSize|You have consumed all available storage and you can't push or add large files to projects over the free tier limit (%{free_size_limit})."
+msgstr ""
+
+msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} for %{namespace_name}"
msgstr ""
-msgid "NamespaceStorageSize|push to your repository, create pipelines, create issues or add comments. To reduce storage capacity, delete unused repositories, artifacts, wikis, issues, and pipelines. %{learn_more_link}."
+msgid "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on %{readonly_project_count} project"
+msgid_plural "NamespaceStorageSize|You have reached the free storage limit of %{free_size_limit} on %{readonly_project_count} projects"
+msgstr[0] ""
+msgstr[1] ""
+
+msgid "NamespaceStorageSize|You have used %{usage_in_percent} of the storage quota for %{namespace_name}"
+msgstr ""
+
+msgid "NamespaceStorageSize|You have used %{usage_in_percent} of the storage quota for %{namespace_name} (%{used_storage} of %{storage_limit})"
msgstr ""
msgid "NamespaceStorage|%{name_with_link} is now read-only. Projects under this namespace are locked and actions are restricted."
@@ -44752,6 +44773,9 @@ msgid_plural "Test coverage: %d hits"
msgstr[0] ""
msgstr[1] ""
+msgid "Test generated by AI"
+msgstr ""
+
msgid "Test settings"
msgstr ""
@@ -50629,12 +50653,6 @@ msgstr ""
msgid "Which API requests are affected?"
msgstr ""
-msgid "Which actions are restricted?"
-msgstr ""
-
-msgid "Which actions become restricted?"
-msgstr ""
-
msgid "While it's rare to have no vulnerabilities, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
msgstr ""
diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb
index 27886934e2e..6bfdf98587b 100644
--- a/qa/qa/page/element.rb
+++ b/qa/qa/page/element.rb
@@ -13,7 +13,9 @@ module QA
@attributes[:pattern] ||= selector
options.each do |option|
- @attributes[:pattern] = option if option.is_a?(String) || option.is_a?(Regexp)
+ if option.is_a?(String) || option.is_a?(Regexp)
+ @attributes[:pattern] = option
+ end
end
end
@@ -26,7 +28,7 @@ module QA
end
def selector_css
- %(#{qa_selector},.#{selector})
+ %Q([data-qa-selector="#{@name}"]#{additional_selectors},.#{selector})
end
def expression
@@ -38,19 +40,14 @@ module QA
end
def matches?(line)
- !!(line =~ /["']#{name}['"]|["']#{name.to_s.tr('_', '-')}['"]|#{expression}/)
+ !!(line =~ /["']#{name}['"]|#{expression}/)
end
private
- def qa_selector
- %([data-testid="#{@name}"]#{additional_selectors},[data-testid="#{@name.to_s.tr('_',
- '-')}"]#{additional_selectors},[data-qa-selector="#{@name}"]#{additional_selectors})
- end
-
def additional_selectors
@attributes.dup.delete_if { |attr| attr == :pattern || attr == :required }.map do |key, value|
- %([data-qa-#{key.to_s.tr('_', '-')}="#{value}"])
+ %Q([data-qa-#{key.to_s.tr('_', '-')}="#{value}"])
end.join
end
end
diff --git a/qa/qa/page/file/edit.rb b/qa/qa/page/file/edit.rb
index ccf163cd1b4..665fef0d794 100644
--- a/qa/qa/page/file/edit.rb
+++ b/qa/qa/page/file/edit.rb
@@ -14,10 +14,8 @@ module QA
end
end
- def wait_for_markdown_preview(component, content)
- return if has_markdown_preview?(component, content)
-
- raise ElementNotFound, %("Couldn't find #{component} element with content '#{content}')
+ def preview
+ click_link('Preview')
end
end
end
diff --git a/qa/qa/page/project/secure/configuration_form.rb b/qa/qa/page/project/secure/configuration_form.rb
index 70eff31bfa9..493ec08d023 100644
--- a/qa/qa/page/project/secure/configuration_form.rb
+++ b/qa/qa/page/project/secure/configuration_form.rb
@@ -9,13 +9,15 @@ module QA
view 'app/assets/javascripts/security_configuration/components/app.vue' do
element :security_configuration_container
- element :security_view_history_link
+ element :security_configuration_history_link
end
view 'app/assets/javascripts/security_configuration/components/feature_card.vue' do
- element :feature_status
+ element :dependency_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
+ element :sast_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
element :sast_enable_button, "`${feature.type}_enable_button`" # rubocop:disable QA/ElementWithPattern
element :dependency_scanning_mr_button, "`${feature.type}_mr_button`" # rubocop:disable QA/ElementWithPattern
+ element :license_scanning_status, "`${feature.type}_status`" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/security_configuration/components/auto_dev_ops_alert.vue' do
@@ -23,15 +25,15 @@ module QA
end
def has_security_configuration_history_link?
- has_element?(:security_view_history_link)
+ has_element?(:security_configuration_history_link)
end
def has_no_security_configuration_history_link?
- has_no_element?(:security_view_history_link)
+ has_no_element?(:security_configuration_history_link)
end
def click_security_configuration_history_link
- click_element(:security_view_history_link)
+ click_element(:security_configuration_history_link)
end
def click_sast_enable_button
@@ -42,20 +44,40 @@ module QA
click_element(:dependency_scanning_mr_button)
end
- def has_true_sast_status?
- has_element?(:feature_status, feature: 'sast_true_status')
+ def has_sast_status?(status_text)
+ within_element(:sast_status) do
+ has_text?(status_text)
+ end
+ end
+
+ def has_no_sast_status?(status_text)
+ within_element(:sast_status) do
+ has_no_text?(status_text)
+ end
+ end
+
+ def has_dependency_scanning_status?(status_text)
+ within_element(:dependency_scanning_status) do
+ has_text?(status_text)
+ end
end
- def has_false_sast_status?
- has_element?(:feature_status, feature: 'sast_false_status')
+ def has_no_dependency_scanning_status?(status_text)
+ within_element(:dependency_scanning_status) do
+ has_no_text?(status_text)
+ end
end
- def has_true_dependency_scanning_status?
- has_element?(:feature_status, feature: 'dependency_scanning_true_status')
+ def has_license_compliance_status?(status_text)
+ within_element(:license_scanning_status) do
+ has_text?(status_text)
+ end
end
- def has_false_dependency_scanning_status?
- has_element?(:feature_status, feature: 'dependency_scanning_false_status')
+ def has_no_license_compliance_status?(status_text)
+ within_element(:license_scanning_status) do
+ has_no_text?(status_text)
+ end
end
def has_auto_devops_container?
diff --git a/qa/qa/page/project/settings/protected_branches.rb b/qa/qa/page/project/settings/protected_branches.rb
index e6b13ed77a0..3eddd0fd33a 100644
--- a/qa/qa/page/project/settings/protected_branches.rb
+++ b/qa/qa/page/project/settings/protected_branches.rb
@@ -11,9 +11,9 @@ module QA
end
view 'app/views/protected_branches/_create_protected_branch.html.haml' do
- element :select_allowed_to_push_dropdown
+ element :allowed_to_push_dropdown
element :allowed_to_push_dropdown_content
- element :select_allowed_to_merge_dropdown
+ element :allowed_to_merge_dropdown
element :allowed_to_merge_dropdown_content
end
@@ -45,7 +45,7 @@ module QA
private
def select_allowed(action, allowed)
- click_element :"select_allowed_to_#{action}_dropdown"
+ click_element :"allowed_to_#{action}_dropdown"
allowed[:roles] = Resource::ProtectedBranch::Roles::NO_ONE unless allowed.key?(:roles)
diff --git a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
index 435b9321601..ffe340eb0dd 100644
--- a/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/source_editor/source_editor_toolbar_spec.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
-# tagged transient due to feature-flag caching flakiness. Remove tag along with feature flag removal.
+
module QA
- RSpec.describe 'Create', feature_flag: { name: 'source_editor_toolbar', scope: :global } do
+ RSpec.describe 'Create' do
describe 'Source editor toolbar preview', product_group: :source_code do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
@@ -13,14 +13,9 @@ module QA
let(:edited_readme_content) { 'Here is the edited content.' }
before do
- Runtime::Feature.enable(:source_editor_toolbar)
Flow::Login.sign_in
end
- after do
- Runtime::Feature.disable(:source_editor_toolbar)
- end
-
it 'can preview markdown side-by-side while editing',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/367749' do
project.visit!
@@ -30,16 +25,11 @@ module QA
Page::File::Show.perform(&:click_edit)
- # wait_until required due to feature_caching. Remove along with feature flag removal.
Page::File::Edit.perform do |file|
- Support::Waiter.wait_until(sleep_interval: 2, max_duration: 60, reload_page: page,
- retry_on_exception: true) do
- expect(file).to have_element(:editor_toolbar_button)
- end
file.remove_content
- file.click_editor_toolbar
file.add_content('# ' + edited_readme_content)
- file.wait_for_markdown_preview('h1', edited_readme_content)
+ file.preview
+ expect(file.has_markdown_preview?('h1', edited_readme_content)).to be true
file.commit_changes
end
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index da1fd224564..fbf58b5e18a 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe QA::Page::Element do
subject { described_class.new(:something, /link_to 'something'/) }
it 'has an attribute[pattern] of the pattern' do
- expect(subject.attributes[:pattern]).to eq(/link_to 'something'/)
+ expect(subject.attributes[:pattern]).to eq /link_to 'something'/
end
it 'is not required by default' do
@@ -98,7 +98,7 @@ RSpec.describe QA::Page::Element do
subject { described_class.new(:something, /link_to 'something_else_entirely'/, required: true) }
it 'has an attribute[pattern] of the passed pattern' do
- expect(subject.attributes[:pattern]).to eq(/link_to 'something_else_entirely'/)
+ expect(subject.attributes[:pattern]).to eq /link_to 'something_else_entirely'/
end
it 'is required' do
@@ -118,10 +118,6 @@ RSpec.describe QA::Page::Element do
expect(subject.selector_css).to include(%q([data-qa-selector="my_element"]))
end
- it 'properly translates to a data-testid' do
- expect(subject.selector_css).to include(%q([data-testid="my_element"]))
- end
-
context 'additional selectors' do
let(:element) { described_class.new(:my_element, index: 3, another_match: 'something') }
let(:required_element) { described_class.new(:my_element, required: true, index: 3) }
diff --git a/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb b/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb
new file mode 100644
index 00000000000..08976d3d2e2
--- /dev/null
+++ b/spec/features/merge_request/user_comments_on_whitespace_hidden_diff_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'User comments on a diff with whitespace changes', :js, feature_category: :code_review_workflow do
+ include MergeRequestDiffHelpers
+
+ let_it_be(:project) { create(:project, :repository) }
+ let(:merge_request) do
+ create(:merge_request_with_diffs, source_project: project, target_project: project,
+ source_branch: 'changes-with-whitespace')
+ end
+
+ let(:user) { create(:user) }
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+
+ visit(diffs_project_merge_request_path(project, merge_request, view: 'parallel'))
+ end
+
+ context 'when hiding whitespace changes' do
+ before do
+ find('.js-show-diff-settings').click
+ find('[data-testid="show-whitespace"]').click
+ wait_for_requests
+ end
+
+ it 'allows commenting on line combinations that are not present in the real diff' do
+ # Comment on line combination old: 19, new 20
+ # This line combination does not exist when whitespace is shown
+ click_diff_line(
+ find_by_scrolling('div[data-path="files/ruby/popen.rb"] .left-side a[data-linenumber="19"]').find(:xpath,
+ '../..'), 'left')
+
+ page.within('.js-discussion-note-form') do
+ fill_in(:note_note, with: 'Comment on diff with whitespace')
+ click_button('Add comment now')
+ end
+
+ wait_for_requests
+
+ page.within('.notes_holder') do
+ expect(page).to have_content('Comment on diff with whitespace')
+ end
+ end
+ end
+end
diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js
index 0f68a69458e..71a56eba22a 100644
--- a/spec/frontend/clusters_list/components/agent_table_spec.js
+++ b/spec/frontend/clusters_list/components/agent_table_spec.js
@@ -1,4 +1,4 @@
-import { GlLink, GlIcon } from '@gitlab/ui';
+import { GlLink, GlIcon, GlBadge, GlTable, GlPagination } from '@gitlab/ui';
import { sprintf } from '~/locale';
import AgentTable from '~/clusters_list/components/agent_table.vue';
import DeleteAgentButton from '~/clusters_list/components/delete_agent_button.vue';
@@ -17,6 +17,7 @@ const provideData = {
};
const defaultProps = {
agents: clusterAgents,
+ maxAgents: null,
};
const DeleteAgentButtonStub = stubComponent(DeleteAgentButton, {
@@ -39,7 +40,11 @@ describe('AgentTable', () => {
const findAgentId = (at) => wrapper.findAllByTestId('cluster-agent-id').at(at);
const findConfiguration = (at) =>
wrapper.findAllByTestId('cluster-agent-configuration-link').at(at);
- const findDeleteAgentButton = () => wrapper.findAllComponents(DeleteAgentButton);
+ const findDeleteAgentButtons = () => wrapper.findAllComponents(DeleteAgentButton);
+ const findTableRow = (at) => wrapper.findComponent(GlTable).find('tbody').findAll('tr').at(at);
+ const findSharedBadgeByRow = (at) => findTableRow(at).findComponent(GlBadge);
+ const findDeleteAgentButtonByRow = (at) => findTableRow(at).findComponent(DeleteAgentButton);
+ const findPagination = () => wrapper.findComponent(GlPagination);
const createWrapper = ({ provide = provideData, propsData = defaultProps } = {}) => {
wrapper = mountExtended(AgentTable, {
@@ -64,6 +69,11 @@ describe('AgentTable', () => {
`('displays agent link for $agentName', ({ agentName, link, lineNumber }) => {
expect(findAgentLink(lineNumber).text()).toBe(agentName);
expect(findAgentLink(lineNumber).attributes('href')).toBe(link);
+ expect(findSharedBadgeByRow(lineNumber).exists()).toBe(false);
+ });
+
+ it('displays "shared" badge if the agent is shared', () => {
+ expect(findSharedBadgeByRow(9).text()).toBe(I18N_AGENT_TABLE.sharedBadgeText);
});
it.each`
@@ -116,8 +126,9 @@ describe('AgentTable', () => {
},
);
- it('displays actions menu for each agent', () => {
- expect(findDeleteAgentButton()).toHaveLength(clusterAgents.length);
+ it('displays actions menu for each agent except the shared agents', () => {
+ expect(findDeleteAgentButtons()).toHaveLength(clusterAgents.length - 1);
+ expect(findDeleteAgentButtonByRow(9).exists()).toBe(false);
});
});
@@ -132,6 +143,7 @@ describe('AgentTable', () => {
${6} | ${'14.8.0'} | ${'15.0.0'} | ${false} | ${true} | ${outdatedTitle}
${7} | ${'14.8.0'} | ${'15.0.0-rc1'} | ${false} | ${true} | ${outdatedTitle}
${8} | ${'14.8.0'} | ${'14.8.10'} | ${false} | ${false} | ${''}
+ ${9} | ${''} | ${'14.8.0'} | ${false} | ${false} | ${''}
`(
'when agent version is "$agentVersion", KAS version is "$kasVersion" and version mismatch is "$versionMismatch"',
({ agentMockIdx, agentVersion, kasVersion, versionMismatch, versionOutdated, title }) => {
@@ -181,5 +193,32 @@ describe('AgentTable', () => {
}
},
);
+
+ describe('pagination', () => {
+ it('should not render pagination buttons when there are no additional pages', () => {
+ createWrapper();
+
+ expect(findPagination().exists()).toBe(false);
+ });
+
+ it('should render pagination buttons when there are additional pages', () => {
+ createWrapper({
+ propsData: { agents: [...clusterAgents, ...clusterAgents, ...clusterAgents] },
+ });
+
+ expect(findPagination().exists()).toBe(true);
+ });
+
+ it('should not render pagination buttons when maxAgents is passed from the parent component', () => {
+ createWrapper({
+ propsData: {
+ agents: [...clusterAgents, ...clusterAgents, ...clusterAgents],
+ maxAgents: 6,
+ },
+ });
+
+ expect(findPagination().exists()).toBe(false);
+ });
+ });
});
});
diff --git a/spec/frontend/clusters_list/components/agents_spec.js b/spec/frontend/clusters_list/components/agents_spec.js
index d91245ba9b4..d6ede01fac4 100644
--- a/spec/frontend/clusters_list/components/agents_spec.js
+++ b/spec/frontend/clusters_list/components/agents_spec.js
@@ -1,4 +1,4 @@
-import { GlAlert, GlKeysetPagination, GlLoadingIcon, GlBanner } from '@gitlab/ui';
+import { GlAlert, GlLoadingIcon, GlBanner } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import Vue, { nextTick } from 'vue';
@@ -19,6 +19,7 @@ Vue.use(VueApollo);
describe('Agents', () => {
let wrapper;
+ let testDate = new Date();
const defaultProps = {
defaultBranchName: 'default',
@@ -31,9 +32,9 @@ describe('Agents', () => {
props = {},
glFeatures = {},
agents = [],
- pageInfo = null,
+ ciAccessAuthorizedAgentsNodes = [],
+ userAccessAuthorizedAgentsNodes = [],
trees = [],
- count = 0,
queryResponse = null,
}) => {
const provide = provideData;
@@ -43,12 +44,16 @@ describe('Agents', () => {
id: '1',
clusterAgents: {
nodes: agents,
- pageInfo,
connections: { nodes: [] },
tokens: { nodes: [] },
- count,
},
- repository: { tree: { trees: { nodes: trees, pageInfo } } },
+ ciAccessAuthorizedAgents: {
+ nodes: ciAccessAuthorizedAgentsNodes,
+ },
+ userAccessAuthorizedAgents: {
+ nodes: userAccessAuthorizedAgentsNodes,
+ },
+ repository: { tree: { trees: { nodes: trees } } },
},
},
};
@@ -78,7 +83,6 @@ describe('Agents', () => {
const findAgentTable = () => wrapper.findComponent(AgentTable);
const findEmptyState = () => wrapper.findComponent(AgentEmptyState);
- const findPaginationButtons = () => wrapper.findComponent(GlKeysetPagination);
const findAlert = () => wrapper.findComponent(GlAlert);
const findBanner = () => wrapper.findComponent(GlBanner);
@@ -87,13 +91,13 @@ describe('Agents', () => {
});
describe('when there is a list of agents', () => {
- let testDate = new Date();
const agents = [
{
__typename: 'ClusterAgent',
id: '1',
name: 'agent-1',
webPath: '/agent-1',
+ createdAt: testDate,
connections: null,
tokens: null,
},
@@ -102,6 +106,7 @@ describe('Agents', () => {
id: '2',
name: 'agent-2',
webPath: '/agent-2',
+ createdAt: testDate,
connections: null,
tokens: {
nodes: [
@@ -113,8 +118,26 @@ describe('Agents', () => {
},
},
];
-
- const count = 2;
+ const ciAccessAuthorizedAgentsNodes = [
+ {
+ agent: {
+ __typename: 'ClusterAgent',
+ id: '3',
+ name: 'ci-agent-1',
+ webPath: 'shared-project/agent-1',
+ createdAt: testDate,
+ connections: null,
+ tokens: null,
+ },
+ },
+ ];
+ const userAccessAuthorizedAgentsNodes = [
+ {
+ agent: {
+ ...agents[0],
+ },
+ },
+ ];
const trees = [
{
@@ -156,10 +179,26 @@ describe('Agents', () => {
],
},
},
+ {
+ id: '3',
+ name: 'ci-agent-1',
+ configFolder: undefined,
+ webPath: 'shared-project/agent-1',
+ status: 'unused',
+ isShared: true,
+ lastContact: null,
+ connections: null,
+ tokens: null,
+ },
];
beforeEach(() => {
- return createWrapper({ agents, count, trees });
+ return createWrapper({
+ agents,
+ ciAccessAuthorizedAgentsNodes,
+ userAccessAuthorizedAgentsNodes,
+ trees,
+ });
});
it('should render agent table', () => {
@@ -172,7 +211,7 @@ describe('Agents', () => {
});
it('should emit agents count to the parent component', () => {
- expect(wrapper.emitted().onAgentsLoad).toEqual([[count]]);
+ expect(wrapper.emitted().onAgentsLoad).toEqual([[expectedAgentsList.length]]);
});
describe.each`
@@ -192,7 +231,7 @@ describe('Agents', () => {
localStorage.setItem(AGENT_FEEDBACK_KEY, true);
}
- return createWrapper({ glFeatures, agents, count, trees });
+ return createWrapper({ glFeatures, agents, trees });
});
it(`should ${bannerShown ? 'show' : 'hide'} the feedback banner`, () => {
@@ -206,7 +245,7 @@ describe('Agents', () => {
showGitlabAgentFeedback: true,
};
beforeEach(() => {
- return createWrapper({ glFeatures, agents, count, trees });
+ return createWrapper({ glFeatures, agents, trees });
});
it('should render the correct title', () => {
@@ -238,51 +277,6 @@ describe('Agents', () => {
expect(findAgentTable().props('agents')).toMatchObject(expectedAgentsList);
});
});
-
- it('should not render pagination buttons when there are no additional pages', () => {
- expect(findPaginationButtons().exists()).toBe(false);
- });
-
- describe('when the list has additional pages', () => {
- const pageInfo = {
- hasNextPage: true,
- hasPreviousPage: false,
- startCursor: 'prev',
- endCursor: 'next',
- };
-
- beforeEach(() => {
- return createWrapper({
- agents,
- pageInfo: {
- ...pageInfo,
- __typename: 'PageInfo',
- },
- });
- });
-
- it('should render pagination buttons', () => {
- expect(findPaginationButtons().exists()).toBe(true);
- });
-
- it('should pass pageInfo to the pagination component', () => {
- expect(findPaginationButtons().props()).toMatchObject(pageInfo);
- });
-
- describe('when limit is passed from the parent component', () => {
- beforeEach(() => {
- return createWrapper({
- props: { limit: 6 },
- agents,
- pageInfo,
- });
- });
-
- it('should not render pagination buttons', () => {
- expect(findPaginationButtons().exists()).toBe(false);
- });
- });
- });
});
describe('when the agent list is empty', () => {
@@ -302,7 +296,10 @@ describe('Agents', () => {
describe('when agents query has errored', () => {
beforeEach(() => {
- return createWrapper({ agents: null });
+ createWrapper({
+ queryResponse: jest.fn().mockRejectedValue({}),
+ });
+ return waitForPromises();
});
it('displays an alert message', () => {
diff --git a/spec/frontend/clusters_list/components/delete_agent_button_spec.js b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
index 2c9a6b11671..8bbb5ec92a7 100644
--- a/spec/frontend/clusters_list/components/delete_agent_button_spec.js
+++ b/spec/frontend/clusters_list/components/delete_agent_button_spec.js
@@ -8,7 +8,7 @@ import deleteAgentMutation from '~/clusters_list/graphql/mutations/delete_agent.
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import DeleteAgentButton from '~/clusters_list/components/delete_agent_button.vue';
-import { MAX_LIST_COUNT, DELETE_AGENT_BUTTON } from '~/clusters_list/constants';
+import { DELETE_AGENT_BUTTON } from '~/clusters_list/constants';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { getAgentResponse, mockDeleteResponse, mockErrorDeleteResponse } from '../mocks/apollo';
@@ -16,7 +16,6 @@ Vue.use(VueApollo);
const projectPath = 'path/to/project';
const defaultBranchName = 'default';
-const maxAgents = MAX_LIST_COUNT;
const agent = {
id: 'agent-id',
name: 'agent-name',
@@ -53,8 +52,6 @@ describe('DeleteAgentButton', () => {
variables: {
projectPath,
defaultBranchName,
- first: maxAgents,
- last: null,
},
data: getAgentResponse.data,
});
@@ -71,7 +68,6 @@ describe('DeleteAgentButton', () => {
};
const propsData = {
defaultBranchName,
- maxAgents,
agent,
};
diff --git a/spec/frontend/clusters_list/components/mock_data.js b/spec/frontend/clusters_list/components/mock_data.js
index af1fb496118..161ea4566e1 100644
--- a/spec/frontend/clusters_list/components/mock_data.js
+++ b/spec/frontend/clusters_list/components/mock_data.js
@@ -205,4 +205,14 @@ export const clusterAgents = [
],
},
},
+ {
+ name: 'ci-agent-1',
+ id: '3',
+ webPath: 'shared-project/agent-1',
+ status: 'inactive',
+ lastContact: connectedTimeInactive.getTime(),
+ isShared: true,
+ connections: null,
+ tokens: null,
+ },
];
diff --git a/spec/frontend/clusters_list/mocks/apollo.js b/spec/frontend/clusters_list/mocks/apollo.js
index 3467b4c665c..c0e25d174ae 100644
--- a/spec/frontend/clusters_list/mocks/apollo.js
+++ b/spec/frontend/clusters_list/mocks/apollo.js
@@ -3,6 +3,7 @@ const agent = {
id: 'agent-id',
name: 'agent-name',
webPath: 'agent-webPath',
+ createdAt: new Date(),
};
const token = {
id: 'token-id',
@@ -14,13 +15,6 @@ const tokens = {
const connections = {
nodes: [],
};
-const pageInfo = {
- endCursor: '',
- hasNextPage: false,
- hasPreviousPage: false,
- startCursor: '',
-};
-const count = 1;
export const createAgentResponse = {
data: {
@@ -73,10 +67,12 @@ export const getAgentResponse = {
project: {
__typename: 'Project',
id: 'project-1',
- clusterAgents: { nodes: [{ ...agent, connections, tokens }], pageInfo, count },
+ clusterAgents: { nodes: [{ ...agent, connections, tokens }] },
+ ciAccessAuthorizedAgents: { nodes: [] },
+ userAccessAuthorizedAgents: { nodes: [] },
repository: {
tree: {
- trees: { nodes: [{ ...agent, path: null }], pageInfo },
+ trees: { nodes: [{ ...agent, path: null }] },
},
},
},
diff --git a/spec/frontend/diffs/store/utils_spec.js b/spec/frontend/diffs/store/utils_spec.js
index 4760a8b7166..bc7c81d2390 100644
--- a/spec/frontend/diffs/store/utils_spec.js
+++ b/spec/frontend/diffs/store/utils_spec.js
@@ -140,6 +140,7 @@ describe('DiffsStoreUtils', () => {
old_line: options.noteTargetLine.old_line,
new_line: options.noteTargetLine.new_line,
line_range: options.lineRange,
+ ignore_whitespace_change: true,
});
const postData = {
@@ -198,6 +199,7 @@ describe('DiffsStoreUtils', () => {
position_type: TEXT_DIFF_POSITION_TYPE,
old_line: options.noteTargetLine.old_line,
new_line: options.noteTargetLine.new_line,
+ ignore_whitespace_change: true,
});
const postData = {
diff --git a/spec/lib/api/entities/draft_note_spec.rb b/spec/lib/api/entities/draft_note_spec.rb
index 59555319bb1..23ea0b9a631 100644
--- a/spec/lib/api/entities/draft_note_spec.rb
+++ b/spec/lib/api/entities/draft_note_spec.rb
@@ -7,12 +7,14 @@ RSpec.describe API::Entities::DraftNote, feature_category: :code_review_workflow
let_it_be(:json) { entity.as_json }
it 'exposes correct attributes' do
+ position = entity.position.to_h.except(:ignore_whitespace_change)
+
expect(json["id"]).to eq entity.id
expect(json["author_id"]).to eq entity.author_id
expect(json["merge_request_id"]).to eq entity.merge_request_id
expect(json["resolve_discussion"]).to eq entity.resolve_discussion
expect(json["discussion_id"]).to eq entity.discussion_id
expect(json["note"]).to eq entity.note
- expect(json["position"].transform_keys(&:to_sym)).to eq entity.position.to_h
+ expect(json["position"].transform_keys(&:to_sym)).to eq position
end
end
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 290585d0991..5270c1777bc 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -10,7 +10,8 @@ RSpec.describe Gitlab::Diff::Formatters::TextFormatter do
head_sha: 789,
old_path: 'old_path.txt',
new_path: 'new_path.txt',
- line_range: nil
+ line_range: nil,
+ ignore_whitespace_change: false
}
end
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 9b0ea892f91..8831cee690d 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Gitlab::Diff::PositionTracer do
let(:project) { double }
let(:old_diff_refs) { diff_refs }
let(:new_diff_refs) { diff_refs }
- let(:position) { double(on_text?: on_text?, diff_refs: diff_refs) }
+ let(:position) { double(on_text?: on_text?, diff_refs: diff_refs, ignore_whitespace_change: false) }
let(:tracer) { double }
context 'position is on text' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 06904849ef5..23f5c61d335 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1454,7 +1454,7 @@ RSpec.describe Gitlab::Git::Repository, feature_category: :source_code_managemen
it "returns the number of commits in the whole repository" do
options = { all: true }
- expect(repository.count_commits(options)).to eq(315)
+ expect(repository.count_commits(options)).to eq(316)
end
end
diff --git a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
index 73ba49bf4ed..0f35c7ee0dc 100644
--- a/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/diff_note_importer_spec.rb
@@ -160,7 +160,8 @@ RSpec.describe Gitlab::GithubImport::Importer::DiffNoteImporter, :aggregate_fail
new_path: file_path,
old_path: file_path,
position_type: 'text',
- line_range: nil
+ line_range: nil,
+ ignore_whitespace_change: false
})
expect(note.note)
.to eq <<~NOTE
diff --git a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
index 3e76b4ae698..8595ea6d495 100644
--- a/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
+++ b/spec/lib/gitlab/github_import/representation/diff_note_spec.rb
@@ -104,7 +104,8 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_red
old_line: nil,
old_path: 'README.md',
position_type: 'text',
- start_sha: 'start'
+ start_sha: 'start',
+ ignore_whitespace_change: false
)
end
end
@@ -122,7 +123,8 @@ RSpec.describe Gitlab::GithubImport::Representation::DiffNote, :clean_gitlab_red
new_line: nil,
old_path: 'README.md',
position_type: 'text',
- start_sha: 'start'
+ start_sha: 'start',
+ ignore_whitespace_change: false
)
end
end
diff --git a/spec/requests/api/npm_group_packages_spec.rb b/spec/requests/api/npm_group_packages_spec.rb
index 888ce548e6d..1a862382d44 100644
--- a/spec/requests/api/npm_group_packages_spec.rb
+++ b/spec/requests/api/npm_group_packages_spec.rb
@@ -153,46 +153,32 @@ RSpec.describe API::NpmGroupPackages, feature_category: :package_registry do
end
describe 'GET /api/v4/packages/npm/-/package/*package_name/dist-tags' do
- let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags") }
-
- subject { get(url) }
-
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'handling get dist tags requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags") }
+ end
end
describe 'PUT /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
- let(:tag_name) { 'test' }
- let(:headers) { build_token_auth_header(personal_access_token.token) }
- let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
-
- subject { put(url, headers: headers) }
-
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'handling create dist tag requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
+ end
end
describe 'DELETE /api/v4/packages/npm/-/package/*package_name/dist-tags/:tag' do
- let(:tag_name) { 'test' }
- let(:headers) { build_token_auth_header(personal_access_token.token) }
- let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
-
- subject { delete(url, headers: headers) }
-
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'handling delete dist tag requests', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/package/#{package_name}/dist-tags/#{tag_name}") }
+ end
end
describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/advisories/bulk' do
- let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/advisories/bulk") }
-
- subject { post(url) }
-
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'handling audit request', path: 'advisories/bulk', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/advisories/bulk") }
+ end
end
describe 'POST /api/v4/groups/:id/-/packages/npm/-/npm/v1/security/audits/quick' do
- let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/audits/quick") }
-
- subject { post(url) }
-
- it_behaves_like 'returning response status', :not_found
+ it_behaves_like 'handling audit request', path: 'audits/quick', scope: :group do
+ let(:url) { api("/groups/#{group.id}/-/packages/npm/-/npm/v1/security/audits/quick") }
+ end
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index ceb567e54c4..1bd935f475d 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -93,7 +93,8 @@ module TestEnv
'gitaly-rename-test' => '94bb47c',
'smime-signed-commits' => 'ed775cc',
'Ääh-test-utf-8' => '7975be0',
- 'ssh-signed-commit' => '7b5160f'
+ 'ssh-signed-commit' => '7b5160f',
+ 'changes-with-whitespace' => 'f2d141fadb33ceaafc95667c1a0a308ad5edc5f9'
}.freeze
# gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
diff --git a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
index 7ace223723c..d4fe45a91a0 100644
--- a/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
+++ b/spec/support/shared_examples/lib/api/ai_workhorse_shared_examples.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-RSpec.shared_examples 'delegates AI request to Workhorse' do |provider_flag|
+RSpec.shared_examples 'behind AI related feature flags' do |provider_flag|
context "when #{provider_flag} is disabled" do
before do
stub_feature_flags(provider_flag => false)
@@ -24,7 +24,9 @@ RSpec.shared_examples 'delegates AI request to Workhorse' do |provider_flag|
expect(response).to have_gitlab_http_status(:not_found)
end
end
+end
+RSpec.shared_examples 'delegates AI request to Workhorse' do
it 'responds with Workhorse send-url headers' do
post api(url, current_user), params: input_params
diff --git a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
index 7f2c445e93d..e6b94f257e4 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions_shared_examples.rb
@@ -18,10 +18,12 @@ RSpec.shared_examples 'diff discussions API' do |parent_type, noteable_type, id_
it "returns a discussion by id" do
get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{diff_note.discussion_id}", user)
+ position = diff_note.position.to_h.except(:ignore_whitespace_change)
+
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq(diff_note.discussion_id)
expect(json_response['notes'].first['body']).to eq(diff_note.note)
- expect(json_response['notes'].first['position']).to eq(diff_note.position.to_h.stringify_keys)
+ expect(json_response['notes'].first['position']).to eq(position.stringify_keys)
expect(json_response['notes'].first['line_range']).to eq(nil)
end
end
@@ -39,7 +41,7 @@ RSpec.shared_examples 'diff discussions API' do |parent_type, noteable_type, id_
}
}
- position = diff_note.position.to_h.merge({ line_range: line_range })
+ position = diff_note.position.to_h.merge({ line_range: line_range }).except(:ignore_whitespace_change)
post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user),
params: { body: 'hi!', position: position }
diff --git a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
index d1712a3c02a..7b9c110536d 100644
--- a/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/npm_packages_shared_examples.rb
@@ -449,7 +449,7 @@ RSpec.shared_examples 'handling audit request' do |path:, scope: :project|
project.send("add_#{user_role}", user) if user_role
project.update!(visibility: visibility.to_s)
- if scope == :instance
+ if %i[instance group].include?(scope)
allow_fetch_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
else
allow_fetch_cascade_application_setting(attribute: "npm_package_requests_forwarding", return_value: request_forward)
@@ -459,7 +459,7 @@ RSpec.shared_examples 'handling audit request' do |path:, scope: :project|
example_name = "#{params[:expected_result]} audit request"
status = params[:expected_status]
- if scope == :instance && params[:expected_status] != :unauthorized
+ if %i[instance group].include?(scope) && params[:expected_status] != :unauthorized
if params[:request_forward]
example_name = 'redirect audit request'
status = :temporary_redirect
@@ -638,6 +638,8 @@ RSpec.shared_examples 'handling get dist tags requests' do |scope: :project|
status = :not_found
end
+ status = :not_found if scope == :group && params[:package_name_type] == :non_existing
+
it_behaves_like example_name, status: status
end
end
@@ -854,6 +856,8 @@ RSpec.shared_examples 'handling different package names, visibilities and user r
status = params[:auth].nil? ? :unauthorized : :not_found
end
+ status = :not_found if scope == :group && params[:package_name_type] == :non_existing && params[:auth].present?
+
it_behaves_like example_name, status: status
end
end