Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-06-18 18:23:10 +00:00
parent 9684836887
commit 181a4e02bb
81 changed files with 1473 additions and 615 deletions

View File

@ -110,9 +110,7 @@ e2e:post-run-e2e-message:
- install_gitlab_gem
script:
- scripts/generate-message-to-run-e2e-pipeline.rb
needs:
- e2e-test-pipeline-generate
- build-assets-image
needs: []
artifacts:
expire_in: 1 day
paths:

View File

@ -1629,12 +1629,6 @@
changes: *code-patterns
- <<: *if-merge-request-and-specific-devops-stage
changes: *code-patterns
- <<: *if-force-ci
when: manual
allow_failure: true
# We used to have a rule at the end here that would catch any remaining code MRs and allow the job to be run
# manually. That rule is now in ".qa:rules:code-merge-request-manual" so it can be included when needed and we can
# still use ".qa:rules:package-and-test-common" in jobs we don't want to be manual.
# Like .qa:rules:package-and-test-common but not allowed to fail.
# It's named `e2e` instead of `package-and-test` because it's used for e2e tests on GDK (and could be used
@ -1705,7 +1699,6 @@
rules:
- !reference [".qa:rules:package-and-test-common", rules]
- !reference [".qa:rules:e2e-schedule-blocking", rules]
- !reference [".qa:rules:code-merge-request-manual", rules]
.qa:rules:package-and-test-ce:
rules:
@ -1770,7 +1763,9 @@
# These provide a manual way to trigger follow-up:e2e:package-and-test-ee
# It is fine if they're overlapping with the automatic ones.
# It'll not hurt and it can simplify the rules, decoupling them.
# If any changes are made to this rule, `.qa:rules:follow-up-e2e` should also be updated.
# If any changes are made to this rule, the following should also be updated:
# 1) .qa:rules:follow-up-e2e
# 2) .qa:rules:post-run-e2e-message
.qa:rules:manual-e2e:
rules:
- !reference [".qa:rules:package-and-test-never-run", rules]
@ -1779,7 +1774,8 @@
- !reference [".qa:rules:code-merge-request-manual", rules]
# These are based on `.qa:rules:manual-e2e` but with manual jobs changed to automatic.
# If any changes are made to this rule, `.qa:rules:manual-e2e` should also be updated.
# If any changes are made to this rule, the following should also be updated:
# 1) .qa:rules:manual-e2e
.qa:rules:follow-up-e2e:
rules:
- !reference [".qa:rules:package-and-test-never-run", rules]
@ -1824,11 +1820,10 @@
when: never
- <<: *if-merge-request-and-specific-devops-stage
when: never
- <<: *if-force-ci
when: never
# From .qa:rules:package-and-test-schedule
- <<: *if-dot-com-gitlab-org-schedule
when: never
# From .qa:rules:manual-e2e
- !reference [".qa:rules:code-merge-request-allowed-to-fail", rules]
.qa:rules:fulfillment-e2e-quarantine-report:

View File

@ -2187,7 +2187,6 @@ Layout/LineLength:
- 'ee/spec/workers/repository_import_worker_spec.rb'
- 'ee/spec/workers/security/orchestration_policy_rule_schedule_namespace_worker_spec.rb'
- 'ee/spec/workers/security/orchestration_policy_rule_schedule_worker_spec.rb'
- 'ee/spec/workers/store_security_reports_worker_spec.rb'
- 'ee/spec/workers/sync_seat_link_request_worker_spec.rb'
- 'ee/spec/workers/update_all_mirrors_worker_spec.rb'
- 'ee/spec/workers/vulnerability_exports/export_deletion_worker_spec.rb'

View File

@ -559,7 +559,7 @@ group :test do
# Moved in `test` because https://gitlab.com/gitlab-org/gitlab/-/issues/217527
gem 'derailed_benchmarks', require: false # rubocop:todo Gemfile/MissingFeatureCategory
gem 'gitlab_quality-test_tooling', '~> 1.28.0', require: false, feature_category: :tooling
gem 'gitlab_quality-test_tooling', '~> 1.29.0', require: false, feature_category: :tooling
end
gem 'octokit', '~> 8.1', feature_category: :importers

View File

@ -229,7 +229,7 @@
{"name":"gitlab-styles","version":"12.0.1","platform":"ruby","checksum":"d8a302b0ab0e1f18e2d11501760f1b85c5e70b5e5ca628828a0786c7984ed133"},
{"name":"gitlab_chronic_duration","version":"0.12.0","platform":"ruby","checksum":"0d766944d415b5c831f176871ee8625783fc0c5bfbef2d79a3a616f207ffc16d"},
{"name":"gitlab_omniauth-ldap","version":"2.2.0","platform":"ruby","checksum":"bb4d20acb3b123ed654a8f6a47d3fac673ece7ed0b6992edb92dca14bad2838c"},
{"name":"gitlab_quality-test_tooling","version":"1.28.0","platform":"ruby","checksum":"d0b2c6bc304b7ccf9003fae851eff330c19ed182079310356a93b30e09fe51f3"},
{"name":"gitlab_quality-test_tooling","version":"1.29.0","platform":"ruby","checksum":"611be320785c9352b8ecdcacaefd271a19e3b76c75f5140ab544085a539eaede"},
{"name":"globalid","version":"1.1.0","platform":"ruby","checksum":"b337e1746f0c8cb0a6c918234b03a1ddeb4966206ce288fbb57779f59b2d154f"},
{"name":"gon","version":"6.4.0","platform":"ruby","checksum":"e3a618d659392890f1aa7db420f17c75fd7d35aeb5f8fe003697d02c4b88d2f0"},
{"name":"google-apis-androidpublisher_v3","version":"0.34.0","platform":"ruby","checksum":"d7e1d7dd92f79c498fe2082222a1740d788e022e660c135564b3fd299cab5425"},

View File

@ -741,7 +741,7 @@ GEM
omniauth (>= 1.3, < 3)
pyu-ruby-sasl (>= 0.0.3.3, < 0.1)
rubyntlm (~> 0.5)
gitlab_quality-test_tooling (1.28.0)
gitlab_quality-test_tooling (1.29.0)
activesupport (>= 7.0, < 7.2)
amatch (~> 0.4.1)
gitlab (~> 4.19)
@ -2022,7 +2022,7 @@ DEPENDENCIES
gitlab-utils!
gitlab_chronic_duration (~> 0.12)
gitlab_omniauth-ldap (~> 2.2.0)
gitlab_quality-test_tooling (~> 1.28.0)
gitlab_quality-test_tooling (~> 1.29.0)
gon (~> 6.4.0)
google-apis-androidpublisher_v3 (~> 0.34.0)
google-apis-cloudbilling_v1 (~> 0.21.0)

View File

@ -139,11 +139,9 @@ export default {
this.$emit('refresh-pipelines-table');
},
onRetryPipeline(pipeline) {
// This emit is only used by the `legacy_pipelines_table_wrapper`.
this.$emit('retry-pipeline', pipeline);
},
onCancelPipeline(pipeline) {
// This emit is only used by the `legacy_pipelines_table_wrapper`.
this.$emit('cancel-pipeline', pipeline);
},
trackPipelineMiniGraph() {

View File

@ -42,7 +42,7 @@ export default {
{
attrs: {
href: chunk,
class: '!gl-text-inherit gl-text-decoration-underline',
class: '!gl-text-inherit gl-underline',
rel: 'nofollow noopener noreferrer', // eslint-disable-line @gitlab/require-i18n-strings
},
},

View File

@ -1,11 +1,17 @@
<script>
import { GlButton, GlEmptyState, GlLoadingIcon, GlModal, GlLink, GlSprintf } from '@gitlab/ui';
import { createAlert } from '~/alert';
import Api from '~/api';
import { getQueryHeaders } from '~/ci/pipeline_details/graph/utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import eventHub from '~/ci/event_hub';
import PipelinesTableComponent from '~/ci/common/pipelines_table.vue';
import { s__, __ } from '~/locale';
import getMergeRequestPipelines from '~/ci/merge_requests/graphql/queries/get_merge_request_pipelines.query.graphql';
import cancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql';
import retryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql';
import { TYPENAME_CI_PIPELINE } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { HTTP_STATUS_UNAUTHORIZED } from '~/lib/utils/http_status';
import { formatPipelinesGraphQLDataToREST } from '../utils';
export default {
@ -47,6 +53,8 @@ export default {
data() {
return {
hasError: false,
isInitialLoading: true,
isRunningMergeRequestPipeline: false,
page: 1,
pageInfo: {},
pipelines: [],
@ -72,6 +80,7 @@ export default {
return formatPipelinesGraphQLDataToREST(data?.project) || [];
},
result({ data }) {
this.isInitialLoading = false;
this.pageInfo = data?.project?.mergeRequest?.pipelines?.pageInfo || {};
},
error() {
@ -84,7 +93,7 @@ export default {
return this.pipelines.length > 0;
},
isLoading() {
return this.$apollo.queries.pipelines.loading;
return this.isInitialLoading && this.$apollo.queries.pipelines.loading;
},
latestPipeline() {
return this.pipelines[0];
@ -114,9 +123,6 @@ export default {
isLatestPipelineCreatedInTargetProject() {
return this.latestPipeline?.project?.full_path === `/${this.targetProjectFullPath}`;
},
isRunningMergeRequestPipeline() {
return false;
},
shouldShowSecurityWarning() {
return (
this.canCreatePipelineInTargetProject &&
@ -139,6 +145,28 @@ export default {
},
},
methods: {
cancelPipeline(pipeline) {
this.executePipelineAction(pipeline, cancelPipelineMutation);
},
retryPipeline(pipeline) {
this.executePipelineAction(pipeline, retryPipelineMutation);
},
async executePipelineAction(pipeline, mutation) {
try {
await this.$apollo.mutate({
mutation,
variables: {
id: convertToGraphQLId(TYPENAME_CI_PIPELINE, pipeline.id),
},
});
this.refreshPipelineTable();
} catch {
createAlert({ message: __('An error occurred while performing this action.') });
}
},
refreshPipelineTable() {
this.$apollo.queries.pipelines.refetch();
},
/**
* When the user clicks on the "Run pipeline" button
* we need to make a post request and
@ -150,11 +178,34 @@ export default {
*
*/
onClickRunPipeline() {
eventHub.$emit('runMergeRequestPipeline', {
projectId: this.projectId,
mergeRequestId: this.mergeRequestId,
});
async onClickRunPipeline() {
try {
this.isRunningMergeRequestPipeline = true;
await Api.postMergeRequestPipeline(this.projectId, {
mergeRequestId: this.mergeRequestId,
});
this.$toast.show(s__('Pipeline|Creating pipeline.'));
} catch (e) {
const unauthorized = e.response.status === HTTP_STATUS_UNAUTHORIZED;
let errorMessage = __(
'An error occurred while trying to run a new pipeline for this merge request.',
);
if (unauthorized) {
errorMessage = __('You do not have permission to run a pipeline on this branch.');
}
createAlert({
message: errorMessage,
primaryButton: {
text: __('Learn more'),
link: helpPagePath('ci/pipelines/merge_request_pipelines.md'),
},
});
}
this.isRunningMergeRequestPipeline = false;
},
tryRunPipeline() {
if (!this.shouldShowSecurityWarning) {
@ -281,6 +332,9 @@ export default {
:pipelines="pipelines"
:update-graph-dropdown="updateGraphDropdown"
:source-project-full-path="sourceProjectFullPath"
@cancel-pipeline="cancelPipeline"
@retry-pipeline="retryPipeline"
@refresh-pipelines-table="refreshPipelineTable"
>
<template #table-header-actions>
<div v-if="canRenderPipelineButton" class="gl-text-right">

View File

@ -21,6 +21,8 @@ query getMergeRequestPipelines($mergeRequestIid: String!, $fullPath: ID!) {
mergeRequestEventType
failureReason
yamlErrors
retryable
cancelable
commit {
id
title

View File

@ -22,7 +22,7 @@ export const formatPipelinesGraphQLDataToREST = (project) => {
return project?.mergeRequest?.pipelines?.nodes?.map((pipeline) => {
return {
...pipeline,
id: getIdFromGraphQLId(mergeRequest.id),
id: getIdFromGraphQLId(pipeline.id),
commit: {
...pipeline.commit,
commit_path: pipeline.commit.webPath,

View File

@ -138,7 +138,7 @@ export default {
v-if="hasChanges"
v-gl-tooltip.html="toggleFileBrowserTooltip"
variant="default"
icon="file-tree"
icon="sidebar"
class="gl-mr-3 js-toggle-tree-list btn-icon"
data-testid="file-tree-button"
:aria-label="toggleFileBrowserTitle"

View File

@ -0,0 +1,9 @@
<script>
export default {
name: 'PagesDeployment',
};
</script>
<template>
<div></div>
</template>

View File

@ -0,0 +1,226 @@
<script>
import { GlToggle, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import { s__ } from '~/locale';
import getProjectPagesDeployments from '../queries/get_project_pages_deployments.graphql';
import PagesDeployment from './deployment.vue';
import LoadMoreDeployments from './load_more_deployments.vue';
export default {
name: 'PagesDeployments',
components: { LoadMoreDeployments, PagesDeployment, GlToggle, GlLoadingIcon, GlAlert },
inject: ['projectFullPath'],
i18n: {
title: s__('Pages|Deployments'),
environmentDeploymentsTitle: s__('Pages|Environment deployments'),
noDeploymentsMessage: s__('Pages|No deployments yet'),
loadErrorMessage: s__(
'Pages|Some Pages deployments could not be loaded. Try reloading the page.',
),
showInactiveLabel: s__('Pages|Include stopped deployments'),
},
data() {
return {
showInactive: false,
requestBatchSize: 2,
hasError: false,
alerts: {},
primaryDeployments: null,
environmentDeployments: null,
};
},
computed: {
sharedQueryVariables() {
return {
active: this.showInactive ? undefined : true,
fullPath: this.projectFullPath,
first: this.requestBatchSize,
};
},
hasMultipleDeployments() {
return (
this.primaryDeployments?.nodes.length > 1 ||
(this.primaryDeployments?.nodes.length && this.environmentDeployments?.nodes.length > 0)
);
},
primaryDeploymentsNotLoaded() {
if (!this.primaryDeployments) return undefined;
return this.primaryDeployments.count - this.primaryDeployments.nodes.length;
},
environmentDeploymentsNotLoaded() {
if (!this.environmentDeployments) return 0;
return this.environmentDeployments.count - this.environmentDeployments.nodes.length;
},
loadedPrimaryDeploymentsCount() {
return this.primaryDeployments?.nodes.length || 0;
},
loadedEnvironmentDeploymentsCount() {
return this.environmentDeployments?.nodes.length || 0;
},
},
apollo: {
primaryDeployments: {
query: getProjectPagesDeployments,
variables() {
return {
...this.sharedQueryVariables,
versioned: false,
};
},
update(data) {
return data.project.pagesDeployments;
},
error() {
this.hasError = true;
},
},
environmentDeployments: {
query: getProjectPagesDeployments,
variables() {
return {
...this.sharedQueryVariables,
versioned: true,
};
},
update(data) {
return data.project.pagesDeployments;
},
error() {
this.hasError = true;
},
},
},
methods: {
toggleShowInactive() {
this.showInactive = !this.showInactive;
},
fetchMorePrimaryDeployments() {
this.$apollo.queries.primaryDeployments.fetchMore({
variables: {
after: this.primaryDeployments.pageInfo.endCursor,
},
updateQuery: this.fetchMoreUpdateResult,
});
},
fetchMoreEnvironmentDeployments() {
this.$apollo.queries.environmentDeployments.fetchMore({
variables: {
after: this.environmentDeployments.pageInfo.endCursor,
},
updateQuery: this.fetchMoreUpdateResult,
});
},
fetchMoreUpdateResult(previousResult, { fetchMoreResult }) {
return {
project: {
...previousResult.project,
pagesDeployments: {
...fetchMoreResult.project.pagesDeployments,
nodes: [
/*
* This weird presence check makes this method resilient against
* empty previousResults, which *should* not happen, but who knows?
* */
...(previousResult?.project?.pagesDeployments
? previousResult.project.pagesDeployments.nodes
: []),
...fetchMoreResult.project.pagesDeployments.nodes,
],
pageInfo: fetchMoreResult.project.pagesDeployments.pageInfo,
},
},
};
},
onChildError({ id, message }) {
this.$set(this.alerts, id, message);
},
dismissAlert(id) {
this.$delete(this.alerts, id);
},
},
};
</script>
<template>
<div>
<gl-alert
v-for="(message, id) in alerts"
:key="id"
variant="danger"
class="gl-mb-4"
sticky
data-testid="alert"
@dismiss="dismissAlert(id)"
>
{{ message }}
</gl-alert>
<div class="gl-flex gl-flex-col md:gl-flex-row gl-mb-4 md:gl-mb-0 gl-justify-between">
<h2 class="gl-text-h2">
{{ $options.i18n.title }}
</h2>
<span class="gl-text-secondary">
<gl-toggle
v-model="showInactive"
:label="$options.i18n.showInactiveLabel"
label-position="left"
data-testid="show-inactive-toggle"
/>
</span>
</div>
<div
v-if="loadedPrimaryDeploymentsCount > 0"
class="gl-flex gl-flex-col gl-gap-4"
data-testid="primary-deployment-list"
>
<pages-deployment
v-for="node in primaryDeployments.nodes"
:key="node.id"
:deployment="node"
class="gl-mb-5"
:query="$apollo.queries.primaryDeployments"
data-testid="primary-deployment"
@error="onChildError"
/>
<load-more-deployments
v-if="primaryDeployments && primaryDeployments.pageInfo.hasNextPage"
:total-deployment-count="primaryDeploymentsNotLoaded"
:loading="$apollo.queries.primaryDeployments.loading"
class="gl-mt-3"
data-testid="load-more-primary-deployments"
@load-more="fetchMorePrimaryDeployments"
/>
</div>
<div v-if="loadedEnvironmentDeploymentsCount > 0" data-testid="environment-deployment-list">
<h3 class="gl-heading-3">{{ $options.i18n.environmentDeploymentsTitle }}</h3>
<div class="gl-flex gl-flex-col gl-gap-4">
<pages-deployment
v-for="node in environmentDeployments.nodes"
:key="node.id"
:deployment="node"
:query="$apollo.queries.environmentDeployments"
data-testid="environment-deployment"
@error="onChildError"
/>
</div>
<load-more-deployments
v-if="environmentDeployments && environmentDeployments.pageInfo.hasNextPage"
:total-deployment-count="environmentDeploymentsNotLoaded"
:loading="$apollo.queries.environmentDeployments.loading"
class="gl-mt-3"
data-testid="load-more-environment-deployments"
@load-more="fetchMoreEnvironmentDeployments"
/>
</div>
<div v-if="!primaryDeployments && !environmentDeployments && $apollo.loading">
<gl-loading-icon size="md" />
</div>
<div
v-else-if="!loadedPrimaryDeploymentsCount && !loadedEnvironmentDeploymentsCount"
class="gl-text-center gl-text-secondary"
>
{{ $options.i18n.noDeploymentsMessage }}
</div>
<gl-alert v-if="hasError" variant="danger" :dismissible="false">
{{ $options.i18n.loadErrorMessage }}
</gl-alert>
</div>
</template>

View File

@ -0,0 +1,12 @@
<script>
import PagesDeployments from './deployments.vue';
export default {
name: 'PagesEdit',
components: { PagesDeployments },
};
</script>
<template>
<pages-deployments class="mb-5" />
</template>

View File

@ -0,0 +1,36 @@
<script>
import { GlButton } from '@gitlab/ui';
export default {
name: 'LoadMoreDeployments',
components: { GlButton },
props: {
totalDeploymentCount: {
type: Number,
required: true,
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<template>
<div class="gl-flex gl-items-center gl-gap-2 gl-justify-center">
<span>
{{ sprintf(__('+ %{n} more deployments'), { n: totalDeploymentCount }) }}
</span>
<gl-button
category="tertiary"
variant="confirm"
size="sm"
:loading="loading"
@click="$emit('load-more')"
>
{{ __('Load more') }}
</gl-button>
</div>
</template>

View File

@ -0,0 +1,44 @@
query GetProjectPagesDeployments(
$fullPath: ID!
$first: Int
$last: Int
$after: String
$before: String
$active: Boolean
$versioned: Boolean
$sort: Sort
) {
project(fullPath: $fullPath) {
id
pagesDeployments(
first: $first
last: $last
after: $after
before: $before
active: $active
versioned: $versioned
sort: $sort
) {
count
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
nodes {
id
active
rootDirectory
ciBuildId
createdAt
deletedAt
fileCount
pathPrefix
size
updatedAt
url
}
}
}
}

View File

@ -1,7 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import PagesEdit from './components/pages_edit.vue';
import PagesEdit from './components/edit.vue';
Vue.use(VueApollo);
@ -20,12 +20,11 @@ export default function initPages() {
el,
name: 'GitlabPagesEditRoot',
apolloProvider,
provide: {
projectFullPath: el.dataset.fullPath,
},
render(createElement) {
return createElement(PagesEdit, {
props: {
...el.dataset,
},
});
return createElement(PagesEdit, {});
},
});
}

View File

@ -1,12 +1,56 @@
<script>
import produce from 'immer';
import { GlLink, GlLoadingIcon, GlIcon } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapActions } from 'vuex';
import { createAlert } from '~/alert';
import { sprintf, __, n__ } from '~/locale';
import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import relatedMergeRequestsQuery from '../queries/related_merge_requests.query.graphql';
export default {
name: 'RelatedMergeRequests',
apollo: {
mergeRequests: {
query: relatedMergeRequestsQuery,
variables() {
return {
projectPath: this.projectPath,
iid: this.iid,
};
},
update: (d) => d?.project?.issue?.relatedMergeRequests?.nodes,
result({ data }) {
const pageInfo = data?.project?.issue?.relatedMergeRequests?.pageInfo;
this.totalCount = data?.project?.issue?.relatedMergeRequests?.count;
if (pageInfo?.hasNextPage) {
this.$apollo.queries.mergeRequests.fetchMore({
variables: {
projectPath: this.projectPath,
iid: this.iid,
after: pageInfo.endCursor,
},
updateQuery: (previousResult, { fetchMoreResult }) => {
const newMergeRequests = fetchMoreResult.project.issue.relatedMergeRequests.nodes;
const prevMergeRequests = previousResult.project.issue.relatedMergeRequests.nodes;
return produce(fetchMoreResult, (draftData) => {
draftData.project.issue.relatedMergeRequests.nodes = prevMergeRequests.concat(
newMergeRequests,
);
});
},
});
}
},
error() {
createAlert({
message: __('Something went wrong while fetching related merge requests.'),
});
},
},
},
components: {
GlIcon,
GlLink,
@ -14,26 +58,30 @@ export default {
RelatedIssuableItem,
},
props: {
endpoint: {
type: String,
required: true,
},
hasClosingMergeRequest: {
type: Boolean,
required: false,
default: false,
},
projectNamespace: {
type: String,
required: true,
},
projectPath: {
type: String,
required: true,
},
iid: {
type: String,
required: true,
},
},
data() {
return {
mergeRequests: null,
totalCount: null,
};
},
computed: {
...mapState(['isFetchingMergeRequests', 'mergeRequests', 'totalCount']),
isFetchingMergeRequests() {
return this.$apollo.queries.mergeRequests.loading;
},
closingMergeRequestsText() {
if (!this.hasClosingMergeRequest) {
return '';
@ -48,18 +96,9 @@ export default {
return sprintf(__('%{mrText}, this issue will be closed automatically.'), { mrText });
},
},
mounted() {
this.setInitialState({ apiEndpoint: this.endpoint });
this.fetchMergeRequests();
},
methods: {
...mapActions(['setInitialState', 'fetchMergeRequests']),
getAssignees(mr) {
if (mr.assignees) {
return mr.assignees;
}
return mr.assignee ? [mr.assignee] : [];
idKey(mergeRequest) {
return getIdFromGraphQLId(mergeRequest.id);
},
},
};
@ -107,19 +146,18 @@ export default {
class="list-item gl-m-0! gl-p-0! gl-border-b-0!"
>
<related-issuable-item
:id-key="mr.id"
:id-key="idKey(mr)"
:display-reference="mr.reference"
:title="mr.title"
:milestone="mr.milestone"
:assignees="getAssignees(mr)"
:created-at="mr.created_at"
:closed-at="mr.closed_at"
:merged-at="mr.merged_at"
:path="mr.web_url"
:assignees="mr.assignees.nodes"
:created-at="mr.createdAt"
:merged-at="mr.mergedAt"
:path="mr.webUrl"
:state="mr.state"
:is-merge-request="true"
:pipeline-status="mr.head_pipeline && mr.head_pipeline.detailed_status"
:pipeline-status="mr.headPipeline && mr.headPipeline.detailedStatus"
path-id-separator="!"
is-merge-request
class="-gl-mx-2"
/>
</li>

View File

@ -1,7 +1,8 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import RelatedMergeRequests from './components/related_merge_requests.vue';
import createStore from './store';
export function initRelatedMergeRequests() {
const el = document.querySelector('#js-related-merge-requests');
@ -9,20 +10,23 @@ export function initRelatedMergeRequests() {
if (!el) {
return undefined;
}
Vue.use(VueApollo);
const { endpoint, hasClosingMergeRequest, projectPath, projectNamespace } = el.dataset;
const { hasClosingMergeRequest, projectPath, iid } = el.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
return new Vue({
apolloProvider,
el,
name: 'RelatedMergeRequestsRoot',
store: createStore(),
render: (createElement) =>
createElement(RelatedMergeRequests, {
props: {
endpoint,
hasClosingMergeRequest: parseBoolean(hasClosingMergeRequest),
projectNamespace,
projectPath,
iid,
},
}),
});

View File

@ -0,0 +1,42 @@
#import "~/graphql_shared/fragments/ci_icon.fragment.graphql"
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/graphql_shared/fragments/milestone.fragment.graphql"
query fetchRelatedMergeRequests($projectPath: ID!, $iid: String!, $after: String = "") {
project(fullPath: $projectPath) {
id
issue(iid: $iid) {
id
relatedMergeRequests(after: $after) {
count
pageInfo {
...PageInfo
}
nodes {
id
reference
state
title
createdAt
mergedAt
webUrl
milestone {
...MilestoneFragment
}
assignees {
nodes {
...User
}
}
headPipeline {
id
detailedStatus {
...CiIcon
}
}
}
}
}
}
}

View File

@ -1,36 +0,0 @@
import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { normalizeHeaders } from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import * as types from './mutation_types';
const REQUEST_PAGE_COUNT = 100;
export const setInitialState = ({ commit }, props) => {
commit(types.SET_INITIAL_STATE, props);
};
export const requestData = ({ commit }) => commit(types.REQUEST_DATA);
export const receiveDataSuccess = ({ commit }, data) => commit(types.RECEIVE_DATA_SUCCESS, data);
export const receiveDataError = ({ commit }) => commit(types.RECEIVE_DATA_ERROR);
export const fetchMergeRequests = ({ state, dispatch }) => {
dispatch('requestData');
return axios
.get(`${state.apiEndpoint}?per_page=${REQUEST_PAGE_COUNT}`)
.then((res) => {
const { headers, data } = res;
const total = Number(normalizeHeaders(headers)['X-TOTAL']) || 0;
dispatch('receiveDataSuccess', { data, total });
})
.catch(() => {
dispatch('receiveDataError');
createAlert({
message: __('Something went wrong while fetching related merge requests.'),
});
});
};

View File

@ -1,15 +0,0 @@
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import createState from './state';
Vue.use(Vuex);
export default () =>
new Vuex.Store({
state: createState(),
actions,
mutations,
});

View File

@ -1,4 +0,0 @@
export const SET_INITIAL_STATE = 'SET_INITIAL_STATE';
export const REQUEST_DATA = 'REQUEST_DATA';
export const RECEIVE_DATA_SUCCESS = 'RECEIVE_DATA_SUCCESS';
export const RECEIVE_DATA_ERROR = 'RECEIVE_DATA_ERROR';

View File

@ -1,19 +0,0 @@
import * as types from './mutation_types';
export default {
[types.SET_INITIAL_STATE](state, { apiEndpoint }) {
state.apiEndpoint = apiEndpoint;
},
[types.REQUEST_DATA](state) {
state.isFetchingMergeRequests = true;
},
[types.RECEIVE_DATA_SUCCESS](state, { data, total }) {
state.isFetchingMergeRequests = false;
state.mergeRequests = data;
state.totalCount = total;
},
[types.RECEIVE_DATA_ERROR](state) {
state.isFetchingMergeRequests = false;
state.hasErrorFetchingMergeRequests = true;
},
};

View File

@ -1,7 +0,0 @@
export default () => ({
apiEndpoint: '',
isFetchingMergeRequests: false,
hasErrorFetchingMergeRequests: false,
mergeRequests: [],
totalCount: 0,
});

View File

@ -120,7 +120,7 @@ export default {
<template #status-badge>
<gl-sprintf v-if="closedStatusLink" :message="statusText">
<template #link>
<gl-link class="!gl-text-inherit gl-text-decoration-underline" :href="closedStatusLink">{{
<gl-link class="!gl-text-inherit gl-underline" :href="closedStatusLink">{{
closedStatusText
}}</gl-link>
</template>

View File

@ -42,7 +42,7 @@ export default {
<gl-link
data-testid="pipeline-sha"
:href="pipeline.commitPath"
class="gl-mr-2 gl-text-decoration-underline"
class="gl-mr-2 gl-underline"
>{{ packageShaShort }}</gl-link
>

View File

@ -53,7 +53,7 @@ export default {
<span>
<gl-sprintf :message="publishedMessage">
<template v-if="projectName" #projectName>
<gl-link class="gl-text-decoration-underline" :href="projectUrl">{{ projectName }}</gl-link>
<gl-link class="gl-underline" :href="projectUrl">{{ projectName }}</gl-link>
</template>
<template #date>
<time-ago-tooltip :time="publishDate" />

View File

@ -207,11 +207,9 @@ export default {
id="peek-view-trace"
class="view"
>
<gl-link
class="gl-text-decoration-underline"
:href="currentRequest.details.tracing.tracing_url"
>{{ s__('PerformanceBar|Trace') }}</gl-link
>
<gl-link class="gl-underline" :href="currentRequest.details.tracing.tracing_url">{{
s__('PerformanceBar|Trace')
}}</gl-link>
</div>
<div v-if="showFlamegraphButtons" id="peek-flamegraph" class="view">
<gl-link

View File

@ -54,7 +54,7 @@ export default {
</script>
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="labels-select-contents-create">
<div class="dropdown-title gl-flex gl-items-center pt-0 pb-2 gl-mb-0">
<gl-button
:aria-label="__('Go back')"

View File

@ -1,6 +1,6 @@
<script>
import { get } from 'lodash';
import { GlAlert, GlTooltipDirective, GlButton, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlTooltipDirective, GlButton, GlFormGroup, GlFormInput } from '@gitlab/ui';
import produce from 'immer';
import { createAlert } from '~/alert';
import { WORKSPACE_GROUP } from '~/issues/constants';
@ -15,8 +15,8 @@ export default {
components: {
GlAlert,
GlButton,
GlFormGroup,
GlFormInput,
GlLoadingIcon,
SidebarColorPicker,
},
directives: {
@ -135,43 +135,43 @@ export default {
</script>
<template>
<div class="labels-select-contents-create js-labels-create">
<div class="dropdown-input">
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mt-3">
{{ error }}
</gl-alert>
<div class="gl-px-4">
<gl-alert v-if="error" variant="danger" :dismissible="false" class="gl-mt-3">
{{ error }}
</gl-alert>
<gl-form-group
class="gl-my-3"
:label="__('Name new label')"
label-for="label-title-input"
label-sr-only
>
<gl-form-input
id="label-title-input"
v-model.trim="labelTitle"
class="gl-mt-3"
autofocus
:placeholder="__('Name new label')"
:autofocus="true"
data-testid="label-title-input"
/>
</div>
<sidebar-color-picker
v-model.trim="selectedColor"
:suggested-colors="suggestedColors"
class="gl-px-4 gl-py-2"
/>
<div class="dropdown-actions gl-display-flex gl-justify-content-space-between gl-pt-3 gl-px-3">
<gl-button
:disabled="disableCreate"
category="primary"
variant="confirm"
class="gl-display-flex gl-align-items-center"
data-testid="create-button"
@click="createLabel"
>
<gl-loading-icon v-if="labelCreateInProgress" size="sm" :inline="true" class="mr-1" />
{{ __('Create') }}
</gl-button>
</gl-form-group>
<sidebar-color-picker v-model.trim="selectedColor" :suggested-colors="suggestedColors" />
<div class="gl-flex gl-justify-end gl-gap-3 gl-mt-2">
<gl-button
class="js-btn-cancel-create"
size="small"
data-testid="cancel-button"
@click.stop="$emit('hideCreateView')"
>
{{ __('Cancel') }}
</gl-button>
<gl-button
:disabled="disableCreate"
:loading="labelCreateInProgress"
size="small"
variant="confirm"
data-testid="create-button"
@click="createLabel"
>
{{ __('Create') }}
</gl-button>
</div>
</div>
</template>

View File

@ -51,7 +51,7 @@ export default {
<div data-testid="dropdown-header">
<div
v-if="!isStandalone"
class="dropdown-title gl-display-flex gl-align-items-center gl-pt-0 gl-pb-3! gl-mb-0"
class="dropdown-title gl-display-flex gl-align-items-center gl-pt-2 gl-pb-4 gl-mb-0"
data-testid="dropdown-header-title"
>
<gl-button
@ -59,7 +59,7 @@ export default {
:aria-label="__('Go back')"
variant="link"
size="small"
class="js-btn-back dropdown-header-button gl-p-0"
class="js-btn-back dropdown-header-button !gl-p-0"
icon="arrow-left"
data-testid="go-back-button"
@click.stop="$emit('toggleDropdownContentsCreateView')"

View File

@ -153,7 +153,7 @@ export default {
<gl-sprintf :message="$options.i18n.legal">
<template #link="{ content }">
<a
class="gl-text-decoration-underline gl-text-gray-500"
class="gl-underline gl-text-gray-500"
:href="$options.privacyLink"
target="_blank"
rel="noreferrer nofollow"

View File

@ -68,9 +68,9 @@ export const generateText = (text) => {
}
if (typeof text === 'object' && typeof text.text === 'string' && typeof text.href === 'string') {
return createText(
`${
text.prependText ? `${escapeText(text.prependText)} ` : ''
}<a class="gl-text-decoration-underline" href="${text.href}">${escapeText(text.text)}</a>`,
`${text.prependText ? `${escapeText(text.prependText)} ` : ''}<a class="gl-underline" href="${
text.href
}">${escapeText(text.text)}</a>`,
);
}

View File

@ -27,10 +27,9 @@
= render_if_exists 'projects/issues/linked_resources'
= render 'projects/issues/related_issues'
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: issuable.iid)),
has_closing_merge_request: (issuable.merge_requests_count(current_user) != 0).to_s,
project_namespace: @project.namespace.path,
project_path: @project.path } }
#js-related-merge-requests{ data: { has_closing_merge_request: (issuable.merge_requests_count(current_user) != 0).to_s,
project_path: @project.full_path,
iid: issuable.iid } }
- if can?(current_user, :read_code, @project)
- add_page_startup_api_call related_branches_path

View File

@ -1,17 +1,18 @@
- if Feature.enabled?('new_pages_ui', @project)
#js-pages
- else
- page_title s_('GitLabPages|Pages')
- page_title s_('GitLabPages|Pages')
- unless @project.pages_deployed?
= render 'waiting'
- unless @project.pages_deployed?
= render 'waiting'
- else
= render 'header'
- if Feature.enabled?('new_pages_ui', @project)
#js-pages{ data: { full_path: @project.full_path } }
%h2.gl-heading-2
= s_('Settings')
= render 'pages_settings'
= render 'ssl_limitations_warning' if pages_subdomain(@project).include?(".")
= render 'access'
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
= render 'list'
- else
= render 'header'
= render 'pages_settings'
= render 'ssl_limitations_warning' if pages_subdomain(@project).include?(".")
= render 'access'
- if Gitlab.config.pages.external_http || Gitlab.config.pages.external_https
= render 'list'
- else
= render 'no_domains'
= render 'destroy'
= render 'no_domains'
= render 'destroy'

View File

@ -25,9 +25,9 @@
= s_("Profiles|This email will be displayed on your public profile.")
.form-group.gl-form-group
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'change-the-email-displayed-on-your-commits')
- commit_email_link_url = help_page_path('user/profile/index', anchor: 'use-an-automatically-generated-private-commit-email')
- commit_email_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: commit_email_link_url }
- commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}.').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
- commit_email_docs_link = s_('Profiles|This email is used for web-based operations, such as edits and merges. %{commit_email_link_start}What is a private commit email?%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
= form.label :commit_email, s_('Profiles|Commit email')
.gl-md-form-input-lg
= form.select :commit_email,

View File

@ -1,6 +1,6 @@
- form = local_assigns.fetch(:form)
- private_profile_help_link = link_to _("Learn more"), help_page_path('user/profile/index', anchor: 'make-your-user-profile-page-private')
- private_profile_label = safe_format(s_("Profiles|Don't display activity-related personal information on your profile. %{private_profile_help_link_start}Learn more%{private_profile_help_link_end}."), tag_pair(private_profile_help_link, :private_profile_help_link_start, :private_profile_help_link_end))
- private_profile_help_link = link_to _("What information is hidden?"), help_page_path('user/profile/index', anchor: 'make-your-user-profile-page-private')
- private_profile_label = safe_format(s_("Profiles|Don't display activity-related personal information on your profile. %{private_profile_help_link_start}What information is hidden?%{private_profile_help_link_end}"), tag_pair(private_profile_help_link, :private_profile_help_link_start, :private_profile_help_link_end))
= form.gitlab_ui_checkbox_component :private_profile, private_profile_label

View File

@ -110,9 +110,7 @@
= f.label :twitter, _('X (formerly Twitter)')
= f.text_field :twitter, class: 'gl-form-input form-control gl-md-form-input-lg', placeholder: s_("Profiles|@username")
.form-group.gl-form-group
- external_accounts_help_url = help_page_path('user/profile/index', anchor: 'add-external-accounts-to-your-user-profile-page')
- external_accounts_link = link_to '', external_accounts_help_url, target: "_blank", rel: "noopener noreferrer"
- external_accounts_docs_link = safe_format(s_('Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more%{external_accounts_link_end}.'), tag_pair(external_accounts_link, :external_accounts_link_start, :external_accounts_link_end))
- external_accounts_docs_link = safe_format(s_('Profiles|Your Discord user ID.'))
- min_discord_length = 17
- max_discord_length = 20
= f.label :discord

View File

@ -794,7 +794,7 @@ Gitlab.ee do
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['elastic_index_initial_bulk_cron_worker']['job_class'] ||= 'ElasticIndexInitialBulkCronWorker'
Settings.cron_jobs['elastic_cluster_reindexing_cron_worker'] ||= {}
Settings.cron_jobs['elastic_cluster_reindexing_cron_worker']['cron'] ||= '*/10 * * * *'
Settings.cron_jobs['elastic_cluster_reindexing_cron_worker']['cron'] ||= '*/5 * * * *'
Settings.cron_jobs['elastic_cluster_reindexing_cron_worker']['job_class'] ||= 'ElasticClusterReindexingCronWorker'
Settings.cron_jobs['elastic_remove_expired_namespace_subscriptions_from_index_cron_worker'] ||= {}
Settings.cron_jobs['elastic_remove_expired_namespace_subscriptions_from_index_cron_worker']['cron'] ||= '10 3 * * *'

View File

@ -1,4 +1,4 @@
- title: "apiFuzzingCiConfigurationCreate GraphQL mutation"
- title: "`apiFuzzingCiConfigurationCreate` GraphQL mutation"
announcement_milestone: "14.6"
removal_milestone: "15.0"
breaking_change: true

View File

@ -1,4 +1,4 @@
- title: "GraphQL networkPolicies resource deprecated" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters."
- title: "GraphQL `networkPolicies` resource deprecated" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters."
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
announcement_milestone: "14.8" # (required) The milestone when this feature was first announced as deprecated.
breaking_change: true # (required) Change to false if this is not a breaking change.

View File

@ -1,4 +1,4 @@
- title: "PipelineSecurityReportFinding name GraphQL field" # (required) The name of the feature to be deprecated
- title: "`name` field for `PipelineSecurityReportFinding` GraphQL type" # (required) The name of the feature to be deprecated
announcement_milestone: "15.1" # (required) The milestone when this feature was first announced as deprecated.
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
@ -6,7 +6,7 @@
stage: Secure # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346335 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
Previously, the [PipelineSecurityReportFinding GraphQL type was updated](https://gitlab.com/gitlab-org/gitlab/-/issues/335372) to include a new `title` field. This field is an alias for the current `name` field, making the less specific `name` field redundant. The `name` field will be removed from the PipelineSecurityReportFinding type in GitLab 16.0.
Previously, the [`PipelineSecurityReportFinding` GraphQL type was updated](https://gitlab.com/gitlab-org/gitlab/-/issues/335372) to include a new `title` field. This field is an alias for the current `name` field, making the less specific `name` field redundant. The `name` field will be removed from the `PipelineSecurityReportFinding` type in GitLab 16.0.
# The following items are not published on the docs page, but may be used in the future.
tiers: Ultimate # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
documentation_url: # (optional) This is a link to the current documentation page

View File

@ -1,4 +1,4 @@
- title: "PipelineSecurityReportFinding projectFingerprint GraphQL field" # (required) The name of the feature to be deprecated
- title: "`projectFingerprint` GraphQL field" # (required) The name of the feature to be deprecated
announcement_milestone: "15.1" # (required) The milestone when this feature was first announced as deprecated.
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
@ -6,7 +6,7 @@
stage: Secure # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/343475 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
The [`project_fingerprint`](https://gitlab.com/groups/gitlab-org/-/epics/2791) attribute of vulnerability findings is being deprecated in favor of a `uuid` attribute. By using UUIDv5 values to identify findings, we can easily associate any related entity with a finding. The `project_fingerprint` attribute is no longer being used to track findings, and will be removed in GitLab 17.0. Starting in 16.1, the output of `project_fingerprint` returns the same value as the `uuid` field.
The [`project_fingerprint`](https://gitlab.com/groups/gitlab-org/-/epics/2791) attribute of vulnerability findings is being deprecated in favor of a `uuid` attribute. By using UUIDv5 values to identify findings, we can easily associate any related entity with a finding. The `project_fingerprint` attribute is no longer being used to track findings, and will be removed in GitLab 17.0. Starting in 16.1, the output of `project_fingerprint` returns the same value as the `uuid` field.
# The following items are not published on the docs page, but may be used in the future.
tiers: Ultimate # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
documentation_url: # (optional) This is a link to the current documentation page

View File

@ -1,7 +1,7 @@
#
# REQUIRED FIELDS
#
- title: "Use of `id` field in vulnerabilityFindingDismiss mutation" # (required) The name of the feature to be deprecated
- title: "Use of `id` field in `vulnerabilityFindingDismiss` mutation" # (required) The name of the feature to be deprecated
announcement_milestone: "15.3" # (required) The milestone when this feature was first announced as deprecated.
removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
@ -9,7 +9,7 @@
stage: Secure # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/367166 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
You can use the vulnerabilityFindingDismiss GraphQL mutation to set the status of a vulnerability finding to `Dismissed`. Previously, this mutation used the `id` field to identify findings uniquely. However, this did not work for dismissing findings from the pipeline security tab. Therefore, using the `id` field as an identifier has been dropped in favor of the `uuid` field. Using the 'uuid' field as an identifier allows you to dismiss the finding from the pipeline security tab.
You can use the `vulnerabilityFindingDismiss` GraphQL mutation to set the status of a vulnerability finding to `Dismissed`. Previously, this mutation used the `id` field to identify findings uniquely. However, this did not work for dismissing findings from the pipeline security tab. Therefore, using the `id` field as an identifier has been dropped in favor of the `uuid` field. Using the 'uuid' field as an identifier allows you to dismiss the finding from the pipeline security tab.
#
# OPTIONAL FIELDS
#

View File

@ -1,4 +1,4 @@
- title: "vulnerabilityFindingDismiss GraphQL mutation"
- title: "`vulnerabilityFindingDismiss` GraphQL mutation"
announcement_milestone: "15.5"
removal_milestone: "16.0"
breaking_change: true

View File

@ -1,7 +1,7 @@
#
# REQUIRED FIELDS
#
- title: "GraphQL: The `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` enum is deprecated. Use `DISABLED_AND_OVERRIDABLE` instead" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead.
- title: "GraphQL: The `DISABLED_WITH_OVERRIDE` value for the `SharedRunnersSetting` enum is deprecated" # (required) Actionable title. e.g., The `confidential` field for a `Note` is deprecated. Use `internal` instead.
announcement_milestone: "15.8" # (required) The milestone when this feature was first announced as deprecated.
announcement_date: "2023-01-22" # (required) The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
@ -11,7 +11,8 @@
stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/385636 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
In GitLab 17.0, the `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` GraphQL enum type will be replaced with the value, `DISABLED_AND_OVERRIDABLE`.
In GitLab 17.0, the `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` GraphQL enum type will be removed.
Use `DISABLED_AND_OVERRIDABLE` instead.
#
# OTHER OPTIONAL FIELDS
#

View File

@ -17,7 +17,7 @@
#
# REQUIRED FIELDS
#
- title: "HashiCorp Vault integration will no longer use CI_JOB_JWT by default"
- title: "HashiCorp Vault integration will no longer use the `CI_JOB_JWT` CI/CD job token by default"
announcement_milestone: "15.9" # (required) The milestone when this feature was first announced as deprecated.
removal_milestone: "17.0" # (required) The milestone when this feature is planned to be removed
breaking_change: true # (required) Change to false if this is not a breaking change.

View File

@ -6,7 +6,7 @@
stage: stage # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: "https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28165" # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
The GitLab Runner Kubernetes executor setting, `terminationGracePeriodSeconds`, is deprecated and will be removed in GitLab 17.0. To manage the cleanup and termination of GitLab Runner worker pods on Kubernetes, customers should instead configure `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`. For information about how to use the `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds, see the [GitLab Runner Executor documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#other-configtoml-settings).
The GitLab Runner Kubernetes executor setting, `terminationGracePeriodSeconds`, is deprecated and will be removed in GitLab 17.0. To manage the cleanup and termination of GitLab Runner worker pods on Kubernetes, customers should instead configure `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`. For information about how to use the `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`, see the [GitLab Runner Executor documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#other-configtoml-settings).
#
# OPTIONAL END OF SUPPORT FIELDS

View File

@ -1,4 +1,4 @@
- title: "Rename the 'require_password_to_approve' field"
- title: "Rename the `require_password_to_approve` field"
removal_milestone: "17.0"
announcement_milestone: "16.9"
breaking_change: true

View File

@ -615,7 +615,7 @@ flow of how we construct a Chat prompt:
GitLab Duo Chat has error codes with specified meanings to assist in debugging.
Currently, they are only logged, but in the future, they will be displayed on the UI.
When developing for GitLab Duo Chat, please include these error codes when returning an error, especially a user-facing error.
When developing for GitLab Duo Chat, please include these error codes when returning an error and [document them](../../user/gitlab_duo_chat/troubleshooting.md), especially for user-facing errors.
### Error Code Format

View File

@ -84,7 +84,7 @@ You can use the [important modifier](https://tailwindcss.com/docs/configuration#
Spacing and sizing CSS utility classes (e.g. `margin`, `padding`, `width`, `height`) use our spacing scale defined in
Color CSS utility classes (e.g. `color` and `background-color`) use colors defined in
[src/tokens/build/tailwind/tokens.cjs](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/24a08b50da6bd3d34fb3f8d24f84436d90d165f6/src/tokens/build/tailwind/tokens.cjs).
They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation)
They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/padding)
but the color names will not match. When using the [Tailwind CSS autocomplete](#tailwind-css-autocomplete)
our configured colors will be shown.
@ -94,7 +94,7 @@ our configured colors will be shown.
Color CSS utility classes (e.g. `color` and `background-color`) use colors defined in
[src/tokens/build/tailwind/tokens.cjs](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/24a08b50da6bd3d34fb3f8d24f84436d90d165f6/src/tokens/build/tailwind/tokens.cjs).
They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation)
They will use the naming conventions documented in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/text-color)
but the color names will not match. When using the [Tailwind CSS autocomplete](#tailwind-css-autocomplete)
our configured colors will be shown.
@ -163,7 +163,7 @@ For full HAML and custom `*-class` prop support these are the recommended update
GitLab defines its own Tailwind CSS config in [tailwind.defaults.js](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js)
to match the Pajamas design system and to prefix CSS utility classes with `gl-`.
This means that in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/installation)
This means that in the [official Tailwind CSS documentation](https://tailwindcss.com/docs/padding)
the spacing, sizing, and color CSS utility classes may not match. Also, the `gl-`
prefix will not be shown. Here is our
[spacing scale](https://gitlab.com/gitlab-org/gitlab-ui/-/blob/6612eaee37cdb4dd0258468c9f415be28c1053f0/tailwind.defaults.js#L4)

View File

@ -358,6 +358,16 @@ Check the [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules) for mor
<my-component />
```
## `<style>` tags
We don't use `<style>` tags in Vue components for a few reasons:
1. You cannot use SCSS variables and mixins or [Tailwind CSS](scss.md#tailwind-css) `@apply` directive.
1. These styles get inserted at runtime.
1. We already have a few other ways to define CSS.
Instead of using a `<style>` tag you should use [Tailwind CSS utility classes](scss.md#tailwind-css) or [page specific CSS](https://gitlab.com/groups/gitlab-org/-/epics/3694).
## Ordering
1. Tag order in `.vue` file
@ -371,7 +381,7 @@ Check the [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules) for mor
// ...
</template>
// We don't use scoped styles but there are few instances of this
// We don't use `<style>` tags but there are few instances of this
<style>
// ...
</style>

View File

@ -1137,7 +1137,7 @@ The message field was removed from security reports schema in GitLab 16.0 and is
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/28165).
</div>
The GitLab Runner Kubernetes executor setting, `terminationGracePeriodSeconds`, is deprecated and will be removed in GitLab 17.0. To manage the cleanup and termination of GitLab Runner worker pods on Kubernetes, customers should instead configure `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`. For information about how to use the `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds, see the [GitLab Runner Executor documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#other-configtoml-settings).
The GitLab Runner Kubernetes executor setting, `terminationGracePeriodSeconds`, is deprecated and will be removed in GitLab 17.0. To manage the cleanup and termination of GitLab Runner worker pods on Kubernetes, customers should instead configure `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`. For information about how to use the `cleanupGracePeriodSeconds` and `podTerminationGracePeriodSeconds`, see the [GitLab Runner Executor documentation](https://docs.gitlab.com/runner/executors/kubernetes.html#other-configtoml-settings).
</div>
@ -1381,6 +1381,20 @@ For customers already using documented and supported token types, there are no b
<div class="deprecation breaking-change" data-milestone="17.0">
### GraphQL `networkPolicies` resource deprecated
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">14.8</span>
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/421440).
</div>
The `networkPolicies` [GraphQL resource](https://docs.gitlab.com/ee/api/graphql/reference/#projectnetworkpolicies) has been deprecated and will be removed in GitLab 17.0. Since GitLab 15.0 this field has returned no data.
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### GraphQL deprecation of `dependencyProxyTotalSizeInBytes` field
<div class="deprecation-notes">
@ -1443,20 +1457,6 @@ Use `totalIssueWeight` instead, introduced in GitLab 16.2.
<div class="deprecation breaking-change" data-milestone="17.0">
### GraphQL networkPolicies resource deprecated
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">14.8</span>
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/421440).
</div>
The `networkPolicies` [GraphQL resource](https://docs.gitlab.com/ee/api/graphql/reference/#projectnetworkpolicies) has been deprecated and will be removed in GitLab 17.0. Since GitLab 15.0 this field has returned no data.
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### GraphQL type, `RunnerMembershipFilter` renamed to `CiRunnerMembershipFilter`
<div class="deprecation-notes">
@ -1472,7 +1472,7 @@ the aliasing for the `RunnerMembershipFilter` type will be removed.
<div class="deprecation breaking-change" data-milestone="17.0">
### GraphQL: The `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` enum is deprecated. Use `DISABLED_AND_OVERRIDABLE` instead
### GraphQL: The `DISABLED_WITH_OVERRIDE` value for the `SharedRunnersSetting` enum is deprecated
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.8</span>
@ -1480,7 +1480,8 @@ the aliasing for the `RunnerMembershipFilter` type will be removed.
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/385636).
</div>
In GitLab 17.0, the `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` GraphQL enum type will be replaced with the value, `DISABLED_AND_OVERRIDABLE`.
In GitLab 17.0, the `DISABLED_WITH_OVERRIDE` value of the `SharedRunnersSetting` GraphQL enum type will be removed.
Use `DISABLED_AND_OVERRIDABLE` instead.
</div>
@ -1504,7 +1505,7 @@ This is a breaking change that will be completed in 17.0.
<div class="deprecation breaking-change" data-milestone="17.0">
### HashiCorp Vault integration will no longer use CI_JOB_JWT by default
### HashiCorp Vault integration will no longer use the `CI_JOB_JWT` CI/CD job token by default
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.9</span>
@ -1839,20 +1840,6 @@ In milestone 17.0, we will remove the `pipelines` attribute from the API respons
<div class="deprecation breaking-change" data-milestone="17.0">
### PipelineSecurityReportFinding projectFingerprint GraphQL field
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.1</span>
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/343475).
</div>
The [`project_fingerprint`](https://gitlab.com/groups/gitlab-org/-/epics/2791) attribute of vulnerability findings is being deprecated in favor of a `uuid` attribute. By using UUIDv5 values to identify findings, we can easily associate any related entity with a finding. The `project_fingerprint` attribute is no longer being used to track findings, and will be removed in GitLab 17.0. Starting in 16.1, the output of `project_fingerprint` returns the same value as the `uuid` field.
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### PostgreSQL 13 no longer supported
<div class="deprecation-notes">
@ -1926,7 +1913,7 @@ For more information, see [Removing tags from our small SaaS runner on Linux](ht
<div class="deprecation breaking-change" data-milestone="17.0">
### Rename the 'require_password_to_approve' field
### Rename the `require_password_to_approve` field
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">16.9</span>
@ -2421,6 +2408,20 @@ enable `postgres_exporter['flags']['collector.stat_user_tables']` instead.
<div class="deprecation breaking-change" data-milestone="17.0">
### `projectFingerprint` GraphQL field
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.1</span>
- Removal in GitLab <span class="milestone">17.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/343475).
</div>
The [`project_fingerprint`](https://gitlab.com/groups/gitlab-org/-/epics/2791) attribute of vulnerability findings is being deprecated in favor of a `uuid` attribute. By using UUIDv5 values to identify findings, we can easily associate any related entity with a finding. The `project_fingerprint` attribute is no longer being used to track findings, and will be removed in GitLab 17.0. Starting in 16.1, the output of `project_fingerprint` returns the same value as the `uuid` field.
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### npm package uploads now occur asynchronously
<div class="deprecation-notes">
@ -3535,20 +3536,6 @@ The option to delete groups and projects immediately by default was deprecated t
<div class="deprecation breaking-change" data-milestone="16.0">
### PipelineSecurityReportFinding name GraphQL field
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.1</span>
- Removal in GitLab <span class="milestone">16.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/346335).
</div>
Previously, the [PipelineSecurityReportFinding GraphQL type was updated](https://gitlab.com/gitlab-org/gitlab/-/issues/335372) to include a new `title` field. This field is an alias for the current `name` field, making the less specific `name` field redundant. The `name` field will be removed from the PipelineSecurityReportFinding type in GitLab 16.0.
</div>
<div class="deprecation breaking-change" data-milestone="16.0">
### PostgreSQL 12 deprecated
<div class="deprecation-notes">
@ -3934,7 +3921,7 @@ In order to make the behavior of toggling the draft status of a merge request mo
<div class="deprecation breaking-change" data-milestone="16.0">
### Use of `id` field in vulnerabilityFindingDismiss mutation
### Use of `id` field in `vulnerabilityFindingDismiss` mutation
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.3</span>
@ -3942,7 +3929,7 @@ In order to make the behavior of toggling the draft status of a merge request mo
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/367166).
</div>
You can use the vulnerabilityFindingDismiss GraphQL mutation to set the status of a vulnerability finding to `Dismissed`. Previously, this mutation used the `id` field to identify findings uniquely. However, this did not work for dismissing findings from the pipeline security tab. Therefore, using the `id` field as an identifier has been dropped in favor of the `uuid` field. Using the 'uuid' field as an identifier allows you to dismiss the finding from the pipeline security tab.
You can use the `vulnerabilityFindingDismiss` GraphQL mutation to set the status of a vulnerability finding to `Dismissed`. Previously, this mutation used the `id` field to identify findings uniquely. However, this did not work for dismissing findings from the pipeline security tab. Therefore, using the `id` field as an identifier has been dropped in favor of the `uuid` field. Using the 'uuid' field as an identifier allows you to dismiss the finding from the pipeline security tab.
</div>
@ -4048,6 +4035,20 @@ To avoid confusion and duplication, the `environment_tier` parameter is deprecat
<div class="deprecation breaking-change" data-milestone="16.0">
### `name` field for `PipelineSecurityReportFinding` GraphQL type
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.1</span>
- Removal in GitLab <span class="milestone">16.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/346335).
</div>
Previously, the [`PipelineSecurityReportFinding` GraphQL type was updated](https://gitlab.com/gitlab-org/gitlab/-/issues/335372) to include a new `title` field. This field is an alias for the current `name` field, making the less specific `name` field redundant. The `name` field will be removed from the `PipelineSecurityReportFinding` type in GitLab 16.0.
</div>
<div class="deprecation breaking-change" data-milestone="16.0">
### `started` iteration state
<div class="deprecation-notes">
@ -4068,7 +4069,7 @@ We plan to continue to support the `started` state in REST API version until the
<div class="deprecation breaking-change" data-milestone="16.0">
### vulnerabilityFindingDismiss GraphQL mutation
### `vulnerabilityFindingDismiss` GraphQL mutation
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">15.5</span>
@ -5544,6 +5545,22 @@ In milestone 15.0, we will completely remove `Version` from `PackageType`.
<div class="deprecation breaking-change" data-milestone="15.0">
### `apiFuzzingCiConfigurationCreate` GraphQL mutation
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">14.6</span>
- Removal in GitLab <span class="milestone">15.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/333233).
</div>
The API Fuzzing configuration snippet is now being generated client-side and does not require an
API request anymore. We are therefore deprecating the `apiFuzzingCiConfigurationCreate` mutation
which isn't being used in GitLab anymore.
</div>
<div class="deprecation breaking-change" data-milestone="15.0">
### `artifacts:reports:cobertura` keyword
<div class="deprecation-notes">
@ -5667,22 +5684,6 @@ The `type` and `types` CI/CD keywords will be removed in GitLab 15.0. Pipelines
<div class="deprecation breaking-change" data-milestone="15.0">
### apiFuzzingCiConfigurationCreate GraphQL mutation
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">14.6</span>
- Removal in GitLab <span class="milestone">15.0</span> ([breaking change](https://docs.gitlab.com/ee/update/terminology.html#breaking-change))
- To discuss this change or learn more, see the [deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/333233).
</div>
The API Fuzzing configuration snippet is now being generated client-side and does not require an
API request anymore. We are therefore deprecating the `apiFuzzingCiConfigurationCreate` mutation
which isn't being used in GitLab anymore.
</div>
<div class="deprecation breaking-change" data-milestone="15.0">
### bundler-audit Dependency Scanning tool
<div class="deprecation-notes">

View File

@ -95,6 +95,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Beta
- Helps populate a merge request more quickly by generating a description based on the contents of the template.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text)
@ -105,6 +106,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Beta
- Helps populate a merge request more quickly by generating a description based on the code changes.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text)
@ -115,6 +117,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Beta
- Helps you understand vulnerabilities, how they can be exploited, and how to fix them.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text). If degraded performance, then Anthropic [`Claude-2.1`](https://docs.anthropic.com/claude/docs/models-overview#model-comparison).
@ -128,6 +131,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps populate an issue more quickly by generating a more in-depth description, based on a short summary you provide.
- LLM: Anthropic [`Claude-2.1`](https://docs.anthropic.com/claude/docs/models-overview#model-comparison)
@ -138,6 +142,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps everyone get up to speed by summarizing the lengthy conversations in an issue.
- LLM: Anthropic [`Claude-2.1`](https://docs.anthropic.com/claude/docs/models-overview#model-comparison)
@ -149,6 +154,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Premium and Ultimate. In the future, [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps you understand the selected code by explaining it more clearly.
- LLM: Anthropic: [`claude-3-haiku-20240307`](https://docs.anthropic.com/en/docs/models-overview#claude-3-a-new-generation-of-ai)
@ -161,6 +167,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- `glab duo ask` helps you discover or recall `git` commands when and where you need them.
- LLM: Vertex AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-chat)
@ -171,6 +178,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps make merge request handover to reviewers easier by summarizing all the comments in a merge request review.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text)
@ -182,6 +190,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps you merge more quickly by generating meaningful commit messages.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text).
@ -192,6 +201,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Helps you determine the root cause for a CI/CD job failure by analyzing the logs.
- LLM: Vertex AI Codey [`text-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text)
@ -202,6 +212,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Help resolve a vulnerability by generating a merge request that addresses it.
- LLM: Vertex AI Codey [`code-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-generation)
@ -212,6 +223,7 @@ DETAILS:
DETAILS:
**Tier:** For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
**Offering:** GitLab.com
**Status:** Experiment
- Processes and responds to your questions about your application's usage data.
- LLM: Vertex AI Codey [`codechat-bison`](https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/code-chat)
@ -222,6 +234,7 @@ DETAILS:
DETAILS:
**Tier:** GitLab.com and Self-managed: For a limited time, Ultimate. In the future, [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md). <br>GitLab Dedicated: GitLab Duo Enterprise.
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
**Status:** Experiment
- Helps you improve planning and decision-making by predicting productivity metrics and identifying anomalies across your software development lifecycle.
- LLM: Statistical forecasting

View File

@ -107,11 +107,20 @@ Because of LLM limits and performance reasons, the content of the currently
opened file is truncated:
- For code completion: to 2048 tokens (roughly 8192 characters).
- For code generation: to 50,000 characters.
- For code generation: to 142,856 tokens (roughly 500,000 characters).
Content above the cursor is prioritized over content below the cursor. The content
above the cursor is truncated from the left side, and content below the cursor
is truncated from the right side.
is truncated from the right side. These numbers represent the maximum input context
size for Code Suggestions.
## Output length
Because of LLM limits and for performance reasons, the output of Code Suggestions
is limited:
- For code completion: to 64 tokens (roughly 256 characters).
- For code generation: to 2048 tokens (roughly 7168 characters).
## Accuracy of results

View File

@ -83,7 +83,7 @@ The storage management and cleanup automation methods described in this page use
- The [`python-gitlab`](https://python-gitlab.readthedocs.io/en/stable/) library, which provides
a feature-rich programming interface.
- The `get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py` script in the [GitLab API with Python](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/) project.
- The `get_all_projects_top_level_namespace_storage_analysis_cleanup_example.py` script in the [GitLab API with Python](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/) project.
For more information about use cases for the `python-gitlab` library,
see [Efficient DevSecOps workflows: Hands-on `python-gitlab` API automation](https://about.gitlab.com/blog/2023/02/01/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation/).
@ -212,7 +212,7 @@ To implement this algorithm:
:::TabTitle GitLab CLI
```shell
export GROUP_NAME="gitlab-de"
export GROUP_NAME="gitlab-da"
# Return sub group IDs
glab api groups/$GROUP_NAME/subgroups | jq --compact-output '.[]' | jq --compact-output '.id'
@ -431,12 +431,12 @@ $ python3 get_all_projects_top_level_namespace_storage_analysis_cleanup_example.
|Project|Job|Artifact name|Artifact type|Artifact size|
|-|-|-|-|-|
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | artifacts.zip | archive | 50.0154 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | metadata.gz | metadata | 0.0001 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | job.log | trace | 0.0030 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | artifacts.zip | archive | 20.0063 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | metadata.gz | metadata | 0.0001 |
| [gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | job.log | trace | 0.0030 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | artifacts.zip | archive | 50.0154 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | metadata.gz | metadata | 0.0001 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297946 | job.log | trace | 0.0030 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | artifacts.zip | archive | 20.0063 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | metadata.gz | metadata | 0.0001 |
| [gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4](Gen Job Artifacts 4) | 4828297945 | job.log | trace | 0.0030 |
```
### Delete job artifacts in bulk
@ -607,7 +607,7 @@ In the following example that uses a Bash script:
- `jq` and the GitLab CLI are installed and authorized.
- The exported environment variable `GL_PROJECT_ID`.
The full script `get_cicd_pipelines_compare_age_threshold_example.sh` is located in the [GitLab API with Linux Shell](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-linux-shell) project.
The full script `get_cicd_pipelines_compare_age_threshold_example.sh` is located in the [GitLab API with Linux Shell](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-linux-shell) project.
```shell
#/bin/bash
@ -749,21 +749,21 @@ python3 get_all_cicd_config_artifacts_expiry.py
|Project|Job|Artifact expiry|
|-|-|-|
| [Gen Job Artifacts 4](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-4) | generator | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job10 | 10 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job1 | 1 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job30 | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | generator | 30 days |
| [Gen Job Artifacts 2](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-2) | generator | ❌ N/A |
| [Gen Job Artifacts 1](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-1) | generator | ❌ N/A |
| [Gen Job Artifacts 4](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-4) | generator | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job10 | 10 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job1 | 1 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | included-job30 | 30 days |
| [Gen Job Artifacts with expiry and included jobs](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) | generator | 30 days |
| [Gen Job Artifacts 2](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-2) | generator | ❌ N/A |
| [Gen Job Artifacts 1](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-1) | generator | ❌ N/A |
```
The `get_all_cicd_config_artifacts_expiry.py` script is located in the [GitLab API with Python project](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/).
The `get_all_cicd_config_artifacts_expiry.py` script is located in the [GitLab API with Python project](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/).
Alternatively, you can use [advanced search](search/advanced_search.md) with API requests. The following example uses the [scope: blobs](../api/search.md#scope-blobs) to searches for the string `artifacts` in all `*.yml` files:
```shell
# https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs
# https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs
export GL_PROJECT_ID=48349263
glab api --method GET projects/$GL_PROJECT_ID/search --field "scope=blobs" --field "search=expire_in filename:*.yml"
@ -798,11 +798,11 @@ export GL_PROJECT_ID=48057080
curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/projects/$GL_PROJECT_ID/registry/repositories" | jq --compact-output '.[]' | jq --compact-output '.id,.location' | jq
4435617
"registry.gitlab.com/gitlab-de/playground/container-package-gen-group/docker-alpine-generator"
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
curl --silent --header "Authorization: Bearer $GITLAB_TOKEN" "https://gitlab.com/api/v4/registry/repositories/4435617?size=true" | jq --compact-output '.id,.location,.size'
4435617
"registry.gitlab.com/gitlab-de/playground/container-package-gen-group/docker-alpine-generator"
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
3401613
```
@ -813,11 +813,11 @@ export GL_PROJECT_ID=48057080
glab api --method GET projects/$GL_PROJECT_ID/registry/repositories | jq --compact-output '.[]' | jq --compact-output '.id,.location'
4435617
"registry.gitlab.com/gitlab-de/playground/container-package-gen-group/docker-alpine-generator"
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
glab api --method GET registry/repositories/4435617 --field='size=true' | jq --compact-output '.id,.location,.size'
4435617
"registry.gitlab.com/gitlab-de/playground/container-package-gen-group/docker-alpine-generator"
"registry.gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator"
3401613
glab api --method GET projects/$GL_PROJECT_ID/registry/repositories/4435617/tags | jq --compact-output '.[]' | jq --compact-output '.name'
@ -904,7 +904,7 @@ Package registries are available [for projects](../api/packages.md#for-a-project
The following example shows fetching packages from a defined project ID using the GitLab CLI. The result set is an array of dictionary items that can be filtered with the `jq` command chain.
```shell
# https://gitlab.com/gitlab-de/playground/container-package-gen-group/generic-package-generator
# https://gitlab.com/gitlab-da/playground/container-package-gen-group/generic-package-generator
export GL_PROJECT_ID=48377643
glab api --method GET projects/$GL_PROJECT_ID/packages | jq --compact-output '.[]' | jq --compact-output '.id,.name,.package_type'
@ -1060,7 +1060,7 @@ Create a test project to generate fake artifact blobs using CI/CD job matrix bui
```yaml
include:
- remote: https://gitlab.com/gitlab-de/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml
- remote: https://gitlab.com/gitlab-da/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml
```
1. [Configure pipeline schedules](../ci/pipelines/schedules.md#add-a-pipeline-schedule).
@ -1070,7 +1070,7 @@ Alternatively, reduce the 86 MB daily generated MB to different values in the `M
```yaml
include:
- remote: https://gitlab.com/gitlab-de/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml
- remote: https://gitlab.com/gitlab-da/use-cases/efficiency/job-artifact-generator/-/raw/main/.gitlab-ci.yml
generator:
parallel:
@ -1079,7 +1079,7 @@ generator:
```
For more information, see the [Job Artifact Generator README](https://gitlab.com/gitlab-de/use-cases/efficiency/job-artifact-generator), with an [example group](https://gitlab.com/gitlab-de/playground/artifact-gen-group).
For more information, see the [Job Artifact Generator README](https://gitlab.com/gitlab-da/use-cases/efficiency/job-artifact-generator), with an [example group](https://gitlab.com/gitlab-da/playground/artifact-gen-group).
### Generate job artifacts with expiry
@ -1089,7 +1089,7 @@ The project CI/CD configuration specifies job definitions in:
- The `artifacts:expire_in` setting.
- Project files and templates.
To test the analysis scripts, the [`gen-job-artifacts-expiry-included-jobs`](https://gitlab.com/gitlab-de/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) project provides an example configuration.
To test the analysis scripts, the [`gen-job-artifacts-expiry-included-jobs`](https://gitlab.com/gitlab-da/playground/artifact-gen-group/gen-job-artifacts-expiry-included-jobs) project provides an example configuration.
```yaml
# .gitlab-ci.yml
@ -1150,7 +1150,7 @@ included-job30:
### Generate container images
The example group [`container-package-gen-group`](https://gitlab.com/gitlab-de/playground/container-package-gen-group) provides projects that:
The example group [`container-package-gen-group`](https://gitlab.com/gitlab-da/playground/container-package-gen-group) provides projects that:
- Use a base image in Dockerfile to build a new image.
- Include the `Docker.gitlab-ci.yml` template to build images on GitLab.com SaaS.
@ -1158,12 +1158,12 @@ The example group [`container-package-gen-group`](https://gitlab.com/gitlab-de/p
Example projects available to fork:
- [`docker-alpine-generator`](https://gitlab.com/gitlab-de/playground/container-package-gen-group/docker-alpine-generator)
- [`docker-python-generator`](https://gitlab.com/gitlab-de/playground/container-package-gen-group/docker-python-generator)
- [`docker-alpine-generator`](https://gitlab.com/gitlab-da/playground/container-package-gen-group/docker-alpine-generator)
- [`docker-python-generator`](https://gitlab.com/gitlab-da/playground/container-package-gen-group/docker-python-generator)
### Generate generic packages
The example project [`generic-package-generator`](https://gitlab.com/gitlab-de/playground/container-package-gen-group/generic-package-generator) provides projects that:
The example project [`generic-package-generator`](https://gitlab.com/gitlab-da/playground/container-package-gen-group/generic-package-generator) provides projects that:
- Generate a random text blob, and create a tarball with the current Unix timestamp as release version.
- Upload the tarball into the generic package registry, using the Unix timestamp as release version.
@ -1200,4 +1200,4 @@ Use the following projects to test storage usage with [cost factors for forks](u
The following resources are not officially supported. Ensure to test scripts and tutorials before running destructive cleanup commands that may not be reverted.
- Forum topic: [Storage management automation resources](https://forum.gitlab.com/t/storage-management-automation-resources/91184)
- Script: [GitLab Storage Analyzer](https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-storage-analyzer), unofficial project by the [GitLab Developer Evangelism team](https://gitlab.com/gitlab-de/). You find similar code examples in this documentation how-to here.
- Script: [GitLab Storage Analyzer](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-storage-analyzer), unofficial project by the [GitLab Developer Evangelism team](https://gitlab.com/gitlab-da/). You find similar code examples in this documentation how-to here.

View File

@ -34,7 +34,7 @@ module Banzai
end
def data_attributes_for(text, parent, object, **data)
additional_attributes = { iid: object.iid }
additional_attributes = { iid: object.iid, namespace_path: parent.full_path }
if parent.is_a?(Namespaces::ProjectNamespace) || parent.is_a?(Project)
additional_attributes[:project_path] = parent.full_path
end

View File

@ -1617,6 +1617,9 @@ msgstr ""
msgid "+ %{numberOfHiddenAssignees} more"
msgstr ""
msgid "+ %{n} more deployments"
msgstr ""
msgid "+%d more"
msgid_plural "+%d more"
msgstr[0] ""
@ -37689,6 +37692,21 @@ msgstr ""
msgid "PagesDomain|Certificate Key is too long. (Max %d bytes)"
msgstr ""
msgid "Pages|Deployments"
msgstr ""
msgid "Pages|Environment deployments"
msgstr ""
msgid "Pages|Include stopped deployments"
msgstr ""
msgid "Pages|No deployments yet"
msgstr ""
msgid "Pages|Some Pages deployments could not be loaded. Try reloading the page."
msgstr ""
msgid "Pagination|First"
msgstr ""
@ -40413,7 +40431,7 @@ msgstr ""
msgid "Profiles|Don't display activity-related personal information on your profile."
msgstr ""
msgid "Profiles|Don't display activity-related personal information on your profile. %{private_profile_help_link_start}Learn more%{private_profile_help_link_end}."
msgid "Profiles|Don't display activity-related personal information on your profile. %{private_profile_help_link_start}What information is hidden?%{private_profile_help_link_end}"
msgstr ""
msgid "Profiles|Edit Profile"
@ -40599,10 +40617,10 @@ msgstr ""
msgid "Profiles|The maximum file size allowed is 200 KiB."
msgstr ""
msgid "Profiles|This email will be displayed on your public profile."
msgid "Profiles|This email is used for web-based operations, such as edits and merges. %{commit_email_link_start}What is a private commit email?%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}."
msgid "Profiles|This email will be displayed on your public profile."
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
@ -40683,7 +40701,7 @@ msgstr ""
msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account."
msgstr ""
msgid "Profiles|Your Discord user ID. %{external_accounts_link_start}Learn more%{external_accounts_link_end}."
msgid "Profiles|Your Discord user ID."
msgstr ""
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
@ -59294,6 +59312,9 @@ msgstr ""
msgid "What happens during repository cleanup?"
msgstr ""
msgid "What information is hidden?"
msgstr ""
msgid "What is GitLab Runner?"
msgstr ""

View File

@ -575,17 +575,9 @@ RSpec.describe 'New/edit issue', :js, feature_category: :team_planning do
within_testid 'sidebar-labels' do
click_button _('Create project label')
wait_for_requests
end
page.within '.js-labels-create' do
find_by_testid('label-title-input').fill_in with: 'test label'
fill_in _('Name new label'), with: 'test label'
first('.suggest-colors-dropdown a').click
click_button 'Create'
wait_for_all_requests
end
page.within '.js-labels-list' do

View File

@ -96,7 +96,7 @@ describe('Job Log Line', () => {
it('renders a link with corresponding styles', () => {
createComponent(mockProps({ text: httpsUrl }));
expect(findLink().classes()).toEqual(['!gl-text-inherit', 'gl-text-decoration-underline']);
expect(findLink().classes()).toEqual(['!gl-text-inherit', 'gl-underline']);
});
it('renders links with queries, surrounded by questions marks', () => {

View File

@ -1,23 +1,39 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlModal } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { stubComponent } from 'helpers/stub_component';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { createAlert } from '~/alert';
import Api from '~/api';
import PipelinesTable from '~/ci/common/pipelines_table.vue';
import PipelinesTableWrapper from '~/ci/merge_requests/components/pipelines_table_wrapper.vue';
import { MR_PIPELINE_TYPE_DETACHED } from '~/ci/merge_requests/constants';
import getMergeRequestsPipelines from '~/ci/merge_requests/graphql/queries/get_merge_request_pipelines.query.graphql';
import cancelPipelineMutation from '~/ci/pipeline_details/graphql/mutations/cancel_pipeline.mutation.graphql';
import retryPipelineMutation from '~/ci/pipeline_details/graphql/mutations/retry_pipeline.mutation.graphql';
import {
HTTP_STATUS_BAD_REQUEST,
HTTP_STATUS_INTERNAL_SERVER_ERROR,
HTTP_STATUS_UNAUTHORIZED,
} from '~/lib/utils/http_status';
import { generateMRPipelinesResponse } from '../mock_data';
Vue.use(VueApollo);
jest.mock('~/alert');
const $toast = {
show: jest.fn(),
};
let wrapper;
let mergeRequestPipelinesRequest;
let cancelPipelineMutationRequest;
let retryPipelineMutationRequest;
let apolloMock;
const showMock = jest.fn();
const defaultProvide = {
graphqlPath: '/api/graphql/',
@ -34,7 +50,11 @@ const defaultProps = {
};
const createComponent = ({ mountFn = shallowMountExtended, props = {} } = {}) => {
const handlers = [[getMergeRequestsPipelines, mergeRequestPipelinesRequest]];
const handlers = [
[getMergeRequestsPipelines, mergeRequestPipelinesRequest],
[cancelPipelineMutation, cancelPipelineMutationRequest],
[retryPipelineMutation, retryPipelineMutationRequest],
];
apolloMock = createMockApollo(handlers);
@ -47,6 +67,15 @@ const createComponent = ({ mountFn = shallowMountExtended, props = {} } = {}) =>
...defaultProps,
...props,
},
mocks: {
$toast,
},
stubs: {
GlModal: stubComponent(GlModal, {
template: '<div />',
methods: { show: showMock },
}),
},
});
return waitForPromises();
@ -55,6 +84,7 @@ const createComponent = ({ mountFn = shallowMountExtended, props = {} } = {}) =>
const findEmptyState = () => wrapper.findByTestId('pipeline-empty-state');
const findErrorEmptyState = () => wrapper.findByTestId('pipeline-error-empty-state');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findModal = () => wrapper.findComponent(GlModal);
const findMrPipelinesDocsLink = () => wrapper.findByTestId('mr-pipelines-docs-link');
const findPipelinesList = () => wrapper.findComponent(PipelinesTable);
const findRunPipelineBtn = () => wrapper.findByTestId('run_pipeline_button');
@ -65,6 +95,12 @@ const findUserPermissionsDocsLink = () => wrapper.findByTestId('user-permissions
beforeEach(() => {
mergeRequestPipelinesRequest = jest.fn();
mergeRequestPipelinesRequest.mockResolvedValue(generateMRPipelinesResponse({ count: 1 }));
cancelPipelineMutationRequest = jest.fn();
cancelPipelineMutationRequest.mockResolvedValue({ data: { pipelineCancel: { errors: [] } } });
retryPipelineMutationRequest = jest.fn();
retryPipelineMutationRequest.mockResolvedValue({ data: { pipelineRetry: { errors: [] } } });
});
afterEach(() => {
@ -163,23 +199,200 @@ describe('PipelinesTableWrapper component', () => {
});
});
describe('when latest pipeline has detached flag', () => {
beforeEach(async () => {
const response = generateMRPipelinesResponse({
mergeRequestEventType: MR_PIPELINE_TYPE_DETACHED,
});
mergeRequestPipelinesRequest.mockResolvedValue(response);
await createComponent({ mountFn: mountExtended });
});
it('renders the run pipeline button', () => {
expect(findRunPipelineBtn().exists()).toBe(true);
expect(findRunPipelineBtnMobile().exists()).toBe(true);
});
});
describe('run pipeline button', () => {
describe('when latest pipeline has detached flag', () => {
describe('on click', () => {
beforeEach(() => {
const response = generateMRPipelinesResponse({
mergeRequestEventType: MR_PIPELINE_TYPE_DETACHED,
});
mergeRequestPipelinesRequest.mockResolvedValue(response);
});
describe('success', () => {
beforeEach(async () => {
jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
await createComponent({ mountFn: mountExtended });
await findRunPipelineBtn().trigger('click');
await waitForPromises();
});
it('displays a toast message during pipeline creation', () => {
expect($toast.show).toHaveBeenCalledWith('Creating pipeline.');
});
it('on desktop, shows a loading button', async () => {
await findRunPipelineBtn().trigger('click');
expect(findRunPipelineBtn().props('loading')).toBe(true);
await waitForPromises();
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
it('on mobile, shows a loading button', async () => {
await findRunPipelineBtnMobile().trigger('click');
expect(findRunPipelineBtn().props('loading')).toBe(true);
await waitForPromises();
expect(findRunPipelineBtn().props('disabled')).toBe(false);
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
});
describe('failure', () => {
const permissionsMsg = 'You do not have permission to run a pipeline on this branch.';
const defaultMsg =
'An error occurred while trying to run a new pipeline for this merge request.';
it.each`
status | message
${HTTP_STATUS_BAD_REQUEST} | ${defaultMsg}
${HTTP_STATUS_UNAUTHORIZED} | ${permissionsMsg}
${HTTP_STATUS_INTERNAL_SERVER_ERROR} | ${defaultMsg}
`('displays permissions error message', async ({ status, message }) => {
const response = { response: { status } };
jest.spyOn(Api, 'postMergeRequestPipeline').mockRejectedValue(response);
await createComponent({ mountFn: mountExtended });
await findRunPipelineBtn().trigger('click');
await waitForPromises();
expect(createAlert).toHaveBeenCalledWith({
message,
primaryButton: {
text: 'Learn more',
link: '/help/ci/pipelines/merge_request_pipelines.md',
},
});
});
});
});
describe('on click for fork merge request', () => {
beforeEach(async () => {
const response = generateMRPipelinesResponse({
mergeRequestEventType: MR_PIPELINE_TYPE_DETACHED,
});
mergeRequestPipelinesRequest.mockResolvedValue(response);
await createComponent({
props: {
canCreatePipelineInTargetProject: true,
sourceProjectFullPath: 'test/parent-project',
targetProjectFullPath: 'test/fork-project',
},
});
createComponent({ mountFn: mountExtended });
await waitForPromises();
jest.spyOn(Api, 'postMergeRequestPipeline').mockResolvedValue();
});
it('renders the run pipeline button', () => {
expect(findRunPipelineBtn().exists()).toBe(true);
expect(findRunPipelineBtnMobile().exists()).toBe(true);
it('on desktop, shows a security warning modal', async () => {
await findRunPipelineBtn().trigger('click');
expect(findModal()).not.toBeNull();
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
it('on mobile, shows a security warning modal', async () => {
await findRunPipelineBtnMobile().trigger('click');
expect(findModal()).not.toBeNull();
expect(findRunPipelineBtn().props('loading')).toBe(false);
});
});
describe('when no pipelines were created on a forked merge request', () => {
beforeEach(async () => {
const response = generateMRPipelinesResponse({ count: 0 });
mergeRequestPipelinesRequest.mockResolvedValue(response);
await createComponent({
mountFn: mountExtended,
props: {
canCreatePipelineInTargetProject: true,
sourceProjectFullPath: 'test/parent-project',
targetProjectFullPath: 'test/fork-project',
},
});
});
it('should show security modal from empty state run pipeline button', async () => {
expect(findEmptyState().exists()).toBe(true);
expect(findModal().exists()).toBe(true);
await findRunPipelineBtn().trigger('click');
expect(showMock).toHaveBeenCalled();
});
});
describe('events', () => {
const response = generateMRPipelinesResponse();
const pipeline = response.data.project.mergeRequest.pipelines.nodes[0];
beforeEach(async () => {
mergeRequestPipelinesRequest.mockResolvedValue(response);
await createComponent();
});
describe('When cancelling a pipeline', () => {
it('execute the cancel graphql mutation', async () => {
expect(cancelPipelineMutationRequest.mock.calls).toHaveLength(0);
findPipelinesList().vm.$emit('cancel-pipeline', pipeline);
await waitForPromises();
expect(cancelPipelineMutationRequest.mock.calls[0]).toEqual([
{ id: 'gid://gitlab/Ci::Pipeline/0' },
]);
});
});
describe('When retrying a pipeline', () => {
it('sends the retry action graphql mutation', async () => {
expect(retryPipelineMutationRequest.mock.calls).toHaveLength(0);
findPipelinesList().vm.$emit('retry-pipeline', pipeline);
await waitForPromises();
expect(retryPipelineMutationRequest.mock.calls[0]).toEqual([
{ id: 'gid://gitlab/Ci::Pipeline/0' },
]);
});
});
describe('When refreshing a pipeline', () => {
it('calls the apollo query again', async () => {
expect(mergeRequestPipelinesRequest.mock.calls).toHaveLength(1);
findPipelinesList().vm.$emit('refresh-pipelines-table');
await waitForPromises();
expect(mergeRequestPipelinesRequest.mock.calls).toHaveLength(2);
});
});
});
});

View File

@ -15,6 +15,8 @@ const createMergeRequestPipelines = ({ mergeRequestEventType = 'MERGE_TRAIN', co
failureReason: null,
yamlErrors: false,
latest: true,
retryable: true,
cancelable: false,
commit: {
id: 'gid://gitlab/Ci::Commit/1',
title:

View File

@ -85,7 +85,7 @@ describe('CompareVersions', () => {
expect(treeListBtn.exists()).toBe(true);
expect(treeListBtn.attributes('aria-label')).toBe('Hide file browser');
expect(treeListBtn.props('icon')).toBe('file-tree');
expect(treeListBtn.props('icon')).toBe('sidebar');
});
it('should render comparison dropdowns with correct values', () => {

View File

@ -0,0 +1,248 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlLoadingIcon } from '@gitlab/ui';
import PagesDeployments from '~/gitlab_pages/components/deployments.vue';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import getProjectPagesDeploymentsQuery from '~/gitlab_pages/queries/get_project_pages_deployments.graphql';
import waitForPromises from 'helpers/wait_for_promises';
describe('PagesDeployments', () => {
Vue.use(VueApollo);
let wrapper;
const getProjectPagesDeploymentsQueryHandler = jest
.fn()
.mockImplementation(({ versioned, after }) => {
return {
data: {
project: {
id: 'gid://gitlab/Project/1',
pagesDeployments: {
__typename: 'PagesDeploymentConnection',
count: 14,
pageInfo: {
__typename: 'PageInfo',
startCursor:
'eyJjcmVhdGVkX2F0IjoiMjAyNC0wNS0yMiAxMzozNzoyMi40MTk4MzcwMDAgKzAwMDAiLCJpZCI6IjQwIn0',
endCursor:
'eyJjcmVhdGVkX2F0IjoiMjAyNC0wNS0yMiAxMzozNzoyMi40MTk4MzcwMDAgKzAwMDAiLCJpZCI6IjQwIn0',
hasNextPage: !after, // This mimics this result being a second page
hasPreviousPage: Boolean(after),
},
nodes: Array(after ? 4 : 10)
.fill(null)
.map((_, i) => ({
__typename: 'PagesDeployment',
id: `gid://gitlab/PagesDeployment/${!after ? i + 1 : i + 11}`,
active: true,
rootDirectory: 'public',
ciBuildId: '499',
createdAt: '2024-05-22T13:37:22Z',
deletedAt: null,
fileCount: 3,
pathPrefix: versioned ? '_stg' : '',
size: 1082,
updatedAt: '2024-05-23T11:48:34Z',
url: 'http://abc.pages.io/',
})),
},
},
},
};
});
const findAllPrimaryDeployments = () => wrapper.findAllByTestId('primary-deployment');
const findAllEnvironmentDeployments = () => wrapper.findAllByTestId('environment-deployment');
const findPrimaryDeploymentsLoadMoreComponent = () =>
wrapper.findByTestId('load-more-primary-deployments');
const findEnvironmentDeploymentsLoadMoreComponent = () =>
wrapper.findByTestId('load-more-environment-deployments');
const createComponent = () => {
wrapper = shallowMountExtended(PagesDeployments, {
apolloProvider: createMockApollo([
[getProjectPagesDeploymentsQuery, getProjectPagesDeploymentsQueryHandler],
]),
provide: {
projectFullPath: 'my-group/my-project',
},
});
};
describe('default behaviour', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('renders the component', () => {
expect(wrapper.exists()).toBe(true);
});
it('calls the query 2 times', () => {
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledTimes(2);
});
it('calls the primary deployments query', () => {
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
active: true,
fullPath: 'my-group/my-project',
versioned: false,
}),
);
});
it('calls the environments deployments query', () => {
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
active: true,
fullPath: 'my-group/my-project',
versioned: true,
}),
);
});
it('renders a list for all primary deployments', () => {
expect(findAllPrimaryDeployments().length).toBe(10);
});
it('renders a list for all environment deployments', () => {
expect(findAllEnvironmentDeployments().length).toBe(10);
});
});
describe('when the user loads more deployments', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('does include the "load more" component for primary deployments', () => {
expect(findPrimaryDeploymentsLoadMoreComponent().exists()).toBe(true);
});
it('does include the "load more" component for environment deployments', () => {
expect(findEnvironmentDeploymentsLoadMoreComponent().exists()).toBe(true);
});
it('fetches more primary deployments', async () => {
findPrimaryDeploymentsLoadMoreComponent().vm.$emit('load-more');
await nextTick();
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
versioned: false,
after: expect.any(String),
}),
);
await waitForPromises();
expect(findPrimaryDeploymentsLoadMoreComponent().exists()).toBe(false);
});
it('fetches more environment deployments', async () => {
findEnvironmentDeploymentsLoadMoreComponent().vm.$emit('load-more');
await nextTick();
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
versioned: true,
after: expect.any(String),
}),
);
await waitForPromises();
expect(findEnvironmentDeploymentsLoadMoreComponent().exists()).toBe(false);
});
});
describe('when the "Include stopped deployments" toggle is switched on', () => {
beforeEach(async () => {
createComponent();
wrapper.findByTestId('show-inactive-toggle').vm.$emit('change', true);
await waitForPromises();
});
it('fetches the primaryDeployments with the "active" filter set to undefined', () => {
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
versioned: false,
active: undefined,
}),
);
});
it('fetches the environmentDeployments with the "active" filter set to undefined', () => {
expect(getProjectPagesDeploymentsQueryHandler).toHaveBeenCalledWith(
expect.objectContaining({
versioned: true,
active: undefined,
}),
);
});
});
describe.each`
type | testid
${'primary'} | ${'primary-deployment'}
${'environment'} | ${'environment-deployment'}
`('if a $type PagesDeployment child emits an error', ({ testid }) => {
const errorMessage = 'Foo';
beforeEach(async () => {
createComponent();
await waitForPromises();
wrapper
.findByTestId(testid)
.vm.$emit('error', { id: 'gid://gitlab/PagesDeployment/1', message: errorMessage });
await nextTick();
});
it('shows an alert with this message', () => {
expect(wrapper.findByTestId('alert').text()).toBe(errorMessage);
});
});
describe('if there are no deployments yet', () => {
beforeEach(async () => {
getProjectPagesDeploymentsQueryHandler.mockImplementation(() => ({
data: {
project: {
id: 'gid://gitlab/Project/1',
pagesDeployments: {
count: 0,
pageInfo: {
startCursor: null,
endCursor: null,
hasNextPage: false,
hasPreviousPage: false,
},
edges: [],
},
},
},
}));
createComponent();
await waitForPromises();
});
it('displays an empty state text', () => {
expect(wrapper.text()).toContain('No deployments yet');
});
});
describe('loading state', () => {
beforeEach(() => {
getProjectPagesDeploymentsQueryHandler.mockImplementation(() => Promise);
createComponent();
});
it('displays the loading icon', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
});

View File

@ -0,0 +1,23 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PagesEdit from '~/gitlab_pages/components/edit.vue';
import PagesDeployments from '~/gitlab_pages/components/deployments.vue';
Vue.use(VueApollo);
describe('PagesEdit', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMountExtended(PagesEdit);
};
beforeEach(() => {
createComponent();
});
it('mounts the PagesDeployments component', () => {
expect(wrapper.findComponent(PagesDeployments).exists()).toBe(true);
});
});

View File

@ -1,25 +1,23 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import PagesEdit from '~/gitlab_pages/components/pages_edit.vue';
import PagesEdit from '~/gitlab_pages/components/edit.vue';
import PagesDeployments from '~/gitlab_pages/components/deployments.vue';
Vue.use(VueApollo);
describe('PagesEdit', () => {
let wrapper;
const props = {};
const createComponent = () => {
wrapper = shallowMountExtended(PagesEdit, {
propsData: props,
});
wrapper = shallowMountExtended(PagesEdit);
};
beforeEach(() => {
createComponent();
});
it('shows the page header', () => {
expect(wrapper.find('h1').text()).toBe('Pages');
it('mounts the PagesDeployments component', () => {
expect(wrapper.findComponent(PagesDeployments).exists()).toBe(true);
});
});

View File

@ -0,0 +1,42 @@
export const primaryDeployment = {
id: 'gid://gitlab/PagesDeployment/1',
active: true,
pathPrefix: null,
url: 'https://example.com',
createdAt: '2023-04-01T12:00:00Z',
ciBuildId: 123,
rootDirectory: 'public',
fileCount: 100,
size: 1024,
updatedAt: '2023-04-02T12:05:00Z',
deletedAt: null,
};
export const environmentDeployment = {
...primaryDeployment,
pathPrefix: '_stg',
};
export const deleteDeploymentResult = {
deletePagesDeployment: {
errors: [],
pagesDeployment: {
id: 1,
active: false,
deletedAt: '2023-04-02T12:10:00Z',
updatedAt: '2023-04-02T12:10:00Z',
},
},
};
export const restoreDeploymentResult = {
restorePagesDeployment: {
errors: [],
pagesDeployment: {
id: 1,
active: true,
deletedAt: null,
updatedAt: '2023-04-02T12:15:00Z',
},
},
};

View File

@ -1,65 +1,121 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import mockData from 'test_fixtures/issues/related_merge_requests.json';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import RelatedMergeRequests from '~/issues/related_merge_requests/components/related_merge_requests.vue';
import createStore from '~/issues/related_merge_requests/store/index';
import relatedMergeRequestsQuery from '~/issues/related_merge_requests/queries/related_merge_requests.query.graphql';
import RelatedIssuableItem from '~/issuable/components/related_issuable_item.vue';
const API_ENDPOINT = '/api/v4/projects/2/issues/33/related_merge_requests';
Vue.use(VueApollo);
const mockData = {
data: {
project: {
id: 1,
issue: {
id: 1,
relatedMergeRequests: {
count: 2,
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: 'eyJpZCI6IjEwNjEwIn0',
endCursor: 'eyJpZCI6IjM0In0',
__typename: 'PageInfo',
},
nodes: [
{
id: 'gid://gitlab/MergeRequest/42',
reference: '!18',
state: 'merged',
title: ':star: hello world',
createdAt: '2023-03-03T12:25:40Z',
mergedAt: '2023-04-27T12:01:04Z',
webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-test/-/merge_requests/18',
milestone: {
expired: false,
id: 'gid://gitlab/Milestone/5',
state: 'active',
title: 'v4.0',
__typename: 'Milestone',
},
assignees: {
nodes: [],
__typename: 'MergeRequestAssigneeConnection',
},
headPipeline: null,
__typename: 'MergeRequest',
},
{
id: 'gid://gitlab/MergeRequest/34',
reference: '!10',
state: 'opened',
title: 'Draft: :parrot: Title with custom emoji',
createdAt: '2022-09-23T14:33:32Z',
mergedAt: null,
webUrl: 'http://gdk.test:3000/gitlab-org/gitlab-test/-/merge_requests/10',
milestone: {
expired: false,
id: 'gid://gitlab/Milestone/5',
state: 'active',
title: 'v4.0',
__typename: 'Milestone',
},
assignees: {
nodes: [
{
id: 'gid://gitlab/User/1',
avatarUrl:
'https://www.gravatar.com/avatar/258d8dc916db8cea2cafb6c3cd0cb0246efe061421dbd83ec3a350428cabda4f?s=80\u0026d=identicon',
name: 'Administrator',
username: 'root',
webUrl: 'http://gdk.test:3000/root',
webPath: '/root',
__typename: 'MergeRequestAssignee',
},
],
__typename: 'MergeRequestAssigneeConnection',
},
headPipeline: {
id: 1,
detailedStatus: {
id: 'success-66-66',
icon: 'status_success',
text: 'Passed',
detailsPath: '/root/test-ci/-/pipelines/66',
__typename: 'DetailedStatus',
},
__typename: 'Pipeline',
},
__typename: 'MergeRequest',
},
],
__typename: 'MergeRequestConnection',
},
__typename: 'Issue',
},
__typename: 'Project',
},
},
};
describe('RelatedMergeRequests', () => {
let wrapper;
let mock;
beforeEach(() => {
// put the fixture in DOM as the component expects
document.body.innerHTML = `<div id="js-issuable-app"></div>`;
document.getElementById('js-issuable-app').dataset.initial = JSON.stringify(mockData);
mock = new MockAdapter(axios);
mock.onGet(`${API_ENDPOINT}?per_page=100`).reply(HTTP_STATUS_OK, mockData, { 'x-total': 2 });
beforeEach(async () => {
const apolloProvider = createMockApollo([
[relatedMergeRequestsQuery, jest.fn().mockResolvedValue(mockData)],
]);
wrapper = shallowMount(RelatedMergeRequests, {
store: createStore(),
apolloProvider,
propsData: {
endpoint: API_ENDPOINT,
projectNamespace: 'gitlab-org',
projectPath: 'gitlab-ce',
iid: '1',
},
});
return axios.waitForAll();
});
afterEach(() => {
mock.restore();
});
describe('methods', () => {
describe('getAssignees', () => {
const assignees = [{ name: 'foo' }, { name: 'bar' }];
describe('when there is assignees array', () => {
it('should return assignees array', () => {
const mr = { assignees };
expect(wrapper.vm.getAssignees(mr)).toEqual(assignees);
});
});
it('should return an array with single assignee', () => {
const mr = { assignee: assignees[0] };
expect(wrapper.vm.getAssignees(mr)).toEqual([assignees[0]]);
});
it('should return empty array when assignee is not set', () => {
expect(wrapper.vm.getAssignees({})).toEqual([]);
expect(wrapper.vm.getAssignees({ assignee: null })).toEqual([]);
});
});
await waitForPromises();
});
describe('template', () => {
@ -68,17 +124,16 @@ describe('RelatedMergeRequests', () => {
expect(wrapper.findAllComponents(RelatedIssuableItem)).toHaveLength(2);
const props = wrapper.findAllComponents(RelatedIssuableItem).at(1).props();
const data = mockData[1];
const data = mockData.data.project.issue.relatedMergeRequests.nodes[1];
expect(props.idKey).toEqual(data.id);
expect(props.idKey).toEqual(34);
expect(props.pathIdSeparator).toEqual('!');
expect(props.pipelineStatus).toBe(data.head_pipeline.detailed_status);
expect(props.assignees).toEqual([data.assignee]);
expect(props.assignees).toEqual(data.assignees.nodes);
expect(props.isMergeRequest).toBe(true);
expect(props.confidential).toEqual(false);
expect(props.title).toEqual(data.title);
expect(props.state).toEqual(data.state);
expect(props.createdAt).toEqual(data.created_at);
expect(props.createdAt).toEqual(data.createdAt);
});
});
});

View File

@ -1,108 +0,0 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import { createAlert } from '~/alert';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import * as actions from '~/issues/related_merge_requests/store/actions';
import * as types from '~/issues/related_merge_requests/store/mutation_types';
jest.mock('~/alert');
describe('RelatedMergeRequest store actions', () => {
let state;
let mock;
beforeEach(() => {
state = {
apiEndpoint: '/api/related_merge_requests',
};
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('setInitialState', () => {
it('commits types.SET_INITIAL_STATE with given props', () => {
const props = { a: 1, b: 2 };
return testAction(
actions.setInitialState,
props,
{},
[{ type: types.SET_INITIAL_STATE, payload: props }],
[],
);
});
});
describe('requestData', () => {
it('commits types.REQUEST_DATA', () => {
return testAction(actions.requestData, null, {}, [{ type: types.REQUEST_DATA }], []);
});
});
describe('receiveDataSuccess', () => {
it('commits types.RECEIVE_DATA_SUCCESS with data', () => {
const data = { a: 1, b: 2 };
return testAction(
actions.receiveDataSuccess,
data,
{},
[{ type: types.RECEIVE_DATA_SUCCESS, payload: data }],
[],
);
});
});
describe('receiveDataError', () => {
it('commits types.RECEIVE_DATA_ERROR', () => {
return testAction(
actions.receiveDataError,
null,
{},
[{ type: types.RECEIVE_DATA_ERROR }],
[],
);
});
});
describe('fetchMergeRequests', () => {
describe('for a successful request', () => {
it('should dispatch success action', () => {
const data = { a: 1 };
mock
.onGet(`${state.apiEndpoint}?per_page=100`)
.replyOnce(HTTP_STATUS_OK, data, { 'x-total': 2 });
return testAction(
actions.fetchMergeRequests,
null,
state,
[],
[{ type: 'requestData' }, { type: 'receiveDataSuccess', payload: { data, total: 2 } }],
);
});
});
describe('for a failing request', () => {
it('should dispatch error action', async () => {
mock.onGet(`${state.apiEndpoint}?per_page=100`).replyOnce(HTTP_STATUS_BAD_REQUEST);
await testAction(
actions.fetchMergeRequests,
null,
state,
[],
[{ type: 'requestData' }, { type: 'receiveDataError' }],
);
expect(createAlert).toHaveBeenCalledTimes(1);
expect(createAlert).toHaveBeenCalledWith({
message: expect.stringMatching('Something went wrong'),
});
});
});
});
});

View File

@ -1,49 +0,0 @@
import * as types from '~/issues/related_merge_requests/store/mutation_types';
import mutations from '~/issues/related_merge_requests/store/mutations';
describe('RelatedMergeRequests Store Mutations', () => {
describe('SET_INITIAL_STATE', () => {
it('should set initial state according to given data', () => {
const apiEndpoint = '/api';
const state = {};
mutations[types.SET_INITIAL_STATE](state, { apiEndpoint });
expect(state.apiEndpoint).toEqual(apiEndpoint);
});
});
describe('REQUEST_DATA', () => {
it('should set loading flag', () => {
const state = {};
mutations[types.REQUEST_DATA](state);
expect(state.isFetchingMergeRequests).toEqual(true);
});
});
describe('RECEIVE_DATA_SUCCESS', () => {
it('should set loading flag and data', () => {
const state = {};
const mrs = [1, 2, 3];
mutations[types.RECEIVE_DATA_SUCCESS](state, { data: mrs, total: mrs.length });
expect(state.isFetchingMergeRequests).toEqual(false);
expect(state.mergeRequests).toEqual(mrs);
expect(state.totalCount).toEqual(mrs.length);
});
});
describe('RECEIVE_DATA_ERROR', () => {
it('should set loading and error flags', () => {
const state = {};
mutations[types.RECEIVE_DATA_ERROR](state);
expect(state.isFetchingMergeRequests).toEqual(false);
expect(state.hasErrorFetchingMergeRequests).toEqual(true);
});
});
});

View File

@ -23,7 +23,7 @@ exports[`publish_method renders 1`] = `
variant="current"
/>
<gl-link-stub
class="gl-mr-2 gl-text-decoration-underline"
class="gl-mr-2 gl-underline"
data-testid="pipeline-sha"
href="/namespace14/project14/-/commit/b83d6e391c22777fca1ed3012fce84f633d7fed0"
>

View File

@ -1,4 +1,4 @@
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { GlAlert, GlFormInput, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
@ -7,7 +7,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { workspaceLabelsQueries, workspaceCreateLabelMutation } from '~/sidebar/queries/constants';
import DropdownContentsCreateView from '~/sidebar/components/labels/labels_select_widget/dropdown_contents_create_view.vue';
import SibebarColorPicker from '~/sidebar/components/sidebar_color_picker.vue';
import SidebarColorPicker from '~/sidebar/components/sidebar_color_picker.vue';
import { DEFAULT_LABEL_COLOR } from '~/sidebar/components/labels/labels_select_widget/constants';
import {
mockCreateLabelResponse as createAbuseReportLabelSuccessfulResponse,
@ -50,16 +50,16 @@ const createLabelErrorHandler = jest.fn().mockRejectedValue('Houston, we have a
describe('DropdownContentsCreateView', () => {
let wrapper;
const findSibebarColorPicker = () => wrapper.findComponent(SibebarColorPicker);
const findSidebarColorPicker = () => wrapper.findComponent(SidebarColorPicker);
const findCreateButton = () => wrapper.find('[data-testid="create-button"]');
const findCancelButton = () => wrapper.find('[data-testid="cancel-button"]');
const findLabelTitleInput = () => wrapper.find('[data-testid="label-title-input"]');
const findLabelTitleInput = () => wrapper.findComponent(GlFormInput);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const fillLabelAttributes = () => {
findLabelTitleInput().vm.$emit('input', 'Test title');
findSibebarColorPicker().vm.$emit('input', '#009966');
findSidebarColorPicker().vm.$emit('input', '#009966');
};
const createComponent = ({
@ -97,7 +97,7 @@ describe('DropdownContentsCreateView', () => {
it('disables a Create button if label title is not set', async () => {
createComponent();
findSibebarColorPicker().vm.$emit('input', '#fff');
findSidebarColorPicker().vm.$emit('input', '#fff');
await nextTick();
expect(findCreateButton().props('disabled')).toBe(true);
@ -106,7 +106,7 @@ describe('DropdownContentsCreateView', () => {
it('disables a Create button if color is not set', async () => {
createComponent();
findLabelTitleInput().vm.$emit('input', 'Test title');
findSibebarColorPicker().vm.$emit('input', '');
findSidebarColorPicker().vm.$emit('input', '');
await nextTick();
expect(findCreateButton().props('disabled')).toBe(true);
@ -144,17 +144,17 @@ describe('DropdownContentsCreateView', () => {
findCreateButton().vm.$emit('click');
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
expect(findCreateButton().props('loading')).toBe(true);
});
it('does not loader spinner after mutation is resolved', async () => {
findCreateButton().vm.$emit('click');
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
expect(findCreateButton().props('loading')).toBe(true);
await waitForPromises();
expect(findLoadingIcon().exists()).toBe(false);
expect(findCreateButton().props('loading')).toBe(false);
});
});

View File

@ -11,8 +11,8 @@ describe('generateText', () => {
${'%{small_start}Hello world%{small_end}'} | ${'<span class="gl-font-sm gl-text-gray-700">Hello world</span>'}
${'%{strong_start}%{danger_start}Hello world%{danger_end}%{strong_end}'} | ${'<span class="gl-font-bold"><span class="gl-font-bold gl-text-red-500">Hello world</span></span>'}
${'%{no_exist_start}Hello world%{no_exist_end}'} | ${'Hello world'}
${{ text: 'Hello world', href: 'http://www.example.com' }} | ${'<a class="gl-text-decoration-underline" href="http://www.example.com">Hello world</a>'}
${{ prependText: 'Hello', text: 'world', href: 'http://www.example.com' }} | ${'Hello <a class="gl-text-decoration-underline" href="http://www.example.com">world</a>'}
${{ text: 'Hello world', href: 'http://www.example.com' }} | ${'<a class="gl-underline" href="http://www.example.com">Hello world</a>'}
${{ prependText: 'Hello', text: 'world', href: 'http://www.example.com' }} | ${'Hello <a class="gl-underline" href="http://www.example.com">world</a>'}
${['array']} | ${null}
`('generates $expectedText from $text', ({ text, expectedText }) => {
expect(generateText(text)).toBe(expectedText);

View File

@ -10,8 +10,8 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
IssuesHelper
end
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
let_it_be(:project) { create(:project, :public) }
let_it_be_with_reload(:issue) { create(:issue, project: project) }
let(:issue_path) { "/#{issue.project.namespace.path}/#{issue.project.path}/-/issues/#{issue.iid}" }
let(:issue_url) { "http://#{Gitlab.config.gitlab.host}#{issue_path}" }
@ -102,6 +102,14 @@ RSpec.describe Banzai::Filter::References::IssueReferenceFilter, feature_categor
expect(link.attr('data-project')).to eq project.id.to_s
end
it 'includes a data-namespace-path attribute' do
doc = reference_filter("Issue #{written_reference}")
link = doc.css('a').first
expect(link).to have_attribute('data-namespace-path')
expect(link.attr('data-namespace-path')).to eq(project.full_path)
end
it 'includes a data-issue attribute' do
doc = reference_filter("See #{written_reference}")
link = doc.css('a').first

View File

@ -183,6 +183,7 @@ RSpec.describe Banzai::Filter::References::WorkItemReferenceFilter, feature_cate
link = doc.css('a').first
expect(link.attr('data-project-path')).to eq cross_project.full_path
expect(link.attr('data-namespace-path')).to eq cross_project.full_path
expect(link.attr('data-iid')).to eq work_item.iid.to_s
end
end
@ -242,6 +243,7 @@ RSpec.describe Banzai::Filter::References::WorkItemReferenceFilter, feature_cate
link = doc.css('a').first
expect(link.attr('data-project-path')).to eq cross_project.full_path
expect(link.attr('data-namespace-path')).to eq cross_project.full_path
expect(link.attr('data-iid')).to eq work_item.iid.to_s
end
end
@ -253,6 +255,14 @@ RSpec.describe Banzai::Filter::References::WorkItemReferenceFilter, feature_cate
context 'when work item exists at the group level' do
let_it_be(:work_item) { create(:work_item, :group_level, namespace: group) }
it 'includes data attributes for issuable popover' do
doc = reference_filter("See #{work_item_url}", context)
link = doc.css('a').first
expect(link.attr('data-namespace-path')).to eq(group.full_path)
expect(link.attr('data-iid')).to eq(work_item.iid.to_s)
end
it 'links to a valid group level work item by URL' do
doc = reference_filter("See #{work_item_url}", context)

View File

@ -19,7 +19,7 @@ RSpec.describe 'Timeline Events', feature_category: :incident_management do
"<a href=\"/#{project.full_path}/-/issues/#{incident.iid}\" data-reference-type=\"issue\" " \
"data-original=\"##{incident.iid}\" data-link=\"false\" data-link-reference=\"false\" " \
"data-issue=\"#{incident.id}\" data-project=\"#{project.id}\" data-iid=\"#{incident.iid}\" " \
"data-project-path=\"#{project.full_path}\" " \
"data-namespace-path=\"#{project.full_path}\" data-project-path=\"#{project.full_path}\" " \
"data-issue-type=\"incident\" data-container=\"body\" data-placement=\"top\" " \
"title=\"#{incident.title}\" class=\"gfm gfm-issue\">##{incident.iid}</a>"
end

View File

@ -2951,7 +2951,6 @@
- './ee/spec/workers/security/sync_scan_policies_worker_spec.rb'
- './ee/spec/workers/security/track_secure_scans_worker_spec.rb'
- './ee/spec/workers/set_user_status_based_on_user_cap_setting_worker_spec.rb'
- './ee/spec/workers/store_security_reports_worker_spec.rb'
- './ee/spec/workers/sync_seat_link_request_worker_spec.rb'
- './ee/spec/workers/sync_seat_link_worker_spec.rb'
- './ee/spec/workers/todos_destroyer/confidential_epic_worker_spec.rb'

View File

@ -109,13 +109,10 @@ RSpec.shared_examples 'labels sidebar widget' do
it 'shows error message if label title is taken' do
page.within(labels_widget) do
fill_in 'Name new label', with: development.title
page.find('.suggested-colors a', match: :first).click
page.find('button', text: 'Create').click
wait_for_requests
click_link 'Magenta-pink'
click_button 'Create'
page.within('.dropdown-input') do
expect(page.find('.gl-alert')).to have_content 'Title'
end
expect(page).to have_css '.gl-alert', text: 'Title'
end
end
end

View File

@ -21,9 +21,11 @@ RSpec.describe 'user_settings/profiles/show', feature_category: :user_profile do
expect(rendered).to have_field('user_name', with: user.name)
expect(rendered).to have_field('user_id', with: user.id)
expected_link = help_page_path('user/profile/index', anchor: 'change-the-email-displayed-on-your-commits')
expected_link = help_page_path('user/profile/index',
anchor: 'use-an-automatically-generated-private-commit-email')
expected_link_html = "<a href=\"#{expected_link}\" target=\"_blank\" " \
"rel=\"noopener noreferrer\">#{_('Learn more')}</a>."
"rel=\"noopener noreferrer\">#{s_('Profiles|What is a private commit email?')}</a>"
expect(rendered.include?(expected_link_html)).to eq(true)
end
@ -61,7 +63,7 @@ RSpec.describe 'user_settings/profiles/show', feature_category: :user_profile do
render
expect(rendered).to render_template('user_settings/profiles/_private_profile')
expect(rendered).to have_link 'Learn more',
expect(rendered).to have_link 'What information is hidden?',
href: help_page_path('user/profile/index', anchor: 'make-your-user-profile-page-private')
end
end