Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
8f836a3022
commit
dc6331c5ed
|
|
@ -158,9 +158,7 @@ export default {
|
|||
</gl-tooltip>
|
||||
</div>
|
||||
<div class="gl-display-flex gl-flex-direction-column gl-flex-grow-1 gl-flex-wrap gl-min-w-0">
|
||||
<div
|
||||
class="gl-display-flex gl-flex-wrap flex-xl-nowrap gl-justify-content-space-between gl-gap-3 gl-min-w-0 gl-mb-2"
|
||||
>
|
||||
<div class="gl-flex gl-justify-between gl-gap-3 gl-min-w-0 gl-mb-2">
|
||||
<div class="item-title gl-min-w-0">
|
||||
<span v-if="childItem.confidential">
|
||||
<gl-icon
|
||||
|
|
|
|||
|
|
@ -54,10 +54,16 @@ export default {
|
|||
required: false,
|
||||
default: true,
|
||||
},
|
||||
disableContent: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
prefetchedWorkItem: null,
|
||||
updateInProgress: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -86,6 +92,9 @@ export default {
|
|||
.map((child) => findHierarchyWidgets(child.widgets) || {})
|
||||
.some((hierarchy) => hierarchy.hasChildren);
|
||||
},
|
||||
disableList() {
|
||||
return this.disableContent || this.updateInProgress;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async removeChild(child) {
|
||||
|
|
@ -121,6 +130,7 @@ export default {
|
|||
},
|
||||
async undoChildRemoval(child) {
|
||||
try {
|
||||
this.updateInProgress = true;
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: updateWorkItemMutation,
|
||||
variables: { input: { id: child.id, hierarchyWidget: { parentId: this.workItemId } } },
|
||||
|
|
@ -140,6 +150,8 @@ export default {
|
|||
} catch (error) {
|
||||
this.$emit('error', s__('WorkItem|Something went wrong while undoing child removal.'));
|
||||
Sentry.captureException(error);
|
||||
} finally {
|
||||
this.updateInProgress = false;
|
||||
}
|
||||
},
|
||||
addWorkItemQuery({ iid }) {
|
||||
|
|
@ -210,6 +222,7 @@ export default {
|
|||
const updatedChildren = this.children.slice();
|
||||
updatedChildren.splice(oldIndex, 1);
|
||||
updatedChildren.splice(newIndex, 0, targetItem);
|
||||
this.updateInProgress = true;
|
||||
|
||||
this.$apollo
|
||||
.mutate({
|
||||
|
|
@ -255,6 +268,9 @@ export default {
|
|||
.catch((error) => {
|
||||
this.$emit('error', error.message);
|
||||
Sentry.captureException(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.updateInProgress = false;
|
||||
});
|
||||
},
|
||||
},
|
||||
|
|
@ -266,7 +282,8 @@ export default {
|
|||
:is="treeRootWrapper"
|
||||
v-bind="treeRootOptions"
|
||||
class="content-list"
|
||||
:class="{ 'gl-cursor-grab sortable-container': canReorder }"
|
||||
data-testid="child-items-container"
|
||||
:class="{ 'gl-cursor-grab sortable-container': canReorder, 'disabled-content': disableList }"
|
||||
@end="handleDragOnEnd"
|
||||
>
|
||||
<work-item-link-child
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ export default {
|
|||
widgetName: TASKS_ANCHOR,
|
||||
showLabels: true,
|
||||
fetchNextPageInProgress: false,
|
||||
disableContent: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -329,6 +330,7 @@ export default {
|
|||
:parent-milestone="issuableMilestone"
|
||||
:form-type="formType"
|
||||
:parent-work-item-type="workItem.workItemType.name"
|
||||
@update-in-progress="disableContent = $event"
|
||||
@cancel="hideAddForm"
|
||||
/>
|
||||
<work-item-children-wrapper
|
||||
|
|
@ -338,6 +340,7 @@ export default {
|
|||
:work-item-id="issuableGid"
|
||||
:work-item-iid="iid"
|
||||
:show-labels="showLabels"
|
||||
:disable-content="disableContent"
|
||||
@error="error = $event"
|
||||
@show-modal="openChild"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -278,8 +278,12 @@ export default {
|
|||
this.error = null;
|
||||
this.isInputValid = true;
|
||||
},
|
||||
markFormSubmitInProgress(value) {
|
||||
this.submitInProgress = value;
|
||||
this.$emit('update-in-progress', this.submitInProgress);
|
||||
},
|
||||
addChild() {
|
||||
this.submitInProgress = true;
|
||||
this.markFormSubmitInProgress(true);
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: updateWorkItemHierarchyMutation,
|
||||
|
|
@ -293,6 +297,9 @@ export default {
|
|||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
// Marking submitInProgress cannot be in finally block
|
||||
// as the form may get close before the event is emitted
|
||||
this.markFormSubmitInProgress(false);
|
||||
if (data.workItemUpdate?.errors?.length) {
|
||||
[this.error] = data.workItemUpdate.errors;
|
||||
} else {
|
||||
|
|
@ -304,17 +311,17 @@ export default {
|
|||
.catch(() => {
|
||||
this.error = this.$options.i18n.addChildErrorMessage;
|
||||
this.isInputValid = false;
|
||||
this.markFormSubmitInProgress(false);
|
||||
})
|
||||
.finally(() => {
|
||||
this.search = '';
|
||||
this.submitInProgress = false;
|
||||
});
|
||||
},
|
||||
createChild() {
|
||||
if (!this.canSubmitForm) {
|
||||
return;
|
||||
}
|
||||
this.submitInProgress = true;
|
||||
this.markFormSubmitInProgress(true);
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: createWorkItemMutation,
|
||||
|
|
@ -329,6 +336,9 @@ export default {
|
|||
}),
|
||||
})
|
||||
.then(({ data }) => {
|
||||
// Marking submitInProgress cannot be in finally block
|
||||
// as the form may get close before the event is emitted
|
||||
this.markFormSubmitInProgress(false);
|
||||
if (data.workItemCreate?.errors?.length) {
|
||||
[this.error] = data.workItemCreate.errors;
|
||||
} else {
|
||||
|
|
@ -340,11 +350,11 @@ export default {
|
|||
.catch(() => {
|
||||
this.error = this.$options.i18n.createChildErrorMessage;
|
||||
this.isInputValid = false;
|
||||
this.markFormSubmitInProgress(false);
|
||||
})
|
||||
.finally(() => {
|
||||
this.search = '';
|
||||
this.childToCreateTitle = null;
|
||||
this.submitInProgress = false;
|
||||
});
|
||||
},
|
||||
closeForm() {
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ export default {
|
|||
showLabels: true,
|
||||
fetchNextPageInProgress: false,
|
||||
workItem: null,
|
||||
disableContent: false,
|
||||
};
|
||||
},
|
||||
apollo: {
|
||||
|
|
@ -324,6 +325,7 @@ export default {
|
|||
:parent-confidential="confidential"
|
||||
@cancel="hideAddForm"
|
||||
@addChild="$emit('addChild')"
|
||||
@update-in-progress="disableContent = $event"
|
||||
/>
|
||||
<work-item-children-wrapper
|
||||
:children="children"
|
||||
|
|
@ -333,6 +335,7 @@ export default {
|
|||
:work-item-iid="workItemIid"
|
||||
:work-item-type="workItemType"
|
||||
:show-labels="showLabels"
|
||||
:disable-content="disableContent"
|
||||
@error="error = $event"
|
||||
@show-modal="showModal"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -30,10 +30,7 @@ class JsonSchemaValidator < ActiveModel::EachValidator
|
|||
|
||||
if options[:detail_errors]
|
||||
validator.validate(value).each do |error|
|
||||
message = format(
|
||||
_("'%{data_pointer}' must be a valid '%{type}'"),
|
||||
data_pointer: error['data_pointer'], type: error['type']
|
||||
)
|
||||
message = format_error_message(error)
|
||||
record.errors.add(attribute, message)
|
||||
end
|
||||
else
|
||||
|
|
@ -45,6 +42,32 @@ class JsonSchemaValidator < ActiveModel::EachValidator
|
|||
|
||||
attr_reader :base_directory
|
||||
|
||||
def format_error_message(error)
|
||||
case error['type']
|
||||
when 'oneOf'
|
||||
format_one_of_error(error)
|
||||
else
|
||||
error['error']
|
||||
end
|
||||
end
|
||||
|
||||
def format_one_of_error(error)
|
||||
schema_options = error['schema']['oneOf']
|
||||
required_props = schema_options.flat_map { |option| option['required'] }.uniq
|
||||
|
||||
message = if error['root_schema']['type'] == 'array'
|
||||
_("value at %{data_pointer} should use only one of: %{requirements}")
|
||||
else
|
||||
_("should use only one of: %{requirements}")
|
||||
end
|
||||
|
||||
format(
|
||||
message,
|
||||
requirements: required_props.join(', '),
|
||||
data_pointer: error['data_pointer']
|
||||
)
|
||||
end
|
||||
|
||||
def valid_schema?(value)
|
||||
validator.valid?(value)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -19021,6 +19021,7 @@ GitLab CI/CD configuration template.
|
|||
| <a id="clusteragentid"></a>`id` | [`ID!`](#id) | ID of the cluster agent. |
|
||||
| <a id="clusteragentname"></a>`name` | [`String`](#string) | Name of the cluster agent. |
|
||||
| <a id="clusteragentproject"></a>`project` | [`Project`](#project) | Project this cluster agent is associated with. |
|
||||
| <a id="clusteragentremotedevelopmentagentconfig"></a>`remoteDevelopmentAgentConfig` | [`RemoteDevelopmentAgentConfig`](#remotedevelopmentagentconfig) | Remote development agent config for the cluster agent. |
|
||||
| <a id="clusteragenttokens"></a>`tokens` | [`ClusterAgentTokenConnection`](#clusteragenttokenconnection) | Tokens associated with the cluster agent. (see [Connections](#connections)) |
|
||||
| <a id="clusteragentupdatedat"></a>`updatedAt` | [`Time`](#time) | Timestamp the cluster agent was updated. |
|
||||
| <a id="clusteragentuseraccessauthorizations"></a>`userAccessAuthorizations` | [`ClusterAgentAuthorizationUserAccess`](#clusteragentauthorizationuseraccess) | User access config for the cluster agent. |
|
||||
|
|
@ -31135,6 +31136,28 @@ Represents the source code attached to a release in a particular format.
|
|||
| <a id="releasesourceformat"></a>`format` | [`String`](#string) | Format of the source. |
|
||||
| <a id="releasesourceurl"></a>`url` | [`String`](#string) | Download URL of the source. |
|
||||
|
||||
### `RemoteDevelopmentAgentConfig`
|
||||
|
||||
Represents a remote development agent configuration.
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="remotedevelopmentagentconfigclusteragent"></a>`clusterAgent` | [`ClusterAgent!`](#clusteragent) | Cluster agent that the remote development agent config belongs to. |
|
||||
| <a id="remotedevelopmentagentconfigcreatedat"></a>`createdAt` | [`Time!`](#time) | Timestamp of when the remote development agent config was created. |
|
||||
| <a id="remotedevelopmentagentconfigdefaultmaxhoursbeforetermination"></a>`defaultMaxHoursBeforeTermination` | [`Int!`](#int) | Default max hours before worksapce termination of the remote development agent config. |
|
||||
| <a id="remotedevelopmentagentconfigdnszone"></a>`dnsZone` | [`String!`](#string) | DNS zone where workspaces are available. |
|
||||
| <a id="remotedevelopmentagentconfigenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether remote development is enabled for the GitLab agent. |
|
||||
| <a id="remotedevelopmentagentconfiggitlabworkspacesproxynamespace"></a>`gitlabWorkspacesProxyNamespace` | [`String!`](#string) | Namespace where gitlab-workspaces-proxy is installed. |
|
||||
| <a id="remotedevelopmentagentconfigid"></a>`id` | [`RemoteDevelopmentRemoteDevelopmentAgentConfigID!`](#remotedevelopmentremotedevelopmentagentconfigid) | Global ID of the remote development agent config. |
|
||||
| <a id="remotedevelopmentagentconfigmaxhoursbeforeterminationlimit"></a>`maxHoursBeforeTerminationLimit` | [`Int!`](#int) | Max hours before worksapce termination limit of the remote development agent config. |
|
||||
| <a id="remotedevelopmentagentconfignetworkpolicyenabled"></a>`networkPolicyEnabled` | [`Boolean!`](#boolean) | Whether the network policy of the remote development agent config is enabled. |
|
||||
| <a id="remotedevelopmentagentconfigprojectid"></a>`projectId` | [`ID`](#id) | ID of the project that the remote development agent config belongs to. |
|
||||
| <a id="remotedevelopmentagentconfigupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of the last update to any mutable remote development agent config property. |
|
||||
| <a id="remotedevelopmentagentconfigworkspacesperuserquota"></a>`workspacesPerUserQuota` | [`Int!`](#int) | Maximum number of workspaces per user. |
|
||||
| <a id="remotedevelopmentagentconfigworkspacesquota"></a>`workspacesQuota` | [`Int!`](#int) | Maximum number of workspaces for the GitLab agent. |
|
||||
|
||||
### `Repository`
|
||||
|
||||
#### Fields
|
||||
|
|
@ -38683,6 +38706,12 @@ A `ReleasesLinkID` is a global ID. It is encoded as a string.
|
|||
|
||||
An example `ReleasesLinkID` is: `"gid://gitlab/Releases::Link/1"`.
|
||||
|
||||
### `RemoteDevelopmentRemoteDevelopmentAgentConfigID`
|
||||
|
||||
A `RemoteDevelopmentRemoteDevelopmentAgentConfigID` is a global ID. It is encoded as a string.
|
||||
|
||||
An example `RemoteDevelopmentRemoteDevelopmentAgentConfigID` is: `"gid://gitlab/RemoteDevelopment::RemoteDevelopmentAgentConfig/1"`.
|
||||
|
||||
### `RemoteDevelopmentWorkspaceID`
|
||||
|
||||
A `RemoteDevelopmentWorkspaceID` is a global ID. It is encoded as a string.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Parameters:
|
|||
| `search` | string | no | Return the list of authorized groups matching the search criteria |
|
||||
| `order_by` | string | no | Order groups by `name`, `path`, `id`, or `similarity`. Default is `name` |
|
||||
| `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` |
|
||||
| `statistics` | boolean | no | Include group statistics (administrators only).<br>*Note:* The REST API response does not provide the full `RootStorageStatistics` data that is shown in the UI. To match the data in the UI, use GraphQL instead of REST. For more information, see the [Group GraphQL API resources](../api/graphql/reference/index.md#group).|
|
||||
| `statistics` | boolean | no | Include group statistics (administrators only).<br>*Note:* For top-level groups, the response returns the full `root_storage_statistics` data displayed in the UI. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/469254) in GitLab 17.3. |
|
||||
| `visibility` | string | no | Limit to groups with `public`, `internal`, or `private` visibility. |
|
||||
| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (administrators only) |
|
||||
| `owned` | boolean | no | Limit to groups explicitly owned by the current user |
|
||||
|
|
@ -95,7 +95,7 @@ GET /groups
|
|||
]
|
||||
```
|
||||
|
||||
When adding the parameter `statistics=true` and the authenticated user is an administrator, additional group statistics are returned.
|
||||
When adding the parameter `statistics=true` and the authenticated user is an administrator, additional group statistics are returned. For top-level groups, `root_storage_statistics` are added as well.
|
||||
|
||||
```plaintext
|
||||
GET /groups?statistics=true
|
||||
|
|
@ -154,6 +154,20 @@ GET /groups?statistics=true
|
|||
"snippets_size": 50,
|
||||
"uploads_size": 0
|
||||
},
|
||||
"root_storage_statistics": {
|
||||
"build_artifacts_size": 0,
|
||||
"container_registry_size": 0,
|
||||
"container_registry_size_is_estimated": false,
|
||||
"dependency_proxy_size": 0,
|
||||
"lfs_objects_size": 0,
|
||||
"packages_size": 0,
|
||||
"pipeline_artifacts_size": 0,
|
||||
"repository_size": 0,
|
||||
"snippets_size": 0,
|
||||
"storage_size": 0,
|
||||
"uploads_size": 0,
|
||||
"wiki_size": 0
|
||||
},
|
||||
"wiki_access_level": "private",
|
||||
"duo_features_enabled": true,
|
||||
"lock_duo_features_enabled": false,
|
||||
|
|
|
|||
|
|
@ -86,9 +86,24 @@ For more information about upgrading GitLab Helm Chart, see [the release notes f
|
|||
|
||||
## 16.11.0
|
||||
|
||||
- A [`groups_direct` field was added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/146881)
|
||||
to the [JSON web token (ID token)](../../ci/secrets/id_token_authentication.md).
|
||||
- If you use GitLab CI/CD ID tokens to authenticate with third party services,
|
||||
this change can cause the HTTP header size to increase. Proxy servers might reject
|
||||
the request if the headers get too big.
|
||||
- If possible, increase the header limit on the receiving system.
|
||||
- See [issue 467253](https://gitlab.com/gitlab-org/gitlab/-/issues/467253) for more details.
|
||||
- After upgrading to GitLab 16.11 some users with large environments and databases experience
|
||||
timeouts loading source code pages in the web UI.
|
||||
- These timeouts are caused by slow PostgreSQL queries for pipeline data, which then
|
||||
exceed the internal 60 second timeout.
|
||||
- You can still clone Git repositories, and other requests for repository data works.
|
||||
- See [issue 472420](https://gitlab.com/gitlab-org/gitlab/-/issues/472420) for more details,
|
||||
including steps to confirm you're affected and housekeeping to run in PostgreSQL to correct it.
|
||||
|
||||
### Linux package installations
|
||||
|
||||
In GitLab 16.11, PostgreSQL will automatically be upgraded to 14.x except for the following cases:
|
||||
In GitLab 16.11, PostgreSQL is automatically upgraded to 14.x except for the following cases:
|
||||
|
||||
- You are running the database in high availability using Patroni.
|
||||
- Your database nodes are part of a GitLab Geo configuration.
|
||||
|
|
|
|||
|
|
@ -716,6 +716,7 @@ Container Scanning for Registry populates the Vulnerability Report only when a n
|
|||
- You must have at least the Maintainer role in a project to enable Container Scanning for Registry.
|
||||
- The project being used must not be empty. If you are utilizing an empty project solely for storing container images, this feature won't function as intended. As a workaround, ensure the project contains an initial commit on the default branch.
|
||||
- By default there is a limit of `50` scans per project per day.
|
||||
- You must [configure container registry notifications](../../../administration/packages/container_registry.md#configure-container-registry-notifications).
|
||||
|
||||
### Enabling Container Scanning for Registry
|
||||
|
||||
|
|
|
|||
|
|
@ -709,7 +709,7 @@ To enable the analyzer, either:
|
|||
- Create a [scan execution policy](../policies/scan_execution_policies.md) that enforces dependency
|
||||
scanning.
|
||||
- Edit the `.gitlab-ci.yml` file manually.
|
||||
- [Use CI/CD components](#use-cicd-components) (Android projects only)
|
||||
- [Use CI/CD components](#use-cicd-components)
|
||||
|
||||
#### Use a preconfigured merge request
|
||||
|
||||
|
|
@ -771,9 +771,9 @@ Pipelines now include a Dependency Scanning job.
|
|||
Use [CI/CD components](../../../ci/components/index.md) to perform Dependency Scanning of your
|
||||
application. For instructions, see the respective component's README file.
|
||||
|
||||
##### Available CI/CD components per language and package manager
|
||||
##### Available CI/CD components
|
||||
|
||||
- [Android applications](https://gitlab.com/explore/catalog/components/android-dependency-scanning)
|
||||
See <https://gitlab.com/explore/catalog/components/dependency-scanning>
|
||||
|
||||
### Running jobs in merge request pipelines
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,15 @@ module API
|
|||
expose :uploads_size
|
||||
end
|
||||
end
|
||||
|
||||
expose :root_storage_statistics, using: Entities::Namespace::RootStorageStatistics,
|
||||
if: ->(group, opts) {
|
||||
expose_root_storage_statistics?(group, opts)
|
||||
}
|
||||
|
||||
def expose_root_storage_statistics?(group, opts)
|
||||
opts[:statistics] && group.root?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module Entities
|
||||
class Namespace
|
||||
class RootStorageStatistics < Grape::Entity
|
||||
expose :build_artifacts_size, documentation: { type: 'integer', desc: 'CI artifacts size in bytes.' }
|
||||
expose :container_registry_size, documentation: { type: 'integer', desc: 'Container Registry size in bytes.' }
|
||||
expose :registry_size_estimated,
|
||||
as: :container_registry_size_is_estimated,
|
||||
documentation: { type: 'boolean',
|
||||
desc: 'Indicates whether the deduplicated Container Registry size for ' \
|
||||
'the namespace is an estimated value or not.' }
|
||||
expose :dependency_proxy_size, documentation: { type: 'integer', desc: 'Dependency Proxy sizes in bytes.' }
|
||||
expose :lfs_objects_size, documentation: { type: 'integer', desc: 'LFS objects size in bytes.' }
|
||||
expose :packages_size, documentation: { type: 'integer', desc: 'Packages size in bytes.' }
|
||||
expose :pipeline_artifacts_size,
|
||||
documentation: { type: 'integer', desc: 'CI pipeline artifacts size in bytes.' }
|
||||
expose :repository_size, documentation: { type: 'integer', desc: 'Git repository size in bytes.' }
|
||||
expose :snippets_size, documentation: { type: 'integer', desc: 'Snippets size in bytes.' }
|
||||
expose :storage_size, documentation: { type: 'integer', desc: 'Total storage in bytes.' }
|
||||
expose :uploads_size, documentation: { type: 'integer', desc: 'Uploads size in bytes.' }
|
||||
expose :wiki_size, documentation: { type: 'integer', desc: 'Wiki size in bytes.' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1533,9 +1533,6 @@ msgstr ""
|
|||
msgid "%{wildcards_link_start}Wildcards%{wildcards_link_end} such as %{code_tag_start}v*%{code_tag_end} or %{code_tag_start}*-release%{code_tag_end} are supported."
|
||||
msgstr ""
|
||||
|
||||
msgid "'%{data_pointer}' must be a valid '%{type}'"
|
||||
msgstr ""
|
||||
|
||||
msgid "'%{group_name}' has been scheduled for removal on %{removal_time}."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -64528,6 +64525,9 @@ msgstr ""
|
|||
msgid "should have length between 16 to 24 characters."
|
||||
msgstr ""
|
||||
|
||||
msgid "should use only one of: %{requirements}"
|
||||
msgstr ""
|
||||
|
||||
msgid "show %{count} more"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -64707,6 +64707,9 @@ msgstr ""
|
|||
msgid "v%{version} published %{timeAgo}"
|
||||
msgstr ""
|
||||
|
||||
msgid "value at %{data_pointer} should use only one of: %{requirements}"
|
||||
msgstr ""
|
||||
|
||||
msgid "value for '%{storage}' must be an integer"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import Vue, { nextTick } from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import Draggable from 'vuedraggable';
|
||||
|
||||
import createMockApollo from 'helpers/mock_apollo_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { isLoggedIn } from '~/lib/utils/common_utils';
|
||||
import WorkItemChildrenWrapper from '~/work_items/components/work_item_links/work_item_children_wrapper.vue';
|
||||
import WorkItemLinkChild from '~/work_items/components/work_item_links/work_item_link_child.vue';
|
||||
import updateWorkItemMutation from '~/work_items/graphql/update_work_item.mutation.graphql';
|
||||
|
|
@ -18,6 +20,8 @@ import {
|
|||
workItemByIidResponseFactory,
|
||||
} from '../../mock_data';
|
||||
|
||||
jest.mock('~/lib/utils/common_utils');
|
||||
|
||||
describe('WorkItemChildrenWrapper', () => {
|
||||
let wrapper;
|
||||
|
||||
|
|
@ -30,6 +34,8 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
.mockResolvedValue(changeWorkItemParentMutationResponse);
|
||||
|
||||
const findWorkItemLinkChildItems = () => wrapper.findAllComponents(WorkItemLinkChild);
|
||||
const findDraggable = () => wrapper.findComponent(Draggable);
|
||||
const findChildItemsContainer = () => wrapper.findByTestId('child-items-container');
|
||||
|
||||
Vue.use(VueApollo);
|
||||
|
||||
|
|
@ -38,6 +44,8 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
confidential = false,
|
||||
children = childrenWorkItems,
|
||||
mutationHandler = updateWorkItemMutationHandler,
|
||||
disableContent = false,
|
||||
canUpdate = false,
|
||||
} = {}) => {
|
||||
const mockApollo = createMockApollo([
|
||||
[workItemByIidQuery, getWorkItemQueryHandler],
|
||||
|
|
@ -62,6 +70,8 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
workItemIid: '1',
|
||||
confidential,
|
||||
children,
|
||||
disableContent,
|
||||
canUpdate,
|
||||
},
|
||||
mocks: {
|
||||
$toast,
|
||||
|
|
@ -114,6 +124,44 @@ describe('WorkItemChildrenWrapper', () => {
|
|||
},
|
||||
);
|
||||
|
||||
it('does not render draggable component when user is not logged in', () => {
|
||||
createComponent({ canUpdate: true });
|
||||
|
||||
expect(findDraggable().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('disables list when `disableContent` is true', () => {
|
||||
createComponent({ disableContent: true });
|
||||
|
||||
expect(findChildItemsContainer().classes('disabled-content')).toBe(true);
|
||||
});
|
||||
|
||||
describe('when user is logged in', () => {
|
||||
beforeEach(() => {
|
||||
isLoggedIn.mockReturnValue(true);
|
||||
});
|
||||
|
||||
it('renders draggable component without disabling the list', () => {
|
||||
createComponent({ canUpdate: true });
|
||||
|
||||
expect(findDraggable().exists()).toBe(true);
|
||||
expect(findDraggable().classes('disabled-content')).toBe(false);
|
||||
});
|
||||
|
||||
it('does not render draggable component when user has no permission', () => {
|
||||
createComponent({ canUpdate: false });
|
||||
|
||||
expect(findDraggable().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('disables the list when `disableContent` is true', () => {
|
||||
createComponent({ disableContent: true, canUpdate: true });
|
||||
|
||||
expect(findDraggable().exists()).toBe(true);
|
||||
expect(findDraggable().classes('disabled-content')).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when removing child work item', () => {
|
||||
const workItem = { id: 'gid://gitlab/WorkItem/2' };
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,19 @@ describe('WorkItemLinksForm', () => {
|
|||
);
|
||||
|
||||
describe('creating a new work item', () => {
|
||||
const submitForm = ({ title, fullPath }) => {
|
||||
findInput().vm.$emit('input', title);
|
||||
|
||||
if (fullPath) {
|
||||
findProjectSelector().vm.$emit('selectProject', fullPath);
|
||||
}
|
||||
|
||||
// Trigger form submission
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
};
|
||||
|
||||
describe('for project level work items', () => {
|
||||
beforeEach(async () => {
|
||||
await createComponent();
|
||||
|
|
@ -171,11 +184,10 @@ describe('WorkItemLinksForm', () => {
|
|||
expect(findFormGroup().props('invalidFeedback')).toBe(null);
|
||||
expect(findInput().props('state')).toBe(true);
|
||||
|
||||
findInput().vm.$emit('input', 'Create task test');
|
||||
// Trigger form submission
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
submitForm({ title: 'Create task test' });
|
||||
|
||||
expect(wrapper.emitted('update-in-progress')).toEqual([[true]]);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findFormGroup().props('state')).toBe(false);
|
||||
|
|
@ -183,14 +195,14 @@ describe('WorkItemLinksForm', () => {
|
|||
'Something went wrong when trying to create a child. Please try again.',
|
||||
);
|
||||
expect(findInput().props('state')).toBe(false);
|
||||
expect(wrapper.emitted('update-in-progress')[1]).toEqual([false]);
|
||||
});
|
||||
|
||||
it('creates child task in non confidential parent and closes the form', async () => {
|
||||
findInput().vm.$emit('input', 'Create task test');
|
||||
submitForm({ title: 'Create task test' });
|
||||
|
||||
expect(wrapper.emitted('update-in-progress')).toEqual([[true]]);
|
||||
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(createMutationResolver).toHaveBeenCalledWith({
|
||||
|
|
@ -206,17 +218,18 @@ describe('WorkItemLinksForm', () => {
|
|||
});
|
||||
expect(wrapper.emitted('addChild')).toEqual([[]]);
|
||||
expect(wrapper.emitted('cancel')).toEqual([[]]);
|
||||
expect(wrapper.emitted('update-in-progress')[1]).toEqual([false]);
|
||||
});
|
||||
|
||||
it('creates child task in confidential parent', async () => {
|
||||
await createComponent({ parentConfidential: true });
|
||||
|
||||
findInput().vm.$emit('input', 'Create confidential task');
|
||||
submitForm({ title: 'Create confidential task' });
|
||||
|
||||
expect(wrapper.emitted('update-in-progress')).toEqual([[true]]);
|
||||
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.vm.childWorkItemType).toEqual(workItemTypeIdForTask);
|
||||
expect(createMutationResolver).toHaveBeenCalledWith({
|
||||
input: {
|
||||
|
|
@ -229,6 +242,7 @@ describe('WorkItemLinksForm', () => {
|
|||
confidential: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.emitted('update-in-progress')[1]).toEqual([false]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -250,13 +264,9 @@ describe('WorkItemLinksForm', () => {
|
|||
});
|
||||
|
||||
it('creates child issue in non confidential parent and closes the form', async () => {
|
||||
findInput().vm.$emit('input', 'Create issue test');
|
||||
submitForm({ title: 'Create issue test', fullPath: projectData[0].fullPath });
|
||||
|
||||
findProjectSelector().vm.$emit('selectProject', projectData[0].fullPath);
|
||||
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
expect(wrapper.emitted('update-in-progress')).toEqual([[true]]);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
|
@ -272,6 +282,7 @@ describe('WorkItemLinksForm', () => {
|
|||
},
|
||||
});
|
||||
expect(wrapper.emitted('addChild')).toEqual([[]]);
|
||||
expect(wrapper.emitted('update-in-progress')[1]).toEqual([false]);
|
||||
expect(wrapper.emitted('cancel')).toEqual([[]]);
|
||||
});
|
||||
|
||||
|
|
@ -283,13 +294,9 @@ describe('WorkItemLinksForm', () => {
|
|||
childrenType: WORK_ITEM_TYPE_ENUM_ISSUE,
|
||||
});
|
||||
|
||||
findInput().vm.$emit('input', 'Create confidential issue');
|
||||
submitForm({ title: 'Create confidential issue', fullPath: projectData[0].fullPath });
|
||||
|
||||
findProjectSelector().vm.$emit('selectProject', projectData[0].fullPath);
|
||||
|
||||
findForm().vm.$emit('submit', {
|
||||
preventDefault: jest.fn(),
|
||||
});
|
||||
expect(wrapper.emitted('update-in-progress')).toEqual([[true]]);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
|
|
@ -304,6 +311,7 @@ describe('WorkItemLinksForm', () => {
|
|||
confidential: true,
|
||||
},
|
||||
});
|
||||
expect(wrapper.emitted('update-in-progress')[1]).toEqual([false]);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,21 +4,67 @@ require 'spec_helper'
|
|||
|
||||
RSpec.describe API::Entities::Group, feature_category: :groups_and_projects do
|
||||
let_it_be(:group) do
|
||||
base_group = create(:group) { |g| create(:project_statistics, namespace_id: g.id) }
|
||||
base_group = create(:group) do |g|
|
||||
create(:project_statistics, namespace_id: g.id)
|
||||
create(:namespace_root_storage_statistics, namespace_id: g.id)
|
||||
end
|
||||
Group.with_statistics.find(base_group.id)
|
||||
end
|
||||
|
||||
subject(:json) { described_class.new(group, { with_custom_attributes: true, statistics: true }).as_json }
|
||||
subject(:json) { described_class.new(group, options).as_json }
|
||||
|
||||
it 'returns expected data' do
|
||||
expect(json.keys).to(
|
||||
include(
|
||||
:organization_id, :path, :description, :visibility, :share_with_group_lock, :require_two_factor_authentication,
|
||||
:two_factor_grace_period, :project_creation_level, :auto_devops_enabled,
|
||||
:subgroup_creation_level, :emails_disabled, :emails_enabled, :lfs_enabled, :default_branch_protection,
|
||||
:default_branch_protection_defaults, :avatar_url, :request_access_enabled, :full_name, :full_path, :created_at,
|
||||
:parent_id, :organization_id, :shared_runners_setting, :custom_attributes, :statistics, :default_branch
|
||||
context 'with statistics' do
|
||||
let(:options) { { with_custom_attributes: true, statistics: true } }
|
||||
|
||||
it 'returns expected data' do
|
||||
expect(json.keys).to(
|
||||
include(
|
||||
:organization_id, :path, :description, :visibility, :share_with_group_lock,
|
||||
:require_two_factor_authentication, :two_factor_grace_period, :project_creation_level, :auto_devops_enabled,
|
||||
:subgroup_creation_level, :emails_disabled, :emails_enabled, :lfs_enabled, :default_branch_protection,
|
||||
:default_branch_protection_defaults, :avatar_url, :request_access_enabled, :full_name, :full_path,
|
||||
:created_at, :parent_id, :organization_id, :shared_runners_setting, :custom_attributes, :statistics,
|
||||
:default_branch, :root_storage_statistics
|
||||
)
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
context 'on a sub-group' do
|
||||
let(:subgroup) do
|
||||
subgroup = create(:group, parent: group, path: "#{group.path}-subgroup") do |g|
|
||||
create(:project_statistics, namespace_id: g.id)
|
||||
end
|
||||
Group.with_statistics.find(subgroup.id)
|
||||
end
|
||||
|
||||
subject(:json) { described_class.new(subgroup, options).as_json }
|
||||
|
||||
it 'does not expose root storage statistics' do
|
||||
expect(json.keys).not_to(include(:root_storage_statistics))
|
||||
end
|
||||
end
|
||||
|
||||
context 'on a group without root storage statistics' do
|
||||
let(:group_without_root_storage_statistics) do
|
||||
base_group = create(:group) do |g|
|
||||
create(:project_statistics, namespace_id: g.id)
|
||||
end
|
||||
Group.with_statistics.find(base_group.id)
|
||||
end
|
||||
|
||||
subject(:json) { described_class.new(group_without_root_storage_statistics, options).as_json }
|
||||
|
||||
it 'returns nil for root storage statistics' do
|
||||
expect(json[:root_storage_statistics]).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without statistics' do
|
||||
let(:options) { { with_custom_attributes: true, statistics: false } }
|
||||
|
||||
it 'does not expose statistics' do
|
||||
expect(json.keys).not_to(include(:statistics, :root_storage_statistics))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe API::Entities::Namespace::RootStorageStatistics, feature_category: :groups_and_projects do
|
||||
let(:root_storage_statistics) { create(:namespace_root_storage_statistics) }
|
||||
|
||||
subject(:entity) { described_class.new(root_storage_statistics).as_json }
|
||||
|
||||
it 'exposes correct attributes' do
|
||||
expect(entity.keys).to(
|
||||
include(
|
||||
:build_artifacts_size,
|
||||
:container_registry_size,
|
||||
:container_registry_size_is_estimated,
|
||||
:dependency_proxy_size,
|
||||
:lfs_objects_size,
|
||||
:packages_size,
|
||||
:pipeline_artifacts_size,
|
||||
:repository_size,
|
||||
:snippets_size,
|
||||
:storage_size,
|
||||
:uploads_size,
|
||||
:wiki_size
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
@ -136,7 +136,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
|
|||
it 'raises an error' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors.first)
|
||||
.to match %r{image executor opts '/docker/platform' must be a valid 'string'}
|
||||
.to match %r{image executor opts value at `/docker/platform` is not a string}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -184,7 +184,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
|
|||
it 'raises an error' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors.first)
|
||||
.to match %r{image executor opts '/docker/user' must be a valid 'string'}
|
||||
.to match %r{image executor opts value at `/docker/user` is not a string}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -194,8 +194,9 @@ RSpec.describe Gitlab::Ci::Config::Entry::Image do
|
|||
|
||||
it 'is not valid' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors.first)
|
||||
.to match %r{image executor opts '/docker/unknown_key' must be a valid 'schema'}
|
||||
expect(entry.errors.first).to match(
|
||||
%r{image executor opts object property at `/docker/unknown_key` is a disallowed additional property}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -270,14 +270,34 @@ RSpec.describe Gitlab::Ci::Config::Entry::Job, feature_category: :pipeline_compo
|
|||
using RSpec::Parameterized::TableSyntax
|
||||
|
||||
where(:case_name, :config, :error) do
|
||||
'when only step is used without name' | { stage: 'build',
|
||||
run: [{ step: 'some reference' }] } | 'job run \'/0\' must be a valid \'required\''
|
||||
'when only script is used without name' | { stage: 'build',
|
||||
run: [{ script: 'echo' }] } | 'job run \'/0\' must be a valid \'required\''
|
||||
'when step and script are used together' | { stage: 'build',
|
||||
run: [{ name: 'step1', step: 'some reference', script: 'echo' }] } | 'job run \'/0\' must be a valid \'oneof\''
|
||||
'when a subkey does not exist' | { stage: 'build',
|
||||
run: [{ name: 'step1', invalid_key: 'some value' }] } | 'job run \'/0\' must be a valid \'required\''
|
||||
'when only step is used without name' | {
|
||||
stage: 'build',
|
||||
run: [{ step: 'some reference' }]
|
||||
} | 'job run object at `/0` is missing required properties: name'
|
||||
|
||||
'when only script is used without name' | {
|
||||
stage: 'build',
|
||||
run: [{ script: 'echo' }]
|
||||
} | 'job run object at `/0` is missing required properties: name'
|
||||
|
||||
'when step and script are used together' | {
|
||||
stage: 'build',
|
||||
run: [{
|
||||
name: 'step1',
|
||||
step: 'some reference',
|
||||
script: 'echo'
|
||||
}]
|
||||
} | 'job run value at /0 should use only one of: step, script'
|
||||
|
||||
'when a required subkey is missing' | {
|
||||
stage: 'build',
|
||||
run: [{ name: 'step1' }]
|
||||
} | 'job run object at `/0` is missing required properties: step'
|
||||
|
||||
'when a subkey is invalid' | {
|
||||
stage: 'build',
|
||||
run: [{ name: 'step1', step: 'some step', invalid_key: 'some value' }]
|
||||
} | 'job run object property at `/0/invalid_key` is a disallowed additional property'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
|
@ -292,7 +312,7 @@ run: [{ name: 'step1', invalid_key: 'some value' }] } | 'job run \'/0\' must be
|
|||
|
||||
it 'returns error about invalid run' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors).to include 'job run \'\' must be a valid \'array\''
|
||||
expect(entry.errors).to include 'job run value at root is not an array'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -312,7 +332,7 @@ run: [{ name: 'step1', invalid_key: 'some value' }] } | 'job run \'/0\' must be
|
|||
|
||||
it 'returns error about invalid env' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors).to include 'job run \'/0/env/my_var\' must be a valid \'string\''
|
||||
expect(entry.errors).to include 'job run value at `/0/env/my_var` is not a string'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -321,7 +341,7 @@ run: [{ name: 'step1', invalid_key: 'some value' }] } | 'job run \'/0\' must be
|
|||
|
||||
it 'returns error about invalid run' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors).to include 'job run \'/0\' must be a valid \'required\''
|
||||
expect(entry.errors).to include 'job run object at `/0` is missing required properties: step'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
|
|||
it 'is not valid' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors.first)
|
||||
.to match %r{service executor opts '/docker/invalid' must be a valid 'schema'}
|
||||
.to match %r{service executor opts object property at `/docker/invalid` is a disallowed additional property}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -216,7 +216,7 @@ RSpec.describe Gitlab::Ci::Config::Entry::Service do
|
|||
it 'is not valid' do
|
||||
expect(entry).not_to be_valid
|
||||
expect(entry.errors.first)
|
||||
.to match %r{service executor opts '/docker/platform' must be a valid 'string'}
|
||||
.to match %r{service executor opts value at `/docker/platform` is not a string}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ RSpec.describe Ci::CreatePipelineService, :ci_config_feature_flag_correctness,
|
|||
it 'returns errors for invalid configuration' do
|
||||
expect(pipeline).not_to be_created_successfully
|
||||
expect(pipeline.errors.full_messages).to include(
|
||||
"jobs:job run '/0' must be a valid 'required'"
|
||||
"jobs:job run object at `/0` is missing required properties: name"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe JsonSchemaValidator do
|
||||
RSpec.describe JsonSchemaValidator, feature_category: :shared do
|
||||
describe '#validates_each' do
|
||||
let(:build_report_result) { build(:ci_build_report_result, :with_junit_success) }
|
||||
|
||||
|
|
@ -78,10 +78,140 @@ RSpec.describe JsonSchemaValidator do
|
|||
|
||||
expect(build_report_result.errors.size).to eq(1)
|
||||
expect(build_report_result.errors.full_messages).to match_array(
|
||||
["Data '/invalid' must be a valid 'schema'"]
|
||||
["Data object property at `/invalid` is a disallowed additional property"]
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when validating config with oneOf JSON schema' do
|
||||
let(:config) do
|
||||
{
|
||||
run: [
|
||||
{
|
||||
name: 'hello_steps',
|
||||
step: 'gitlab.com/gitlab-org/ci-cd/runner-tools/echo-step',
|
||||
inputs: {
|
||||
echo: 'hello steps!'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
end
|
||||
|
||||
let(:job) { Gitlab::Ci::Config::Entry::Job.new(config, name: :rspec) }
|
||||
let(:errors) { ActiveModel::Errors.new(job) }
|
||||
|
||||
let(:validator) do
|
||||
described_class.new(
|
||||
attributes: [:run],
|
||||
base_directory: 'app/validators/json_schemas',
|
||||
filename: 'run_steps',
|
||||
hash_conversion: true,
|
||||
detail_errors: true
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
job.compose!
|
||||
allow(job).to receive(:errors).and_return(errors)
|
||||
end
|
||||
|
||||
subject { validator.validate(job) }
|
||||
|
||||
context 'when the value is a valid array of hashes' do
|
||||
before do
|
||||
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
|
||||
end
|
||||
|
||||
it 'returns no errors' do
|
||||
subject
|
||||
|
||||
expect(job.errors).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a required property is missing' do
|
||||
before do
|
||||
config[:run] = [{ name: 'hello_steps' }]
|
||||
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(job.errors).not_to be_empty
|
||||
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run object at `/0` is missing required properties: step")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when oneOf validation fails' do
|
||||
before do
|
||||
config[:run] = [nil]
|
||||
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(job.errors).not_to be_empty
|
||||
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq(
|
||||
"run value at /0 should use only one of: step, script"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when there is a general validation error' do
|
||||
before do
|
||||
config[:run] = 'not an array'
|
||||
allow(job).to receive(:read_attribute_for_validation).and_return(config[:run])
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
subject
|
||||
|
||||
expect(job.errors).not_to be_empty
|
||||
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run value at root is not an array")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a non-array value violates oneOf constraint' do
|
||||
let(:schema) do
|
||||
{
|
||||
"type" => "object",
|
||||
"properties" => {
|
||||
"run" => {
|
||||
"oneOf" => [
|
||||
{ required: ["step"], title: "step" },
|
||||
{ required: ["script"], title: "script" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
let(:validator) do
|
||||
described_class.new(
|
||||
attributes: [:run],
|
||||
filename: 'test_schema',
|
||||
detail_errors: true
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
config[:run] = 'C'
|
||||
allow(job).to receive(:read_attribute_for_validation).and_return({ run: config[:run] })
|
||||
allow(JSONSchemer).to receive(:schema).and_return(JSONSchemer.schema(schema))
|
||||
allow(File).to receive(:read).with(anything).and_return(schema.to_json)
|
||||
end
|
||||
|
||||
it 'returns an error message for oneOf violation without data pointer' do
|
||||
subject
|
||||
|
||||
expect(job.errors).not_to be_empty
|
||||
expect("#{job.errors.first.attribute} #{job.errors.first.type}").to eq("run should use only one of: step, script")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ require (
|
|||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/smartystreets/goconvey v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.7
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.8
|
||||
gitlab.com/gitlab-org/labkit v1.21.0
|
||||
go.uber.org/goleak v1.3.0
|
||||
gocloud.dev v0.38.0
|
||||
|
|
|
|||
|
|
@ -486,8 +486,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
|
||||
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.7 h1:csL3bPTHIEE0147aCo1HFgNLMgmHwlwr1Ns65OlcMuE=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.7/go.mod h1:lJizRUtXRd1SBHjNbbbL9OsGN4TiugvfRBd8bIsdWI0=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.8 h1:bL9F90+rXTlQcsSuZJivn+CIwKGXXc787IJi4g3XQEU=
|
||||
gitlab.com/gitlab-org/gitaly/v16 v16.11.8/go.mod h1:lJizRUtXRd1SBHjNbbbL9OsGN4TiugvfRBd8bIsdWI0=
|
||||
gitlab.com/gitlab-org/labkit v1.21.0 h1:hLmdBDtXjD1yOmZ+uJOac3a5Tlo83QaezwhES4IYik4=
|
||||
gitlab.com/gitlab-org/labkit v1.21.0/go.mod h1:zeATDAaSBelPcPLbTTq8J3ZJEHyPTLVBM1q3nva+/W4=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
|
|
|
|||
Loading…
Reference in New Issue