Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-03-29 12:09:41 +00:00
parent f33d86ffac
commit 0dd9f7b364
19 changed files with 156 additions and 133 deletions

View File

@ -44,10 +44,10 @@ export function createProject(projectData) {
}); });
} }
export function deleteProject(projectId) { export function deleteProject(projectId, params) {
const url = buildApiUrl(PROJECT_PATH).replace(':id', projectId); const url = buildApiUrl(PROJECT_PATH).replace(':id', projectId);
return axios.delete(url); return axios.delete(url, { params });
} }
export function importProjectMembers(sourceId, targetId) { export function importProjectMembers(sourceId, targetId) {

View File

@ -7,6 +7,7 @@
export const organizationProjects = [ export const organizationProjects = [
{ {
id: 'gid://gitlab/Project/8', id: 'gid://gitlab/Project/8',
fullPath: 'project/8',
nameWithNamespace: 'Twitter / Typeahead.Js', nameWithNamespace: 'Twitter / Typeahead.Js',
organizationEditPath: '/-/organizations/default/projects/twitter/Typeahead.Js/edit', organizationEditPath: '/-/organizations/default/projects/twitter/Typeahead.Js/edit',
webUrl: 'http://127.0.0.1:3000/twitter/Typeahead.Js', webUrl: 'http://127.0.0.1:3000/twitter/Typeahead.Js',
@ -40,6 +41,7 @@ export const organizationProjects = [
}, },
{ {
id: 'gid://gitlab/Project/7', id: 'gid://gitlab/Project/7',
fullPath: 'project/7',
nameWithNamespace: 'Flightjs / Flight', nameWithNamespace: 'Flightjs / Flight',
organizationEditPath: '/-/organizations/default/projects/flightjs/Flight/edit', organizationEditPath: '/-/organizations/default/projects/flightjs/Flight/edit',
webUrl: 'http://127.0.0.1:3000/flightjs/Flight', webUrl: 'http://127.0.0.1:3000/flightjs/Flight',
@ -73,6 +75,7 @@ export const organizationProjects = [
}, },
{ {
id: 'gid://gitlab/Project/6', id: 'gid://gitlab/Project/6',
fullPath: 'project/6',
nameWithNamespace: 'Jashkenas / Underscore', nameWithNamespace: 'Jashkenas / Underscore',
organizationEditPath: '/-/organizations/default/projects/jashkenas/Underscore/edit', organizationEditPath: '/-/organizations/default/projects/jashkenas/Underscore/edit',
webUrl: 'http://127.0.0.1:3000/jashkenas/Underscore', webUrl: 'http://127.0.0.1:3000/jashkenas/Underscore',
@ -106,6 +109,7 @@ export const organizationProjects = [
}, },
{ {
id: 'gid://gitlab/Project/5', id: 'gid://gitlab/Project/5',
fullPath: 'project/5',
nameWithNamespace: 'Commit451 / Lab Coat', nameWithNamespace: 'Commit451 / Lab Coat',
organizationEditPath: '/-/organizations/default/projects/Commit451/lab-coat/edit', organizationEditPath: '/-/organizations/default/projects/Commit451/lab-coat/edit',
webUrl: 'http://127.0.0.1:3000/Commit451/lab-coat', webUrl: 'http://127.0.0.1:3000/Commit451/lab-coat',
@ -139,6 +143,7 @@ export const organizationProjects = [
}, },
{ {
id: 'gid://gitlab/Project/1', id: 'gid://gitlab/Project/1',
fullPath: 'project/1',
nameWithNamespace: 'Toolbox / Gitlab Smoke Tests', nameWithNamespace: 'Toolbox / Gitlab Smoke Tests',
organizationEditPath: '/-/organizations/default/projects/toolbox/gitlab-smoke-tests/edit', organizationEditPath: '/-/organizations/default/projects/toolbox/gitlab-smoke-tests/edit',
webUrl: 'http://127.0.0.1:3000/toolbox/gitlab-smoke-tests', webUrl: 'http://127.0.0.1:3000/toolbox/gitlab-smoke-tests',

View File

@ -1,5 +1,6 @@
fragment BaseProject on Project { fragment BaseProject on Project {
id id
fullPath
archived archived
nameWithNamespace nameWithNamespace
organizationEditPath organizationEditPath

View File

@ -113,10 +113,7 @@ export default {
<div class="hide-collapsed gl-line-height-20 gl-font-weight-bold"> <div class="hide-collapsed gl-line-height-20 gl-font-weight-bold">
{{ contactsLabel }} {{ contactsLabel }}
</div> </div>
<div <div v-if="shouldShowContacts" class="hide-collapsed gl-display-flex gl-flex-wrap gl-mt-2">
class="hide-collapsed gl-display-flex gl-flex-wrap"
:class="contacts.length > 0 ? 'gl-mt-2' : ''"
>
<div <div
v-for="(contact, index) in contacts" v-for="(contact, index) in contacts"
:id="`contact_container_${index}`" :id="`contact_container_${index}`"
@ -137,5 +134,12 @@ export default {
</gl-popover> </gl-popover>
</div> </div>
</div> </div>
<div
v-else
data-testid="crm-empty-message"
class="gl-display-flex gl-align-items-center hide-collapsed gl-text-gray-500"
>
{{ __('To add active contacts, use /add_contacts.') }}
</div>
</div> </div>
</template> </template>

View File

@ -4,7 +4,6 @@ import {
GlSprintf, GlSprintf,
GlModal, GlModal,
GlAlert, GlAlert,
GlLoadingIcon,
GlDisclosureDropdown, GlDisclosureDropdown,
GlDisclosureDropdownGroup, GlDisclosureDropdownGroup,
GlDisclosureDropdownItem, GlDisclosureDropdownItem,
@ -40,7 +39,6 @@ export default {
GlSprintf, GlSprintf,
GlModal, GlModal,
GlAlert, GlAlert,
GlLoadingIcon,
GlDisclosureDropdown, GlDisclosureDropdown,
GlDisclosureDropdownGroup, GlDisclosureDropdownGroup,
GlDisclosureDropdownItem, GlDisclosureDropdownItem,
@ -347,7 +345,6 @@ export default {
</div> </div>
<gl-modal <gl-modal
ref="deleteModal"
v-model="isDeleteModalVisible" v-model="isDeleteModalVisible"
modal-id="delete-modal" modal-id="delete-modal"
:title="__('Delete snippet modal')" :title="__('Delete snippet modal')"
@ -374,11 +371,10 @@ export default {
<gl-button <gl-button
variant="danger" variant="danger"
category="primary" category="primary"
:disabled="isLoading" :loading="isLoading"
data-testid="delete-snippet-button" data-testid="delete-snippet-button"
@click="deleteSnippet" @click="deleteSnippet"
> >
<gl-loading-icon v-if="isLoading" size="sm" inline />
{{ __('Delete snippet') }} {{ __('Delete snippet') }}
</gl-button> </gl-button>
</template> </template>

View File

@ -73,7 +73,7 @@ export default {
</template> </template>
</template> </template>
<span v-else class="gl-text-secondary"> <span v-else class="gl-text-secondary">
{{ s__('TimeTracking|Use /spend or /estimate to manage time.') }} {{ s__('TimeTracking|To manage time, use /spend or /estimate.') }}
</span> </span>
</div> </div>
</div> </div>

View File

@ -1,67 +0,0 @@
# Use this template to announce a feature deprecation or other
# important planned changes at least three releases prior to removal.
# Breaking changes must happen in a major release.
#
# See the deprecation guidelines to confirm your understanding of GitLab's definitions:
# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
#
# If an End of Support period applies, see the OPTIONAL section below.
#
# For more information, see the handbook:
# https://handbook.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-and-other-planned-breaking-change-announcements
# ===================
# REQUIRED FIELDS
# ===================
# ----- DELETE EVERYTHING ABOVE THIS LINE -----
- title: "Hosted Runners on Linux operating system upgrade"
# The milestones for the deprecation announcement, and the removal.
removal_milestone: "17.0"
announcement_milestone: "16.10"
# Change breaking_change to false if needed.
breaking_change: true
# The stage and GitLab username of the person reporting the change,
# and a link to the deprecation issue
reporter: tmaczukin
stage: ci
issue_url: https://gitlab.com/gitlab-org/ci-cd/shared-runners/infrastructure/-/issues/60
impact: low # Can be one of: [critical, high, medium, low]
scope: instance # Can be one or a combination of: [instance, group, project]
resolution_role: Developer # Can be one of: [Admin, Owner, Maintainer, Developer]
manual_task: true # Can be true or false. Use this to denote whether a resolution action must be performed manually (true), or if it can be automated by using the API or other automation (false).
body: | # (required) Don't change this line.
With GitLab 17.0 we're upgrading the container-optimized operating system ([COS](https://cloud.google.com/container-optimized-os/docs))
of the ephemeral VMs used to execute jobs for [Hosted Runners on Linux](https://docs.gitlab.com/ee/ci/runners/saas/linux_saas_runner.html).
The COS upgrade includes a Docker Engine upgrade from Version 19.03.15 to Version 23.0.5, which introduced
a known compatibility issue.
GitLab CI/CD jobs [using Docker-in-Docker-based jobs](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker)
with a Docker-in-Docker service version prior to 20.10 and GitLab CI/CD jobs [using Kaniko to build container images](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html)
with a Kaniko service version older than `v1.9.0` will start failing.
To fix these issues, an update of service version in `.gitlab-ci.yml` is required.
Both issues, including a detailed explanation of how they affect jobs and how to fix
the issue, are described
[in the announcement blog post](https://about.gitlab.com/blog/2023/10/04/updating-the-os-version-of-saas-runners-on-linux/).
# ==============================
# OPTIONAL END-OF-SUPPORT FIELDS
# ==============================
#
# If an End of Support period applies:
# 1) Share this announcement in the `#spt_managers` Support channel in Slack
# 2) Mention `@gitlab-com/support` in this merge request.
#
# When support for this feature ends, in XX.YY milestone format.
end_of_support_milestone:
# Array of tiers the feature is currently available to,
# like [Free, Silver, Gold, Core, Premium, Ultimate]
tiers:
# Links to documentation and thumbnail image
documentation_url:
image_url:
# Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
video_url:

View File

@ -1,10 +1,25 @@
--- ---
table_name: dependency_proxy_blob_states table_name: dependency_proxy_blob_states
classes: classes:
- Geo::DependencyProxyBlobState - Geo::DependencyProxyBlobState
feature_categories: feature_categories:
- geo_replication - geo_replication
description: Separate table for dependency proxy blob verification states description: Separate table for dependency proxy blob verification states
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101429 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101429
milestone: '15.6' milestone: '15.6'
gitlab_schema: gitlab_main gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
desired_sharding_key:
group_id:
references: namespaces
backfill_via:
parent:
foreign_key: dependency_proxy_blob_id
table: dependency_proxy_blobs
sharding_key: group_id
belongs_to: dependency_proxy_blob

View File

@ -1,10 +1,25 @@
--- ---
table_name: dependency_proxy_manifest_states table_name: dependency_proxy_manifest_states
classes: classes:
- Geo::DependencyProxyManifestState - Geo::DependencyProxyManifestState
feature_categories: feature_categories:
- geo_replication - geo_replication
description: Separate table for dependency proxy manifest verification states description: Separate table for dependency proxy manifest verification states
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102908 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102908
milestone: '15.6' milestone: '15.6'
gitlab_schema: gitlab_main gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
desired_sharding_key:
group_id:
references: namespaces
backfill_via:
parent:
foreign_key: dependency_proxy_manifest_id
table: dependency_proxy_manifests
sharding_key: group_id
belongs_to: dependency_proxy_manifest

View File

@ -7,4 +7,19 @@ feature_categories:
description: User mentions in epic descriptions description: User mentions in epic descriptions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19009 introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/19009
milestone: '12.6' milestone: '12.6'
gitlab_schema: gitlab_main gitlab_schema: gitlab_main_cell
allow_cross_joins:
- gitlab_main_clusterwide
allow_cross_transactions:
- gitlab_main_clusterwide
allow_cross_foreign_keys:
- gitlab_main_clusterwide
desired_sharding_key:
group_id:
references: namespaces
backfill_via:
parent:
foreign_key: epic_id
table: epics
sharding_key: group_id
belongs_to: epic

View File

@ -25479,6 +25479,7 @@ Check permissions for the current user on a vulnerability finding.
| <a id="projectid"></a>`id` | [`ID!`](#id) | ID of the project. | | <a id="projectid"></a>`id` | [`ID!`](#id) | ID of the project. |
| <a id="projectimportstatus"></a>`importStatus` | [`String`](#string) | Status of import background job of the project. | | <a id="projectimportstatus"></a>`importStatus` | [`String`](#string) | Status of import background job of the project. |
| <a id="projectincidentmanagementtimelineeventtags"></a>`incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. | | <a id="projectincidentmanagementtimelineeventtags"></a>`incidentManagementTimelineEventTags` | [`[TimelineEventTagType!]`](#timelineeventtagtype) | Timeline event tags for the project. |
| <a id="projectisadjourneddeletionenabled"></a>`isAdjournedDeletionEnabled` **{warning-solid}** | [`Boolean!`](#boolean) | **Introduced** in GitLab 16.11. **Status**: Experiment. Indicates if delayed project deletion is enabled. |
| <a id="projectiscatalogresource"></a>`isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in GitLab 15.11. **Status**: Experiment. Indicates if a project is a catalog resource. | | <a id="projectiscatalogresource"></a>`isCatalogResource` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in GitLab 15.11. **Status**: Experiment. Indicates if a project is a catalog resource. |
| <a id="projectisforked"></a>`isForked` | [`Boolean!`](#boolean) | Project is forked. | | <a id="projectisforked"></a>`isForked` | [`Boolean!`](#boolean) | Project is forked. |
| <a id="projectissuesaccesslevel"></a>`issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. | | <a id="projectissuesaccesslevel"></a>`issuesAccessLevel` | [`ProjectFeatureAccess`](#projectfeatureaccess) | Access level required for issues access. |
@ -25509,6 +25510,7 @@ Check permissions for the current user on a vulnerability finding.
| <a id="projectpackagesprotectionrules"></a>`packagesProtectionRules` | [`PackagesProtectionRuleConnection`](#packagesprotectionruleconnection) | Packages protection rules for the project. (see [Connections](#connections)) | | <a id="projectpackagesprotectionrules"></a>`packagesProtectionRules` | [`PackagesProtectionRuleConnection`](#packagesprotectionruleconnection) | Packages protection rules for the project. (see [Connections](#connections)) |
| <a id="projectpath"></a>`path` | [`String!`](#string) | Path of the project. | | <a id="projectpath"></a>`path` | [`String!`](#string) | Path of the project. |
| <a id="projectpathlocks"></a>`pathLocks` | [`PathLockConnection`](#pathlockconnection) | The project's path locks. (see [Connections](#connections)) | | <a id="projectpathlocks"></a>`pathLocks` | [`PathLockConnection`](#pathlockconnection) | The project's path locks. (see [Connections](#connections)) |
| <a id="projectpermanentdeletiondate"></a>`permanentDeletionDate` **{warning-solid}** | [`String`](#string) | **Introduced** in GitLab 16.11. **Status**: Experiment. Date when project will be deleted if delayed project deletion is enabled. |
| <a id="projectpipelineanalytics"></a>`pipelineAnalytics` | [`PipelineAnalytics`](#pipelineanalytics) | Pipeline analytics. | | <a id="projectpipelineanalytics"></a>`pipelineAnalytics` | [`PipelineAnalytics`](#pipelineanalytics) | Pipeline analytics. |
| <a id="projectpipelinetriggers"></a>`pipelineTriggers` **{warning-solid}** | [`PipelineTriggerConnection`](#pipelinetriggerconnection) | **Introduced** in GitLab 16.3. **Status**: Experiment. List of pipeline trigger tokens. | | <a id="projectpipelinetriggers"></a>`pipelineTriggers` **{warning-solid}** | [`PipelineTriggerConnection`](#pipelinetriggerconnection) | **Introduced** in GitLab 16.3. **Status**: Experiment. List of pipeline trigger tokens. |
| <a id="projectpreventmergewithoutjiraissueenabled"></a>`preventMergeWithoutJiraIssueEnabled` | [`Boolean!`](#boolean) | Indicates if an associated issue from Jira is required. | | <a id="projectpreventmergewithoutjiraissueenabled"></a>`preventMergeWithoutJiraIssueEnabled` | [`Boolean!`](#boolean) | Indicates if an associated issue from Jira is required. |

View File

@ -1241,33 +1241,6 @@ set `AUTO_DEVOPS_BUILD_IMAGE_CNB_BUILDER` to `heroku/builder:20`.
<div class="deprecation breaking-change" data-milestone="17.0"> <div class="deprecation breaking-change" data-milestone="17.0">
### Hosted Runners on Linux operating system upgrade
<div class="deprecation-notes">
- Announced in GitLab <span class="milestone">16.10</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/ci-cd/shared-runners/infrastructure/-/issues/60).
</div>
With GitLab 17.0 we're upgrading the container-optimized operating system ([COS](https://cloud.google.com/container-optimized-os/docs))
of the ephemeral VMs used to execute jobs for [Hosted Runners on Linux](https://docs.gitlab.com/ee/ci/runners/saas/linux_saas_runner.html).
The COS upgrade includes a Docker Engine upgrade from Version 19.03.15 to Version 23.0.5, which introduced
a known compatibility issue.
GitLab CI/CD jobs [using Docker-in-Docker-based jobs](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker)
with a Docker-in-Docker service version prior to 20.10 and GitLab CI/CD jobs [using Kaniko to build container images](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html)
with a Kaniko service version older than `v1.9.0` will start failing.
To fix these issues, an update of service version in `.gitlab-ci.yml` is required.
Both issues, including a detailed explanation of how they affect jobs and how to fix
the issue, are described
[in the announcement blog post](https://about.gitlab.com/blog/2023/10/04/updating-the-os-version-of-saas-runners-on-linux/).
</div>
<div class="deprecation breaking-change" data-milestone="17.0">
### Internal container registry API tag deletion endpoint ### Internal container registry API tag deletion endpoint
<div class="deprecation-notes"> <div class="deprecation-notes">

View File

@ -122,7 +122,7 @@ module Gitlab
def instance_count_request(amount = 1) def instance_count_request(amount = 1)
@request_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_requests_total, 'Client side Redis request count, per Redis server') @request_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_requests_total, 'Client side Redis request count, per Redis server')
@request_counter.increment({ storage: storage_key }, amount) @request_counter.increment(storage_labels, amount)
end end
def instance_count_pipelined_request(size) def instance_count_pipelined_request(size)
@ -132,7 +132,7 @@ module Gitlab
{}, {},
[10, 100, 1000, 10_000] [10, 100, 1000, 10_000]
) )
@pipeline_size_histogram.observe({ storage: storage_key, storage_shard: shard_key }, size) @pipeline_size_histogram.observe(storage_labels, size)
end end
def instance_count_exception(ex) def instance_count_exception(ex)
@ -140,12 +140,12 @@ module Gitlab
# server is doing. Redis itself does not expose error counts. This # server is doing. Redis itself does not expose error counts. This
# metric can be used for Redis alerting and service health monitoring. # metric can be used for Redis alerting and service health monitoring.
@exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_exceptions_total, 'Client side Redis exception count, per Redis server, per exception class') @exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_exceptions_total, 'Client side Redis exception count, per Redis server, per exception class')
@exception_counter.increment({ storage: storage_key, storage_shard: shard_key, exception: ex.class.to_s }) @exception_counter.increment(storage_labels.merge(exception: ex.class.to_s))
end end
def instance_count_connection_exception(ex) def instance_count_connection_exception(ex)
@connection_exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_connection_exceptions_total, 'Client side Redis connection exception count, per Redis server, per exception class') @connection_exception_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_connection_exceptions_total, 'Client side Redis connection exception count, per Redis server, per exception class')
@connection_exception_counter.increment({ storage: storage_key, storage_shard: shard_key, exception: ex.class.to_s }) @connection_exception_counter.increment(storage_labels.merge(exception: ex.class.to_s))
end end
def instance_count_cluster_redirection(ex) def instance_count_cluster_redirection(ex)
@ -153,7 +153,7 @@ module Gitlab
# redirected to the right node, especially during resharding.. # redirected to the right node, especially during resharding..
# This metric can be used for Redis alerting and service health monitoring. # This metric can be used for Redis alerting and service health monitoring.
@redirection_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_redirections_total, 'Client side Redis Cluster redirection count, per Redis node, per slot') @redirection_counter ||= Gitlab::Metrics.counter(:gitlab_redis_client_redirections_total, 'Client side Redis Cluster redirection count, per Redis node, per slot')
@redirection_counter.increment(decompose_redirection_message(ex.message).merge({ storage: storage_key, storage_shard: shard_key })) @redirection_counter.increment(decompose_redirection_message(ex.message).merge(storage_labels))
end end
def instance_observe_duration(duration) def instance_observe_duration(duration)
@ -164,15 +164,19 @@ module Gitlab
[0.1, 0.5, 0.75, 1] [0.1, 0.5, 0.75, 1]
) )
@request_latency_histogram.observe({ storage: storage_key, storage_shard: shard_key }, duration) @request_latency_histogram.observe(storage_labels, duration)
end end
def log_exception(ex) def log_exception(ex)
::Gitlab::ErrorTracking.log_exception(ex, storage: storage_key, storage_shard: shard_key) ::Gitlab::ErrorTracking.log_exception(ex, **storage_labels)
end end
private private
def storage_labels
{ storage: storage_key, storage_shard: shard_key }
end
def request_count_key def request_count_key
strong_memoize(:request_count_key) { build_key(:redis_request_count) } strong_memoize(:request_count_key) { build_key(:redis_request_count) }
end end

View File

@ -52758,7 +52758,7 @@ msgstr ""
msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}" msgid "TimeTracking|Time remaining: %{timeRemainingHumanReadable}"
msgstr "" msgstr ""
msgid "TimeTracking|Use /spend or /estimate to manage time." msgid "TimeTracking|To manage time, use /spend or /estimate."
msgstr "" msgstr ""
msgid "Timeago|%s days ago" msgid "Timeago|%s days ago"
@ -52999,6 +52999,9 @@ msgstr ""
msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more%{linkEnd}." msgid "To add a custom suffix, set up a Service Desk email address. %{linkStart}Learn more%{linkEnd}."
msgstr "" msgstr ""
msgid "To add active contacts, use /add_contacts."
msgstr ""
msgid "To add the entry manually, provide the following details to the application on your phone." msgid "To add the entry manually, provide the following details to the application on your phone."
msgstr "" msgstr ""

View File

@ -86,13 +86,27 @@ describe('~/api/projects_api.js', () => {
jest.spyOn(axios, 'delete'); jest.spyOn(axios, 'delete');
}); });
it('deletes to the correct URL', () => { describe('without params', () => {
const expectedUrl = `/api/v7/projects/${projectId}`; it('deletes to the correct URL', () => {
const expectedUrl = `/api/v7/projects/${projectId}`;
mock.onDelete(expectedUrl).replyOnce(HTTP_STATUS_OK); mock.onDelete(expectedUrl).replyOnce(HTTP_STATUS_OK);
return projectsApi.deleteProject(projectId).then(() => { return projectsApi.deleteProject(projectId).then(() => {
expect(axios.delete).toHaveBeenCalledWith(expectedUrl); expect(axios.delete).toHaveBeenCalledWith(expectedUrl, { params: undefined });
});
});
});
describe('with params', () => {
it('deletes to the correct URL with params', () => {
const expectedUrl = `/api/v7/projects/${projectId}`;
mock.onDelete(expectedUrl).replyOnce(HTTP_STATUS_OK);
return projectsApi.deleteProject(projectId, { testParam: true }).then(() => {
expect(axios.delete).toHaveBeenCalledWith(expectedUrl, { params: { testParam: true } });
});
}); });
}); });
}); });

View File

@ -9,6 +9,7 @@ import getIssueCrmContactsQuery from '~/sidebar/queries/get_issue_crm_contacts.q
import issueCrmContactsSubscription from '~/sidebar/queries/issue_crm_contacts.subscription.graphql'; import issueCrmContactsSubscription from '~/sidebar/queries/issue_crm_contacts.subscription.graphql';
import { import {
getIssueCrmContactsQueryResponse, getIssueCrmContactsQueryResponse,
getIssueCrmContactsQueryResponseEmpty,
issueCrmContactsUpdateResponse, issueCrmContactsUpdateResponse,
issueCrmContactsUpdateNullResponse, issueCrmContactsUpdateNullResponse,
} from '../mock_data'; } from '../mock_data';
@ -21,6 +22,9 @@ describe('Issue crm contacts component', () => {
let fakeApollo; let fakeApollo;
const successQueryHandler = jest.fn().mockResolvedValue(getIssueCrmContactsQueryResponse); const successQueryHandler = jest.fn().mockResolvedValue(getIssueCrmContactsQueryResponse);
const emptySuccessQueryHandler = jest
.fn()
.mockResolvedValue(getIssueCrmContactsQueryResponseEmpty);
const successSubscriptionHandler = jest.fn().mockResolvedValue(issueCrmContactsUpdateResponse); const successSubscriptionHandler = jest.fn().mockResolvedValue(issueCrmContactsUpdateResponse);
const nullSubscriptionHandler = jest.fn().mockResolvedValue(issueCrmContactsUpdateNullResponse); const nullSubscriptionHandler = jest.fn().mockResolvedValue(issueCrmContactsUpdateNullResponse);
@ -80,6 +84,16 @@ describe('Issue crm contacts component', () => {
); );
}); });
it('has an empty state', async () => {
mountComponent({
queryHandler: emptySuccessQueryHandler,
subscriptionHandler: nullSubscriptionHandler,
});
await waitForPromises();
expect(wrapper.findByTestId('crm-empty-message').exists()).toBe(true);
});
it('renders correct results after subscription update', async () => { it('renders correct results after subscription update', async () => {
mountComponent(); mountComponent();
await waitForPromises(); await waitForPromises();

View File

@ -31,6 +31,18 @@ export const getIssueCrmContactsQueryResponse = {
}, },
}; };
export const getIssueCrmContactsQueryResponseEmpty = {
data: {
issue: {
__typename: 'Issue',
id: 'gid://gitlab/Issue/123',
customerRelationsContacts: {
nodes: [],
},
},
},
};
export const issueCrmContactsUpdateNullResponse = { export const issueCrmContactsUpdateNullResponse = {
data: { data: {
issueCrmContactsUpdated: null, issueCrmContactsUpdated: null,

View File

@ -13,6 +13,7 @@ import { mountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { stubComponent, RENDER_ALL_SLOTS_TEMPLATE } from 'helpers/stub_component';
import { Blob, BinaryBlob } from 'jest/blob/components/mock_data'; import { Blob, BinaryBlob } from 'jest/blob/components/mock_data';
import { differenceInMilliseconds } from '~/lib/utils/datetime_utility'; import { differenceInMilliseconds } from '~/lib/utils/datetime_utility';
import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue'; import SnippetHeader, { i18n } from '~/snippets/components/snippet_header.vue';
@ -83,6 +84,7 @@ describe('Snippet header component', () => {
GlDisclosureDropdownGroup, GlDisclosureDropdownGroup,
GlDisclosureDropdownItem, GlDisclosureDropdownItem,
GlIcon, GlIcon,
GlModal: stubComponent(GlModal, { template: RENDER_ALL_SLOTS_TEMPLATE }),
}, },
directives: { directives: {
GlTooltip: createMockDirective('gl-tooltip'), GlTooltip: createMockDirective('gl-tooltip'),
@ -99,9 +101,10 @@ describe('Snippet header component', () => {
const findSpamAction = () => wrapper.findByText('Submit as spam'); const findSpamAction = () => wrapper.findByText('Submit as spam');
const findDeleteAction = () => wrapper.findByText('Delete'); const findDeleteAction = () => wrapper.findByText('Delete');
const findDeleteModal = () => wrapper.findComponent(GlModal); const findDeleteModal = () => wrapper.findComponent(GlModal);
const findDeleteModalDeleteAction = () => wrapper.findByTestId('delete-snippet-button');
const findIcon = () => wrapper.findComponent(GlIcon); const findIcon = () => wrapper.findComponent(GlIcon);
const findTooltip = () => getBinding(findIcon().element, 'gl-tooltip'); const findTooltip = () => getBinding(findIcon().element, 'gl-tooltip');
const findSpamIcon = () => wrapper.findComponent('[data-testid="snippets-spam-icon"]'); const findSpamIcon = () => wrapper.findByTestId('snippets-spam-icon');
const title = 'The property of Thor'; const title = 'The property of Thor';
@ -294,17 +297,21 @@ describe('Snippet header component', () => {
}); });
describe('Delete mutation', () => { describe('Delete mutation', () => {
const deleteSnippet = async () => { const openDeleteSnippetModal = async () => {
// Click delete action // Click delete action
findDropdown().trigger('click'); findDropdown().trigger('click');
findDeleteAction().trigger('click'); findDeleteAction().trigger('click');
await nextTick(); await nextTick();
};
const deleteSnippet = async () => {
await openDeleteSnippetModal();
expect(findDeleteModal().props().visible).toBe(true); expect(findDeleteModal().props().visible).toBe(true);
// Click delete button in delete modal // Click delete button in delete modal
document.querySelector('[data-testid="delete-snippet-button"').click(); findDeleteModalDeleteAction().trigger('click');
await waitForPromises(); await waitForPromises();
}; };
@ -325,9 +332,19 @@ describe('Snippet header component', () => {
await deleteSnippet(); await deleteSnippet();
expect(document.querySelector('[data-testid="delete-alert"').textContent.trim()).toBe( expect(wrapper.findByTestId('delete-alert').text()).toBe(ERROR_MSG);
ERROR_MSG, });
);
it('puts the `Delete snippet` modal button in the loading state on click', async () => {
createComponent();
expect(findDeleteModalDeleteAction().props('loading')).toBe(false);
await openDeleteSnippetModal();
findDeleteModalDeleteAction().trigger('click');
await nextTick();
expect(findDeleteModalDeleteAction().props('loading')).toBe(true);
}); });
describe('in case of successful mutation, closes modal and redirects to correct listing', () => { describe('in case of successful mutation, closes modal and redirects to correct listing', () => {

View File

@ -33,7 +33,7 @@ describe('WorkItemTimeTracking component', () => {
createComponent({ timeEstimate: 0, totalTimeSpent: 0 }); createComponent({ timeEstimate: 0, totalTimeSpent: 0 });
expect(findTimeTrackingBody().text()).toMatchInterpolatedText( expect(findTimeTrackingBody().text()).toMatchInterpolatedText(
'Use /spend or /estimate to manage time.', 'To manage time, use /spend or /estimate.',
); );
expect(findProgressBar().exists()).toBe(false); expect(findProgressBar().exists()).toBe(false);
}); });