diff options
27 files changed, 308 insertions, 123 deletions
diff --git a/app/assets/javascripts/work_items/components/item_state.vue b/app/assets/javascripts/work_items/components/item_state.vue index cf1d9040334..2a0913e380a 100644 --- a/app/assets/javascripts/work_items/components/item_state.vue +++ b/app/assets/javascripts/work_items/components/item_state.vue @@ -62,7 +62,7 @@ export default { :value="state" :options="$options.states" :disabled="disabled" - class="gl-w-auto hide-select-decoration" + class="gl-w-auto hide-select-decoration gl-pl-3" :class="{ 'gl-bg-transparent! gl-cursor-text!': disabled }" @change="setState" /> diff --git a/app/assets/javascripts/work_items/components/work_item_assignees.vue b/app/assets/javascripts/work_items/components/work_item_assignees.vue index f1b12e6478f..fcf2bfdba80 100644 --- a/app/assets/javascripts/work_items/components/work_item_assignees.vue +++ b/app/assets/javascripts/work_items/components/work_item_assignees.vue @@ -144,6 +144,9 @@ export default { return this.searchKey.length === 0; }, addAssigneesText() { + if (!this.canUpdate) { + return s__('WorkItem|None'); + } return this.allowsMultipleAssignees ? s__('WorkItem|Add assignees') : s__('WorkItem|Add assignee'); @@ -270,7 +273,7 @@ export default { :loading="isLoadingUsers" :view-only="!canUpdate" :allow-clear-all="isEditing" - class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0!" + class="assignees-selector gl-flex-grow-1 gl-border gl-border-white gl-rounded-base col-9 gl-align-self-start gl-px-0! gl-mx-2" @input="handleAssigneesInput" @text-input="debouncedSearchKeyUpdate" @focus="handleFocus" @@ -280,7 +283,7 @@ export default { > <template #empty-placeholder> <div - class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-top-2" + class="add-assignees gl-min-w-fit-content gl-display-flex gl-align-items-center gl-text-gray-300 gl-pr-4 gl-pl-2 gl-top-2" data-testid="empty-state" > <gl-icon name="profile" /> @@ -301,7 +304,7 @@ export default { :title="token.name" :data-user-id="getUserId(token.id)" data-placement="top" - class="gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link" + class="gl-ml-n2 gl-text-decoration-none! gl-text-body! gl-display-flex gl-md-display-inline-flex! gl-align-items-center js-user-link" > <gl-avatar :size="24" :src="token.avatarUrl" /> <span class="gl-pl-2">{{ token.name }}</span> diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue index 372bf7ec749..6e591c9f481 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue @@ -102,11 +102,17 @@ export default { childrenIds() { return this.children.map((c) => c.id); }, + childrenCountLabel() { + return this.isLoading && this.children.length === 0 ? '...' : this.children.length; + }, }, methods: { badgeVariant(state) { return state === STATE_OPEN ? 'success' : 'info'; }, + iconClass(state) { + return state === STATE_OPEN ? 'gl-text-green-500' : 'gl-text-blue-500'; + }, toggle() { this.isOpen = !this.isOpen; }, @@ -237,7 +243,16 @@ export default { class="gl-px-5 gl-py-3 gl-display-flex gl-justify-content-space-between" :class="{ 'gl-border-b-1 gl-border-b-solid gl-border-b-gray-100': isOpen }" > - <h5 class="gl-m-0 gl-line-height-24 gl-flex-grow-1">{{ $options.i18n.title }}</h5> + <div class="gl-display-flex gl-flex-grow-1"> + <h5 class="gl-m-0 gl-line-height-24">{{ $options.i18n.title }}</h5> + <span + class="gl-display-inline-flex gl-align-items-center gl-line-height-24 gl-ml-3" + data-testid="children-count" + > + <gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-2 gl-text-gray-500" /> + {{ childrenCountLabel }} + </span> + </div> <gl-button v-if="canUpdate" category="secondary" @@ -296,7 +311,11 @@ export default { data-testid="confidential-icon" :title="__('Confidential')" /> - <gl-icon :name="$options.WIDGET_TYPE_TASK_ICON" class="gl-mr-3 gl-text-gray-700" /> + <gl-icon + :name="$options.WIDGET_TYPE_TASK_ICON" + class="gl-mr-3" + :class="iconClass(child.state)" + /> <gl-button :href="childPath(child.id)" category="tertiary" diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js index 00a5f050b77..b0ade1fbe45 100644 --- a/app/assets/javascripts/work_items/constants.js +++ b/app/assets/javascripts/work_items/constants.js @@ -28,7 +28,7 @@ export const i18n = { }; export const WIDGET_ICONS = { - TASK: 'task-done', + TASK: 'issue-type-task', }; export const WORK_ITEM_STATUS_TEXT = { diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb index 928c617471b..b595c3c6790 100644 --- a/app/controllers/concerns/notes_actions.rb +++ b/app/controllers/concerns/notes_actions.rb @@ -217,7 +217,8 @@ module NotesActions :note, :line_code, # LegacyDiffNote :position, # DiffNote - :confidential + :confidential, + :internal ).tap do |create_params| create_params.merge!( params.permit(:merge_request_diff_head_sha, :in_reply_to_discussion_id) diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb index 1b673204213..f48e62af767 100644 --- a/app/graphql/mutations/notes/create/base.rb +++ b/app/graphql/mutations/notes/create/base.rb @@ -21,7 +21,13 @@ module Mutations argument :confidential, GraphQL::Types::Boolean, required: false, - description: 'Confidentiality flag of a note. Default is false.' + description: 'Confidentiality flag of a note. Default is false.', + deprecated: { reason: :renamed, replacement: 'internal', milestone: '15.3' } + + argument :internal, + GraphQL::Types::Boolean, + required: false, + description: 'Internal flag for a note. Default is false.' def resolve(args) noteable = authorized_find!(id: args[:noteable_id]) @@ -49,7 +55,7 @@ module Mutations { noteable: noteable, note: args[:body], - confidential: args[:confidential] + internal: args[:internal] || args[:confidential] } end diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb index 1e312273314..c254460a51f 100644 --- a/app/graphql/types/notes/note_type.rb +++ b/app/graphql/types/notes/note_type.rb @@ -37,7 +37,17 @@ module Types field :confidential, GraphQL::Types::Boolean, null: true, description: 'Indicates if this note is confidential.', - method: :confidential? + method: :confidential?, + deprecated: { + reason: :renamed, + replacement: 'internal', + milestone: '15.3' + } + + field :internal, GraphQL::Types::Boolean, null: true, + description: 'Indicates if this note is internal.', + method: :confidential? + field :created_at, Types::TimeType, null: false, description: 'Timestamp of the note creation.' field :discussion, Types::Notes::DiscussionType, null: true, diff --git a/app/services/notes/build_service.rb b/app/services/notes/build_service.rb index 8c250526efc..cc5c81cf280 100644 --- a/app/services/notes/build_service.rb +++ b/app/services/notes/build_service.rb @@ -16,6 +16,14 @@ module Notes params.merge!(discussion.reply_attributes) end + # The `confidential` param for notes is deprecated with 15.3 + # and renamed to `internal`. + # We still accept `confidential` until the param gets removed from the API. + # Until we have not migrated the database column to `internal` we need to rename + # the parameter. Issue: https://gitlab.com/gitlab-org/gitlab/-/issues/367923. + params[:confidential] = params[:internal] || params[:confidential] + params.delete(:internal) + new_note(params, discussion) end diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 8305927e0e8..5146323d8ac 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -1440,7 +1440,8 @@ Input type: `CreateDiffNoteInput` | ---- | ---- | ----------- | | <a id="mutationcreatediffnotebody"></a>`body` | [`String!`](#string) | Content of the note. | | <a id="mutationcreatediffnoteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationcreatediffnoteconfidential"></a>`confidential` | [`Boolean`](#boolean) | Confidentiality flag of a note. Default is false. | +| <a id="mutationcreatediffnoteconfidential"></a>`confidential` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** This was renamed. Please use `internal`. Deprecated in 15.3. | +| <a id="mutationcreatediffnoteinternal"></a>`internal` | [`Boolean`](#boolean) | Internal flag for a note. Default is false. | | <a id="mutationcreatediffnotenoteableid"></a>`noteableId` | [`NoteableID!`](#noteableid) | Global ID of the resource to add a note to. | | <a id="mutationcreatediffnoteposition"></a>`position` | [`DiffPositionInput!`](#diffpositioninput) | Position of this note on a diff. | @@ -1492,7 +1493,8 @@ Input type: `CreateImageDiffNoteInput` | ---- | ---- | ----------- | | <a id="mutationcreateimagediffnotebody"></a>`body` | [`String!`](#string) | Content of the note. | | <a id="mutationcreateimagediffnoteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationcreateimagediffnoteconfidential"></a>`confidential` | [`Boolean`](#boolean) | Confidentiality flag of a note. Default is false. | +| <a id="mutationcreateimagediffnoteconfidential"></a>`confidential` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** This was renamed. Please use `internal`. Deprecated in 15.3. | +| <a id="mutationcreateimagediffnoteinternal"></a>`internal` | [`Boolean`](#boolean) | Internal flag for a note. Default is false. | | <a id="mutationcreateimagediffnotenoteableid"></a>`noteableId` | [`NoteableID!`](#noteableid) | Global ID of the resource to add a note to. | | <a id="mutationcreateimagediffnoteposition"></a>`position` | [`DiffImagePositionInput!`](#diffimagepositioninput) | Position of this note on a diff. | @@ -1589,8 +1591,9 @@ Input type: `CreateNoteInput` | ---- | ---- | ----------- | | <a id="mutationcreatenotebody"></a>`body` | [`String!`](#string) | Content of the note. | | <a id="mutationcreatenoteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | -| <a id="mutationcreatenoteconfidential"></a>`confidential` | [`Boolean`](#boolean) | Confidentiality flag of a note. Default is false. | +| <a id="mutationcreatenoteconfidential"></a>`confidential` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated:** This was renamed. Please use `internal`. Deprecated in 15.3. | | <a id="mutationcreatenotediscussionid"></a>`discussionId` | [`DiscussionID`](#discussionid) | Global ID of the discussion this note is in reply to. | +| <a id="mutationcreatenoteinternal"></a>`internal` | [`Boolean`](#boolean) | Internal flag for a note. Default is false. | | <a id="mutationcreatenotemergerequestdiffheadsha"></a>`mergeRequestDiffHeadSha` | [`String`](#string) | SHA of the head commit which is used to ensure that the merge request has not been updated since the request was sent. | | <a id="mutationcreatenotenoteableid"></a>`noteableId` | [`NoteableID!`](#noteableid) | Global ID of the resource to add a note to. | @@ -14845,10 +14848,11 @@ Represents the network policy. | <a id="noteauthor"></a>`author` | [`UserCore!`](#usercore) | User who wrote this note. | | <a id="notebody"></a>`body` | [`String!`](#string) | Content of the note. | | <a id="notebodyhtml"></a>`bodyHtml` | [`String`](#string) | The GitLab Flavored Markdown rendering of `note`. | -| <a id="noteconfidential"></a>`confidential` | [`Boolean`](#boolean) | Indicates if this note is confidential. | +| <a id="noteconfidential"></a>`confidential` **{warning-solid}** | [`Boolean`](#boolean) | **Deprecated** in 15.3. This was renamed. Use: `internal`. | | <a id="notecreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of the note creation. | | <a id="notediscussion"></a>`discussion` | [`Discussion`](#discussion) | Discussion this note is a part of. | | <a id="noteid"></a>`id` | [`NoteID!`](#noteid) | ID of the note. | +| <a id="noteinternal"></a>`internal` | [`Boolean`](#boolean) | Indicates if this note is internal. | | <a id="noteposition"></a>`position` | [`DiffPosition`](#diffposition) | Position of this note on a diff. | | <a id="noteproject"></a>`project` | [`Project`](#project) | Project associated with the note. | | <a id="noteresolvable"></a>`resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. | diff --git a/doc/api/job_artifacts.md b/doc/api/job_artifacts.md index ee9f1678b18..31da0638d23 100644 --- a/doc/api/job_artifacts.md +++ b/doc/api/job_artifacts.md @@ -16,11 +16,11 @@ Get the job's artifacts zipped archive of a project. GET /projects/:id/jobs/:job_id/artifacts ``` -| Attribute | Type | Required | Description | -|-------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | -| `job_id` | integer | yes | ID of a job. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|---------------------------|----------------|----------|-------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | ID of a job. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only in a CI/CD job defined in the `.gitlab-ci.yml` file. The value is always `$CI_JOB_TOKEN`. The job associated with the `$CI_JOB_TOKEN` must be running when this token is used. | Example request using the `PRIVATE-TOKEN` header: @@ -80,12 +80,12 @@ GET /projects/:id/jobs/artifacts/:ref_name/download?job=name Parameters -| Attribute | Type | Required | Description | -|-------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | -| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | -| `job` | string | yes | The name of the job. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|---------------------------|----------------|----------|-------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | +| `ref_name` | string | yes | Branch or tag name in repository. HEAD or SHA references are not supported. | +| `job` | string | yes | The name of the job. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only in a CI/CD job defined in the `.gitlab-ci.yml` file. The value is always `$CI_JOB_TOKEN`. The job associated with the `$CI_JOB_TOKEN` must be running when this token is used. | Example request using the `PRIVATE-TOKEN` header: @@ -141,12 +141,12 @@ GET /projects/:id/jobs/:job_id/artifacts/*artifact_path Parameters -| Attribute | Type | Required | Description | -|-----------------|----------------|----------|------------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | -| `job_id` | integer | yes | The unique job identifier. | -| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|---------------------------|----------------|----------|-------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | +| `job_id` | integer | yes | The unique job identifier. | +| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only in a CI/CD job defined in the `.gitlab-ci.yml` file. The value is always `$CI_JOB_TOKEN`. The job associated with the `$CI_JOB_TOKEN` must be running when this token is used. | Example request: @@ -185,13 +185,13 @@ GET /projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=name Parameters: -| Attribute | Type | Required | Description | -|-----------------|----------------|----------|--------------------------------------------------------------------------------------------------------------| -| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | -| `ref_name` | string | yes | Branch or tag name in repository. `HEAD` or `SHA` references are not supported. | -| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | -| `job` | string | yes | The name of the job. | -| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only inside `.gitlab-ci.yml`. Its value is always `$CI_JOB_TOKEN`. | +| Attribute | Type | Required | Description | +|---------------------------|----------------|----------|-------------| +| `id` | integer/string | yes | ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user. | +| `ref_name` | string | yes | Branch or tag name in repository. `HEAD` or `SHA` references are not supported. | +| `artifact_path` | string | yes | Path to a file inside the artifacts archive. | +| `job` | string | yes | The name of the job. | +| `job_token` **(PREMIUM)** | string | no | To be used with [triggers](../ci/jobs/ci_job_token.md#download-an-artifact-from-a-different-pipeline) for multi-project pipelines. It should be invoked only in a CI/CD job defined in the `.gitlab-ci.yml` file. The value is always `$CI_JOB_TOKEN`. The job associated with the `$CI_JOB_TOKEN` must be running when this token is used. | Example request: diff --git a/doc/api/notes.md b/doc/api/notes.md index 759bef8fea2..e0799cdd380 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -80,7 +80,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at "noteable_type": "Issue", "noteable_iid": 377, "resolvable": false, - "confidential": false + "confidential": false, + "internal": false }, { "id": 305, @@ -101,7 +102,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at "noteable_type": "Issue", "noteable_iid": 121, "resolvable": false, - "confidential": true + "confidential": true, + "internal": true } ] ``` @@ -145,7 +147,8 @@ Parameters: | `id` | integer or string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding). | | `issue_iid` | integer | yes | The IID of an issue. | | `body` | string | yes | The content of a note. Limited to 1,000,000 characters. | -| `confidential` | boolean | no | The confidential flag of a note. Default is false. | +| `confidential` | boolean | no | **Deprecated:** will be removed in GitLab 16.0 and renamed to `internal`. The confidential flag of a note. Default is false. | +| `internal` | boolean | no | The internal flag of a note. Overrides `confidential` when both parameters are submitted. Default is false. | | `created_at` | string | no | Date time string, ISO 8601 formatted. It must be after 1970-01-01. Example: `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) | ```shell @@ -378,7 +381,8 @@ Parameters: "noteable_type": "MergeRequest", "noteable_iid": 2, "resolvable": false, - "confidential": false + "confidential": false, + "internal": false } ``` @@ -506,7 +510,8 @@ Parameters: "expires_at": null, "updated_at": "2013-10-02T07:34:20Z", "created_at": "2013-10-02T07:34:20Z", - "confidential": false + "confidential": false, + "internal": false } ``` @@ -530,7 +535,8 @@ Parameters: | `body` | string | yes | The content of a note. Limited to 1,000,000 characters. | | `epic_id` | integer | yes | The ID of an epic | | `id` | integer or string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) | -| `confidential` | boolean | no | The confidential flag of a note. Default is `false`. | +| `confidential` | boolean | no | **Deprecated:** will be removed in GitLab 16.0 and is renamed to `internal`. The confidential flag of a note. Default is `false`. | +| `internal` | boolean | no | The internal flag of a note. Overrides `confidential` when both parameters are submitted. Default is `false`. | ```shell curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/5/epics/11/notes?body=note" diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md index 1451e5d2b60..712fd7b45d6 100644 --- a/doc/ci/docker/using_kaniko.md +++ b/doc/ci/docker/using_kaniko.md @@ -64,7 +64,7 @@ build: entrypoint: [""] script: - mkdir -p /kaniko/.docker - - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}" > /kaniko/.docker/config.json + - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 -w 0)\"}}}" > /kaniko/.docker/config.json - >- /kaniko/executor --context "${CI_PROJECT_DIR}" diff --git a/lib/api/entities/note.rb b/lib/api/entities/note.rb index a597aa7bb4a..a92f534bbdc 100644 --- a/lib/api/entities/note.rb +++ b/lib/api/entities/note.rb @@ -26,6 +26,7 @@ module API expose :resolved_at, if: ->(note, options) { note.resolvable? } expose :confidential?, as: :confidential + expose :confidential?, as: :internal # Avoid N+1 queries as much as possible expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) } diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 59f57179871..77c479c529a 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -73,7 +73,8 @@ module API params do requires :noteable_id, type: Integer, desc: 'The ID of the noteable' requires :body, type: String, desc: 'The content of a note' - optional :confidential, type: Boolean, desc: 'Confidentiality note flag, default is false' + optional :confidential, type: Boolean, desc: '[Deprecated in 15.3] Renamed to internal' + optional :internal, type: Boolean, desc: 'Internal note flag, default is false' optional :created_at, type: String, desc: 'The creation date of the note' optional :merge_request_diff_head_sha, type: String, desc: 'The SHA of the head commit' end @@ -87,7 +88,7 @@ module API note: params[:body], noteable_type: noteables_str.classify, noteable_id: noteable.id, - confidential: params[:confidential], + internal: params[:internal] || params[:confidential], created_at: params[:created_at], merge_request_diff_head_sha: params[:merge_request_diff_head_sha] } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c7fd171ff2f..825c51bba46 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -460,9 +460,6 @@ msgstr "" msgid "%{address} is an invalid IP address range" msgstr "" -msgid "%{attribute} must be between %{min} and %{max}" -msgstr "" - msgid "%{author_link} cloned %{original_issue} to %{new_issue}." msgstr "" @@ -44371,6 +44368,9 @@ msgstr "" msgid "WorkItem|No child items are currently assigned. Use child items to prioritize tasks that your team should complete in order to accomplish your goals!" msgstr "" +msgid "WorkItem|None" +msgstr "" + msgid "WorkItem|Open" msgstr "" diff --git a/package.json b/package.json index 674ce56b5b0..ab6ef62856e 100644 --- a/package.json +++ b/package.json @@ -196,7 +196,7 @@ "yaml": "^2.0.0-10" }, "devDependencies": { - "@gitlab/eslint-plugin": "15.0.0", + "@gitlab/eslint-plugin": "16.0.0", "@gitlab/stylelint-config": "4.1.0", "@graphql-eslint/eslint-plugin": "3.10.7", "@testing-library/dom": "^7.16.2", diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb index 85e5de46afd..9050765afd6 100644 --- a/spec/controllers/projects/notes_controller_spec.rb +++ b/spec/controllers/projects/notes_controller_spec.rb @@ -345,34 +345,77 @@ RSpec.describe Projects::NotesController do } end - context 'when `confidential` parameter is not provided' do - it 'sets `confidential` to `false` in JSON response' do + context 'when parameter is not provided' do + it 'sets `confidential` and `internal` to `false` in JSON response' do create! expect(response).to have_gitlab_http_status(:ok) expect(json_response['confidential']).to be false + expect(json_response['internal']).to be false end end - context 'when `confidential` parameter is `false`' do - let(:extra_note_params) { { confidential: false } } + context 'when is not a confidential note' do + context 'when using the `internal` parameter' do + let(:extra_note_params) { { internal: false } } - it 'sets `confidential` to `false` in JSON response' do - create! + it 'sets `confidential` and `internal` to `false` in JSON response' do + create! - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['confidential']).to be false + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['confidential']).to be false + expect(json_response['internal']).to be false + end + end + + context 'when using deprecated `confidential` parameter' do + let(:extra_note_params) { { confidential: false } } + + it 'sets `confidential` and `internal` to `false` in JSON response' do + create! + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['confidential']).to be false + expect(json_response['internal']).to be false + end end end - context 'when `confidential` parameter is `true`' do - let(:extra_note_params) { { confidential: true } } + context 'when is a confidential note' do + context 'when using the `internal` parameter' do + let(:extra_note_params) { { internal: true } } - it 'sets `confidential` to `true` in JSON response' do - create! + it 'sets `confidential` and `internal` to `true` in JSON response' do + create! - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['confidential']).to be true + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['confidential']).to be true + expect(json_response['internal']).to be true + end + end + + context 'when using deprecated `confidential` parameter' do + let(:extra_note_params) { { confidential: true } } + + it 'sets `confidential` and `internal` to `true` in JSON response' do + create! + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['confidential']).to be true + expect(json_response['internal']).to be true + end + end + + context 'when `internal` parameter is `true` and `confidential` parameter is `false`' do + let(:extra_note_params) { { internal: true, confidential: false } } + + it 'uses the `internal` param as source of truth' do + create! + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['confidential']).to be true + expect(json_response['internal']).to be true + end end end end diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json index efc31a4f833..da2d2a83a8d 100644 --- a/spec/fixtures/api/schemas/entities/discussion.json +++ b/spec/fixtures/api/schemas/entities/discussion.json @@ -67,7 +67,8 @@ "toggle_award_path": { "type": "string" }, "path": { "type": "string" }, "commands_changes": { "type": "object", "additionalProperties": true }, - "confidential": { "type": ["boolean", "null"] } + "confidential": { "type": ["boolean", "null"] }, + "internal": { "type": ["boolean", "null"] } }, "required": [ "id", "attachment", "author", "created_at", "updated_at", diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json index 25e8f9cbed6..d6d0300a64f 100644 --- a/spec/fixtures/api/schemas/public_api/v4/notes.json +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -32,7 +32,8 @@ "resolvable": { "type": "boolean" }, "resolved_by": { "type": ["string", "null"] }, "resolved_at": { "type": ["string", "null"] }, - "confidential": { "type": ["boolean", "null"] } + "confidential": { "type": ["boolean", "null"] }, + "internal": { "type": ["boolean", "null"] } }, "required": [ "id", "body", "attachment", "author", "created_at", "updated_at", diff --git a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js index 2b56cc3ca46..6c243cc8328 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_links_spec.js @@ -77,6 +77,7 @@ describe('WorkItemLinks', () => { const findToggleAddFormButton = () => wrapper.findByTestId('toggle-add-form'); const findAddLinksForm = () => wrapper.findByTestId('add-links-form'); const findFirstLinksMenu = () => wrapper.findByTestId('links-menu'); + const findChildrenCount = () => wrapper.findByTestId('children-count'); beforeEach(async () => { await createComponent(); @@ -92,7 +93,7 @@ describe('WorkItemLinks', () => { expect(findLinksBody().exists()).toBe(true); }); - it('expands on click toggle button', async () => { + it('collapses on click toggle button', async () => { findToggleButton().vm.$emit('click'); await nextTick(); @@ -140,6 +141,12 @@ describe('WorkItemLinks', () => { expect(confidentialIcon.props('name')).toBe('eye-slash'); }); + it('displays number if children', () => { + expect(findChildrenCount().exists()).toBe(true); + + expect(findChildrenCount().text()).toContain('4'); + }); + describe('when no permission to update', () => { beforeEach(async () => { await createComponent({ response: workItemHierarchyNoUpdatePermissionResponse }); diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb index 03ff7828cf5..cbf7f086dbe 100644 --- a/spec/graphql/types/notes/note_type_spec.rb +++ b/spec/graphql/types/notes/note_type_spec.rb @@ -9,6 +9,7 @@ RSpec.describe GitlabSchema.types['Note'] do body body_html confidential + internal created_at discussion id diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb index 22b5f2d5112..9c3842db31a 100644 --- a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb @@ -79,21 +79,29 @@ RSpec.describe 'Adding a Note' do context 'for an issue' do let(:noteable) { create(:issue, project: project) } - let(:mutation) do - variables = { + let(:mutation) { graphql_mutation(:create_note, variables) } + let(:variables) do + { noteable_id: GitlabSchema.id_from_object(noteable).to_s, - body: body, - confidential: true - } - - graphql_mutation(:create_note, variables) + body: body + }.merge(variables_extra) end before do project.add_developer(current_user) end - it_behaves_like 'a Note mutation with confidential notes' + context 'when using internal param' do + let(:variables_extra) { { internal: true } } + + it_behaves_like 'a Note mutation with confidential notes' + end + + context 'when using deprecated confidential param' do + let(:variables_extra) { { confidential: true } } + + it_behaves_like 'a Note mutation with confidential notes' + end end context 'when body only contains quick actions' do diff --git a/spec/services/notes/build_service_spec.rb b/spec/services/notes/build_service_spec.rb index 0e2bbcc8c66..c25895d2efa 100644 --- a/spec/services/notes/build_service_spec.rb +++ b/spec/services/notes/build_service_spec.rb @@ -170,7 +170,7 @@ RSpec.describe Notes::BuildService do end context 'when creating a new confidential comment' do - let(:params) { { confidential: true, noteable: issue } } + let(:params) { { internal: true, noteable: issue } } shared_examples 'user allowed to set comment as confidential' do it { expect(new_note.confidential).to be_truthy } @@ -219,6 +219,14 @@ RSpec.describe Notes::BuildService do it_behaves_like 'user not allowed to set comment as confidential' end + + context 'when using the deprecated `confidential` parameter' do + let(:params) { { internal: true, noteable: issue } } + + shared_examples 'user allowed to set comment as confidential' do + it { expect(new_note.confidential).to be_truthy } + end + end end context 'when replying to a confidential comment' do diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index ed64663260e..37318d76586 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -7,37 +7,74 @@ RSpec.describe Notes::CreateService do let_it_be(:issue) { create(:issue, project: project) } let_it_be(:user) { create(:user) } - let(:opts) do - { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id, confidential: true } - end + let(:base_opts) { { note: 'Awesome comment', noteable_type: 'Issue', noteable_id: issue.id } } + let(:opts) { base_opts.merge(confidential: true) } describe '#execute' do + subject(:note) { described_class.new(project, user, opts).execute } + before do project.add_maintainer(user) end context "valid params" do it 'returns a valid note' do - note = described_class.new(project, user, opts).execute - expect(note).to be_valid end it 'returns a persisted note' do - note = described_class.new(project, user, opts).execute - expect(note).to be_persisted end - it 'note has valid content' do - note = described_class.new(project, user, opts).execute + context 'with internal parameter' do + context 'when confidential' do + let(:opts) { base_opts.merge(internal: true) } + + it 'returns a confidential note' do + expect(note).to be_confidential + end + end + + context 'when not confidential' do + let(:opts) { base_opts.merge(internal: false) } + + it 'returns a confidential note' do + expect(note).not_to be_confidential + end + end + end + + context 'with confidential parameter' do + context 'when confidential' do + let(:opts) { base_opts.merge(confidential: true) } + it 'returns a confidential note' do + expect(note).to be_confidential + end + end + + context 'when not confidential' do + let(:opts) { base_opts.merge(confidential: false) } + + it 'returns a confidential note' do + expect(note).not_to be_confidential + end + end + end + + context 'with confidential and internal parameter set' do + let(:opts) { base_opts.merge(internal: true, confidential: false) } + + it 'prefers the internal parameter' do + expect(note).to be_confidential + end + end + + it 'note has valid content' do expect(note.note).to eq(opts[:note]) end it 'note belongs to the correct project' do - note = described_class.new(project, user, opts).execute - expect(note.project).to eq(project) end @@ -60,8 +97,6 @@ RSpec.describe Notes::CreateService do end context 'issue is an incident' do - subject { described_class.new(project, user, opts).execute } - let(:issue) { create(:incident, project: project) } it_behaves_like 'an incident management tracked event', :incident_management_incident_comment do diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb index 2c6118779e6..0aa3bf8944f 100644 --- a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb +++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb @@ -94,5 +94,6 @@ RSpec.shared_examples 'a Note mutation with confidential notes' do expect(mutation_response).to have_key('note') expect(mutation_response['note']['confidential']).to eq(true) + expect(mutation_response['note']['internal']).to eq(true) end end diff --git a/spec/support/shared_examples/requests/api/notes_shared_examples.rb b/spec/support/shared_examples/requests/api/notes_shared_examples.rb index a59235486ec..8479493911b 100644 --- a/spec/support/shared_examples/requests/api/notes_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/notes_shared_examples.rb @@ -376,13 +376,28 @@ RSpec.shared_examples 'noteable API with confidential notes' do |parent_type, no post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params end - it "creates a confidential note if confidential is set to true" do - post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true) + context 'with internal param' do + it "creates a confidential note if internal is set to true" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(internal: true) - expect(response).to have_gitlab_http_status(:created) - expect(json_response['body']).to eq('hi!') - expect(json_response['confidential']).to be_truthy - expect(json_response['author']['username']).to eq(user.username) + expect(response).to have_gitlab_http_status(:created) + expect(json_response['body']).to eq('hi!') + expect(json_response['confidential']).to be_truthy + expect(json_response['internal']).to be_truthy + expect(json_response['author']['username']).to eq(user.username) + end + end + + context 'with deprecated confidential param' do + it "creates a confidential note if confidential is set to true" do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/notes", user), params: params.merge(confidential: true) + + expect(response).to have_gitlab_http_status(:created) + expect(json_response['body']).to eq('hi!') + expect(json_response['confidential']).to be_truthy + expect(json_response['internal']).to be_truthy + expect(json_response['author']['username']).to eq(user.username) + end end end end diff --git a/yarn.lock b/yarn.lock index 414ef9334a1..89534d4170d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1019,10 +1019,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/at.js/-/at.js-1.5.7.tgz#1ee6f838cc4410a1d797770934df91d90df8179e" integrity sha512-c6ySRK/Ma7lxwpIVbSAF3P+xiTLrNTGTLRx4/pHK111AdFxwgUwrYF6aVZFXvmG65jHOJHoa0eQQ21RW6rm0Rg== -"@gitlab/eslint-plugin@15.0.0": - version "15.0.0" - resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-15.0.0.tgz#fbf5da0b6e6812a681552c8043f75af95de45fc4" - integrity sha512-bVKP132SYbtOhvtFRnRSy2W3x9zWbp12dkvaHJCm+3UOe1nFXGXjRfKAdw5w4bjX2Rb3oaM8/TiiUmg6J+2gZQ== +"@gitlab/eslint-plugin@16.0.0": + version "16.0.0" + resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin/-/eslint-plugin-16.0.0.tgz#83b71bb3f749c6e52138d2c1c17ac623e7b2e3db" + integrity sha512-2n7geoRPkeMAq4GCqyvFzcTgcSrTM7pdCOxfcqIeuTmh/PFGhh+m7YC+YC4enhGOCN8lo08buLZhXkSgWiHSqA== dependencies: "@babel/core" "^7.17.0" "@babel/eslint-parser" "^7.17.0" @@ -1034,7 +1034,7 @@ eslint-plugin-jest "^23.8.2" eslint-plugin-promise "^4.2.1" eslint-plugin-unicorn "^40.1.0" - eslint-plugin-vue "^8.5.0" + eslint-plugin-vue "^9.3.0" lodash "^4.17.21" "@gitlab/favicon-overlay@2.0.0": @@ -5197,17 +5197,18 @@ eslint-plugin-unicorn@^40.1.0: semver "^7.3.5" strip-indent "^3.0.0" -eslint-plugin-vue@^8.5.0: - version "8.7.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-8.7.1.tgz#f13c53547a0c9d64588a675cc5ecc6ccaf63703f" - integrity sha512-28sbtm4l4cOzoO1LtzQPxfxhQABararUb1JtqusQqObJpWX2e/gmVyeYVfepizPFne0Q5cILkYGiBoV36L12Wg== +eslint-plugin-vue@^9.3.0: + version "9.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.3.0.tgz#c3f5ce515dae387e062428725c5cf96098d9da0b" + integrity sha512-iscKKkBZgm6fGZwFt6poRoWC0Wy2dQOlwUPW++CiPoQiw1enctV2Hj5DBzzjJZfyqs+FAXhgzL4q0Ww03AgSmQ== dependencies: eslint-utils "^3.0.0" natural-compare "^1.4.0" nth-check "^2.0.1" postcss-selector-parser "^6.0.9" semver "^7.3.5" - vue-eslint-parser "^8.0.1" + vue-eslint-parser "^9.0.1" + xml-name-validator "^4.0.0" eslint-rule-composer@^0.3.0: version "0.3.0" @@ -5230,7 +5231,7 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.0.0, eslint-scope@^7.1.1: +eslint-scope@^7.1.1: version "7.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== @@ -5262,7 +5263,7 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.1.0, eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== @@ -5312,7 +5313,7 @@ eslint@8.21.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" -espree@^9.0.0, espree@^9.3.2, espree@^9.3.3: +espree@^9.3.1, espree@^9.3.2, espree@^9.3.3: version "9.3.3" resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== @@ -10487,10 +10488,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== dependencies: lru-cache "^6.0.0" @@ -11898,18 +11899,18 @@ vue-apollo@^3.0.7: serialize-javascript "^4.0.0" throttle-debounce "^2.1.0" -vue-eslint-parser@^8.0.1: - version "8.3.0" - resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz#5d31129a1b3dd89c0069ca0a1c88f970c360bd0d" - integrity sha512-dzHGG3+sYwSf6zFBa0Gi9ZDshD7+ad14DGOdTLjruRVgZXe2J+DcZ9iUhyR48z5g1PqRa20yt3Njna/veLJL/g== +vue-eslint-parser@^9.0.1: + version "9.0.3" + resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz#0c17a89e0932cc94fa6a79f0726697e13bfe3c96" + integrity sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og== dependencies: - debug "^4.3.2" - eslint-scope "^7.0.0" - eslint-visitor-keys "^3.1.0" - espree "^9.0.0" + debug "^4.3.4" + eslint-scope "^7.1.1" + eslint-visitor-keys "^3.3.0" + espree "^9.3.1" esquery "^1.4.0" lodash "^4.17.21" - semver "^7.3.5" + semver "^7.3.6" vue-functional-data-merge@^3.1.0: version "3.1.0" @@ -12360,6 +12361,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + xml@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" |