Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
30785cadee
commit
8746f6e79d
|
|
@ -6,7 +6,7 @@ workflow:
|
|||
|
||||
include:
|
||||
- project: gitlab-org/quality/pipeline-common
|
||||
ref: 3.1.5
|
||||
ref: 5.1.0
|
||||
file:
|
||||
- /ci/base.gitlab-ci.yml
|
||||
- /ci/allure-report.yml
|
||||
|
|
@ -235,12 +235,12 @@ stages:
|
|||
--project "gitlab-org/quality/testcase-sessions" \
|
||||
--token "${QA_TEST_SESSION_TOKEN}" \
|
||||
--ci-project-token "${GENERATE_TEST_SESSION_READ_API_REPORTER_TOKEN}" \
|
||||
--issue-url-file REPORT_ISSUE_URL
|
||||
--issue-url-file report_issue_url.txt
|
||||
artifacts:
|
||||
when: always
|
||||
expire_in: 1d
|
||||
paths:
|
||||
- qa/REPORT_ISSUE_URL
|
||||
- qa/report_issue_url.txt
|
||||
|
||||
.notify-slack:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -8,10 +8,14 @@ const SHA_REGEX = /[\da-f]{40}/gi;
|
|||
// GitLab default domain (override in jh)
|
||||
export const DOMAIN = 'gitlab.com';
|
||||
|
||||
// About GitLab default host (overwrite in jh)
|
||||
// Following URLs will be overwritten in jh
|
||||
export const FORUM_URL = `https://forum.${DOMAIN}/`; // forum.gitlab.com
|
||||
export const DOCS_URL = `https://docs.${DOMAIN}`; // docs.gitlab.com
|
||||
|
||||
// About GitLab default host
|
||||
export const PROMO_HOST = `about.${DOMAIN}`; // about.gitlab.com
|
||||
|
||||
// About Gitlab default url (overwrite in jh)
|
||||
// About Gitlab default url
|
||||
export const PROMO_URL = `https://${PROMO_HOST}`;
|
||||
|
||||
// Reset the cursor in a Regex so that multiple uses before a recompile don't fail
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ export default {
|
|||
@filterPipelines="filterPipelines"
|
||||
/>
|
||||
<gl-collapsible-listbox
|
||||
v-model="visibilityPipelineIdType"
|
||||
data-testid="pipeline-key-collapsible-box"
|
||||
:toggle-text="selectedPipelineKeyOption.text"
|
||||
:items="$options.PipelineKeyOptions"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import AccessorUtilities from '~/lib/utils/accessor';
|
||||
import { __ } from '~/locale';
|
||||
import { getTopFrequentItems, formatContextSwitcherItems } from '../utils';
|
||||
import ItemsList from './items_list.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
ItemsList,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
|
|
@ -68,6 +74,9 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
removeItem: __('Remove'),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -87,7 +96,20 @@ export default {
|
|||
>
|
||||
{{ pristineText }}
|
||||
</div>
|
||||
<items-list :aria-label="title" :items="cachedFrequentItems" @remove-item="handleItemRemove">
|
||||
<items-list :aria-label="title" :items="cachedFrequentItems">
|
||||
<template #actions="{ item }">
|
||||
<gl-button
|
||||
v-gl-tooltip.right.viewport
|
||||
size="small"
|
||||
category="tertiary"
|
||||
icon="dash"
|
||||
:aria-label="$options.i18n.removeItem"
|
||||
:title="$options.i18n.removeItem"
|
||||
class="gl-align-self-center gl-p-1! gl-absolute gl-right-4"
|
||||
data-testid="item-remove"
|
||||
@click.stop.prevent="handleItemRemove(item)"
|
||||
/>
|
||||
</template>
|
||||
<template #view-all-items>
|
||||
<slot name="view-all-items"></slot>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
} from '@gitlab/ui';
|
||||
import GitlabVersionCheckBadge from '~/gitlab_version_check/components/gitlab_version_check_badge.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { DOMAIN, PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
|
||||
import { FORUM_URL, DOCS_URL, PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
|
||||
import { __, s__ } from '~/locale';
|
||||
import { STORAGE_KEY } from '~/whats_new/utils/notification';
|
||||
import Tracking from '~/tracking';
|
||||
|
|
@ -93,7 +93,7 @@ export default {
|
|||
},
|
||||
{
|
||||
text: this.$options.i18n.docs,
|
||||
href: `https://docs.${DOMAIN}`,
|
||||
href: DOCS_URL,
|
||||
extraAttrs: {
|
||||
...this.trackingAttrs('gitlab_documentation'),
|
||||
},
|
||||
|
|
@ -107,7 +107,7 @@ export default {
|
|||
},
|
||||
{
|
||||
text: this.$options.i18n.forum,
|
||||
href: `https://forum.${DOMAIN}/`,
|
||||
href: FORUM_URL,
|
||||
extraAttrs: {
|
||||
...this.trackingAttrs('community_forum'),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
<script>
|
||||
import { GlButton, GlTooltipDirective } from '@gitlab/ui';
|
||||
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
|
||||
import NavItem from './nav_item.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
GlButton,
|
||||
ProjectAvatar,
|
||||
NavItem,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
type: Array,
|
||||
|
|
@ -40,17 +35,7 @@ export default {
|
|||
/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<gl-button
|
||||
v-gl-tooltip.right.viewport
|
||||
size="small"
|
||||
category="tertiary"
|
||||
icon="dash"
|
||||
:aria-label="__('Remove')"
|
||||
:title="__('Remove')"
|
||||
class="gl-align-self-center gl-p-1! gl-absolute gl-right-4"
|
||||
data-testid="item-remove"
|
||||
@click.stop.prevent="$emit('remove-item', item)"
|
||||
/>
|
||||
<slot name="actions" :item="item"></slot>
|
||||
</template>
|
||||
</nav-item>
|
||||
<slot name="view-all-items"></slot>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import { s__ } from '~/locale';
|
||||
import { PANELS_WITH_PINS } from '../constants';
|
||||
import NavItem from './nav_item.vue';
|
||||
import PinnedSection from './pinned_section.vue';
|
||||
|
|
@ -42,6 +43,10 @@ export default {
|
|||
},
|
||||
},
|
||||
|
||||
i18n: {
|
||||
mainNavigation: s__('Navigation|Main navigation'),
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
// This is used as a provide and injected into the nav items.
|
||||
|
|
@ -137,7 +142,7 @@ export default {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="gl-p-2 gl-relative">
|
||||
<nav :aria-label="$options.i18n.mainNavigation" class="gl-p-2 gl-relative">
|
||||
<ul v-if="hasStaticItems" class="gl-p-0 gl-m-0">
|
||||
<nav-item v-for="item in staticItems" :key="item.id" :item="item" is-static />
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Mutations
|
||||
module Environments
|
||||
class Update < ::Mutations::BaseMutation
|
||||
graphql_name 'EnvironmentUpdate'
|
||||
description 'Update an environment.'
|
||||
|
||||
authorize :update_environment
|
||||
|
||||
argument :id,
|
||||
::Types::GlobalIDType[::Environment],
|
||||
required: true,
|
||||
description: 'Global ID of the environment to update.'
|
||||
|
||||
argument :external_url,
|
||||
GraphQL::Types::String,
|
||||
required: false,
|
||||
description: 'External URL of the environment.'
|
||||
|
||||
argument :tier,
|
||||
Types::DeploymentTierEnum,
|
||||
required: false,
|
||||
description: 'Tier of the environment.'
|
||||
|
||||
field :environment,
|
||||
Types::EnvironmentType,
|
||||
null: true,
|
||||
description: 'Environment after attempt to update.'
|
||||
|
||||
def resolve(id:, **kwargs)
|
||||
environment = authorized_find!(id: id)
|
||||
|
||||
response = ::Environments::UpdateService.new(environment.project, current_user, kwargs).execute(environment)
|
||||
|
||||
if response.success?
|
||||
{ environment: response.payload[:environment], errors: [] }
|
||||
else
|
||||
{ environment: response.payload[:environment], errors: response.errors }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -53,6 +53,7 @@ module Types
|
|||
mount_mutation Mutations::DependencyProxy::GroupSettings::Update
|
||||
mount_mutation Mutations::Environments::CanaryIngress::Update
|
||||
mount_mutation Mutations::Environments::Stop
|
||||
mount_mutation Mutations::Environments::Update
|
||||
mount_mutation Mutations::IncidentManagement::TimelineEvent::Create, alpha: { milestone: '15.6' }
|
||||
mount_mutation Mutations::IncidentManagement::TimelineEvent::PromoteFromNote
|
||||
mount_mutation Mutations::IncidentManagement::TimelineEvent::Update
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ module SafeFormatHelper
|
|||
def safe_format(format, **args)
|
||||
raise ArgumentError, 'Argument `format` must not be marked as html_safe!' if format.html_safe?
|
||||
|
||||
format(
|
||||
# Use `Kernel.format` to avoid conflicts with ViewComponent's `format`.
|
||||
Kernel.format(
|
||||
html_escape(format),
|
||||
args.transform_values { |value| html_escape(value) }
|
||||
).html_safe
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Environments
|
||||
class UpdateService < BaseService
|
||||
def execute(environment)
|
||||
unless can?(current_user, :update_environment, environment)
|
||||
return ServiceResponse.error(
|
||||
message: _('Unauthorized to update the environment'),
|
||||
payload: { environment: environment }
|
||||
)
|
||||
end
|
||||
|
||||
if environment.update(**params)
|
||||
ServiceResponse.success(payload: { environment: environment })
|
||||
else
|
||||
ServiceResponse.error(
|
||||
message: environment.errors.full_messages,
|
||||
payload: { environment: environment }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3060,6 +3060,29 @@ Input type: `EnvironmentStopInput`
|
|||
| <a id="mutationenvironmentstopenvironment"></a>`environment` | [`Environment`](#environment) | Environment after attempt to stop. |
|
||||
| <a id="mutationenvironmentstoperrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.environmentUpdate`
|
||||
|
||||
Update an environment.
|
||||
|
||||
Input type: `EnvironmentUpdateInput`
|
||||
|
||||
#### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationenvironmentupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationenvironmentupdateexternalurl"></a>`externalUrl` | [`String`](#string) | External URL of the environment. |
|
||||
| <a id="mutationenvironmentupdateid"></a>`id` | [`EnvironmentID!`](#environmentid) | Global ID of the environment to update. |
|
||||
| <a id="mutationenvironmentupdatetier"></a>`tier` | [`DeploymentTier`](#deploymenttier) | Tier of the environment. |
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="mutationenvironmentupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
|
||||
| <a id="mutationenvironmentupdateenvironment"></a>`environment` | [`Environment`](#environment) | Environment after attempt to update. |
|
||||
| <a id="mutationenvironmentupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
|
||||
|
||||
### `Mutation.environmentsCanaryIngressUpdate`
|
||||
|
||||
**Deprecated** This endpoint is planned to be removed along with certificate-based clusters. [See this epic](https://gitlab.com/groups/gitlab-org/configure/-/epics/8) for more information.
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ GitLab Runners use a set of globally scoped endpoints to:
|
|||
|
||||
- registration of a new runner via registration token `https://gitlab.com/api/v4/runners`
|
||||
([subject for removal](../runner_tokens/index.md)) (`registration token`)
|
||||
- creation of a new runner in the context of a user `https://gitlab.com/api/v4/user/runners` (`runner token`)
|
||||
- requests jobs via an authenticated `https://gitlab.com/api/v4/jobs/request` endpoint (`runner token`)
|
||||
- upload job status via `https://gitlab.com/api/v4/jobs/:job_id` (`build token`)
|
||||
- upload trace via `https://gitlab.com/api/v4/jobs/:job_id/trace` (`build token`)
|
||||
|
|
|
|||
|
|
@ -97,8 +97,8 @@ This problem was discovered in <https://gitlab.com/gitlab-org/gitlab-qa/-/issues
|
|||
work-around was suggested in <https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/4717>.
|
||||
A feature proposal to segregate access control regarding running pipelines from ability to push/merge was also created at <https://gitlab.com/gitlab-org/gitlab/-/issues/24585>.
|
||||
|
||||
For more technical details on CI/CD setup and documentation on adding new test jobs to `package-and-test` pipeline, see
|
||||
[`package_and_test` setup documentation](package_and_test_pipeline.md).
|
||||
For more technical details on CI/CD setup and documentation on adding new test jobs to `e2e:package-and-test` pipeline, see
|
||||
[`e2e:package_and_test` setup documentation](package_and_test_pipeline.md).
|
||||
|
||||
#### With merged results pipelines
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ Use these environment variables to configure metrics export:
|
|||
| -------- | -------- | ----------- |
|
||||
| `QA_INFLUXDB_URL` | `true` | Should be set to `https://influxdb.quality.gitlab.net`. No default value. |
|
||||
| `QA_INFLUXDB_TOKEN` | `true` | InfluxDB write token that can be found under `Influxdb auth tokens` document in `Gitlab-QA` `1Password` vault. No default value. |
|
||||
| `QA_RUN_TYPE` | `false` | Arbitrary name for test execution, like `package-and-test`. Automatically inferred from the project name for live environment test executions. No default value. |
|
||||
| `QA_RUN_TYPE` | `false` | Arbitrary name for test execution, like `e2e:package-and-test`. Automatically inferred from the project name for live environment test executions. No default value. |
|
||||
| `QA_EXPORT_TEST_METRICS` | `false` | Flag to enable or disable metrics export to InfluxDB. Defaults to `false`. |
|
||||
| `QA_SAVE_TEST_METRICS` | `false` | Flag to enable or disable saving metrics as JSON file. Defaults to `false`. |
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ You can create [Personal access tokens](../user/profile/personal_access_tokens.m
|
|||
You can limit the scope and expiration date of your personal access tokens. By default,
|
||||
they inherit permissions from the user who created them.
|
||||
|
||||
You can use the [personal access tokens API](../api/personal_access_tokens.md) to
|
||||
programmatically take action, such as
|
||||
[rotating a personal access token](../api/personal_access_tokens.md#rotate-a-personal-access-token).
|
||||
|
||||
## OAuth2 tokens
|
||||
|
||||
GitLab can serve as an [OAuth2 provider](../api/oauth2.md) to allow other services to access the GitLab API on a user's behalf.
|
||||
|
|
@ -47,6 +51,10 @@ You can limit the scope and expiration date of project access tokens. When you
|
|||
create a project access token, GitLab creates a [bot user for projects](../user/project/settings/project_access_tokens.md#bot-users-for-projects).
|
||||
Bot users for projects are service accounts and do not count as licensed seats.
|
||||
|
||||
You can use the [project access tokens API](../api/project_access_tokens.md) to
|
||||
programmatically take action, such as
|
||||
[rotating a project access token](../api/project_access_tokens.md#rotate-a-project-access-token).
|
||||
|
||||
## Group access tokens
|
||||
|
||||
[Group access tokens](../user/group/settings/group_access_tokens.md#group-access-tokens)
|
||||
|
|
@ -60,6 +68,10 @@ You can limit the scope and expiration date of group access tokens. When you
|
|||
create a group access token, GitLab creates a [bot user for groups](../user/group/settings/group_access_tokens.md#bot-users-for-groups).
|
||||
Bot users for groups are service accounts and do not count as licensed seats.
|
||||
|
||||
You can use the [group access tokens API](../api/group_access_tokens.md) to
|
||||
programmatically take action, such as
|
||||
[rotating a project access token](../api/group_access_tokens.md#rotate-a-group-access-token).
|
||||
|
||||
## Deploy tokens
|
||||
|
||||
[Deploy tokens](../user/project/deploy_tokens/index.md) allow you to download (`git clone`) or push and pull packages and container registry images of a project without having a user and a password. Deploy tokens cannot be used with the GitLab API.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ GitLab is creating AI-assisted features across our DevSecOps platform. These fea
|
|||
|
||||
## Enable AI/ML features
|
||||
|
||||
> Introduced in GitLab 16.0 and is [actively being rolled out](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222).
|
||||
> Introduced in GitLab 16.0 and [actively being rolled out](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118222).
|
||||
|
||||
Prerequisites:
|
||||
|
||||
|
|
@ -133,7 +133,11 @@ To give feedback, select the **Give Feedback** link.
|
|||
|
||||
This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
|
||||
|
||||
You can generate a merge request summary by using the `/summarize_diff` quick action in a merge request comment. This action posts a comment from a GitLab bot. The comment provides a summary of the changes and the related SHA for when that summary was generated.
|
||||
You can generate a merge request summary in a merge request comment.
|
||||
|
||||
- In a comment, type `/summarize_diff`.
|
||||
|
||||
This action posts a comment from a GitLab bot. The comment provides a summary of the changes and the related SHA for when that summary was generated.
|
||||
|
||||
Provide feedback on this experimental feature in [issue 408726](https://gitlab.com/gitlab-org/gitlab/-/issues/408726).
|
||||
|
||||
|
|
@ -146,12 +150,15 @@ and the target branch is sent to the large language model referenced above.
|
|||
|
||||
This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
|
||||
|
||||
When you've completed your review of a merge request and are ready to [submit your review](project/merge_requests/reviews/index.md#submit-a-review) you can choose to have summary generated for you. To generate the summary:
|
||||
When you've completed your review of a merge request and are ready to [submit your review](project/merge_requests/reviews/index.md#submit-a-review), you can have a summary generated for you.
|
||||
|
||||
1. Select the AI Actions dropdown list.
|
||||
To generate the summary:
|
||||
|
||||
1. When you are ready to submit your review, select **Finish review**.
|
||||
1. Select **AI Actions** (**{tanuki}**).
|
||||
1. Select **Summarize my code review**.
|
||||
|
||||
The summary is generated and entered in to the comment box where you can edit and refine prior to submitting with your review.
|
||||
The summary is displayed in the comment box. You can edit and refine the summary prior to submitting your review.
|
||||
|
||||
Provide feedback on this experimental feature in [issue 408991](https://gitlab.com/gitlab-org/gitlab/-/issues/408991).
|
||||
|
||||
|
|
@ -166,12 +173,15 @@ Provide feedback on this experimental feature in [issue 408991](https://gitlab.c
|
|||
|
||||
This feature is an [Experiment](../policy/alpha-beta-support.md) on GitLab.com that is powered by OpenAI's GPT-3. It requires the [group-level third-party AI features setting](group/manage.md#group-third-party-ai-features-setting) to be enabled.
|
||||
|
||||
When in a merge request you can choose to have GitLab suggest tests for the file you are reviewing. This can help to determine if appropriate test coverage has been provided or help with writing tests to provide more coverage for your project. To generate a test suggestion:
|
||||
In a merge request, you can get a list of suggested tests for the file you are reviewing. This functionality can help determine if appropriate test coverage has been provided, or if you need more coverage for your project.
|
||||
|
||||
1. Select the menu icon on the header of a file.
|
||||
To generate a test suggestion:
|
||||
|
||||
1. In a merge request, select the **Changes** tab.
|
||||
1. On the header for the file, in the upper-right corner, select **Options** (**{ellipsis_v}**).
|
||||
1. Select **Generate test with AI**.
|
||||
|
||||
A sidebar opens where the test suggestion is generated. From there you can choose to copy that suggestion in to your editor as the start of your tests.
|
||||
The test suggestion is generated in a sidebar. You can copy the suggestion to your editor and use it as the start of your tests.
|
||||
|
||||
Feedback on this experimental feature can be provided in [issue 408995](https://gitlab.com/gitlab-org/gitlab/-/issues/408995).
|
||||
|
||||
|
|
|
|||
|
|
@ -122,8 +122,10 @@ To protect a branch:
|
|||
1. On the left sidebar, select **Settings > Repository**.
|
||||
1. Expand **Protected branches**.
|
||||
1. From the **Branch** dropdown list, select the branch you want to protect.
|
||||
1. From the **Allowed to merge** list, select a role, or group that can merge into this branch. In GitLab Premium, you can also add users.
|
||||
1. From the **Allowed to push and merge** list, select a role, group, or user that can push to this branch. In GitLab Premium, you can also add users.
|
||||
1. From the **Allowed to merge** list, select a role that can merge into this branch.
|
||||
In GitLab Premium and Ultimate, you can also add groups or individual users.
|
||||
1. From the **Allowed to push and merge** list, select a role that can push to this branch.
|
||||
In GitLab Premium and Ultimate, you can also add groups or individual users.
|
||||
1. Select **Protect**.
|
||||
|
||||
The protected branch displays in the list of protected branches.
|
||||
|
|
@ -152,8 +154,10 @@ To protect multiple branches at the same time:
|
|||
| `production/*` | `production/app-server`, `production/load-balancer` |
|
||||
| `*gitlab*` | `gitlab`, `gitlab/staging`, `master/gitlab/production` |
|
||||
|
||||
1. From the **Allowed to merge** list, select a role, or group that can merge into this branch. In GitLab Premium, you can also add users.
|
||||
1. From the **Allowed to push and merge** list, select a role, group, or user that can push to this branch. In GitLab Premium, you can also add users.
|
||||
1. From the **Allowed to merge** list, select a role that can merge into
|
||||
this branch.
|
||||
1. From the **Allowed to push and merge** list, select a role that can
|
||||
push to this branch. In GitLab Premium or Ultimate, you can also add groups or individual users.
|
||||
1. Select **Protect**.
|
||||
|
||||
The protected branch displays in the list of protected branches.
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ Prerequisites:
|
|||
1. Select **Tag**.
|
||||
1. Enter the string to use for tag matching. Wildcards (`*`) are supported.
|
||||
1. Select **Create wildcard**.
|
||||
1. In **Allowed to create** , select either the users or roles that may create protected tags.
|
||||
1. In **Allowed to create** , select roles that may create protected tags.
|
||||
In GitLab Premium and Ultimate, you can also select groups or individual users.
|
||||
1. Select **Protect**.
|
||||
|
||||
The protected tag (or wildcard) displays in the **Protected tags** list.
|
||||
|
|
|
|||
|
|
@ -123,7 +123,6 @@ module Gitlab
|
|||
uuid: uuid,
|
||||
report_type: report.type,
|
||||
name: finding_name(data, identifiers, location),
|
||||
compare_key: data['cve'] || '',
|
||||
location: location,
|
||||
evidence: evidence,
|
||||
severity: parse_severity_level(data['severity']),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ module Gitlab
|
|||
class Finding
|
||||
include ::VulnerabilityFindingHelpers
|
||||
|
||||
attr_reader :compare_key
|
||||
attr_reader :confidence
|
||||
attr_reader :identifiers
|
||||
attr_reader :flags
|
||||
|
|
@ -33,10 +32,10 @@ module Gitlab
|
|||
|
||||
delegate :file_path, :start_line, :end_line, to: :location
|
||||
|
||||
alias_method :compare_key, :uuid
|
||||
alias_method :cve, :compare_key
|
||||
|
||||
def initialize(compare_key:, identifiers:, flags: [], links: [], remediations: [], location:, evidence:, metadata_version:, name:, original_data:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false, found_by_pipeline: nil) # rubocop:disable Metrics/ParameterLists
|
||||
@compare_key = compare_key
|
||||
def initialize(identifiers:, flags: [], links: [], remediations: [], location:, evidence:, metadata_version:, name:, original_data:, report_type:, scanner:, scan:, uuid:, confidence: nil, severity: nil, details: {}, signatures: [], project_id: nil, vulnerability_finding_signatures_enabled: false, found_by_pipeline: nil) # rubocop:disable Metrics/ParameterLists
|
||||
@confidence = confidence
|
||||
@identifiers = identifiers
|
||||
@flags = flags
|
||||
|
|
@ -203,7 +202,7 @@ module Gitlab
|
|||
private
|
||||
|
||||
def generate_project_fingerprint
|
||||
Digest::SHA1.hexdigest(compare_key)
|
||||
Digest::SHA1.hexdigest(compare_key.to_s)
|
||||
end
|
||||
|
||||
def location_fingerprints
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ module Gitlab
|
|||
belongs_to :issue
|
||||
|
||||
validates :object_name, :valitador_name, :table_name, presence: true
|
||||
|
||||
scope :with_open_issues, -> { joins(:issue).where('issue.state_id': Issue.available_states[:opened]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def inconsistency_record
|
||||
schema_inconsistency_model.find_by(
|
||||
schema_inconsistency_model.with_open_issues.find_by(
|
||||
object_name: inconsistency.object_name,
|
||||
table_name: inconsistency.table_name,
|
||||
valitador_name: inconsistency.type
|
||||
|
|
|
|||
|
|
@ -5645,6 +5645,9 @@ msgstr ""
|
|||
msgid "ApprovalRule|Name"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalRule|Need triage"
|
||||
msgstr ""
|
||||
|
||||
msgid "ApprovalRule|Needs triage"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -29460,6 +29463,9 @@ msgstr ""
|
|||
msgid "Navigation|Leave admin mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Navigation|Main navigation"
|
||||
msgstr ""
|
||||
|
||||
msgid "Navigation|Manage"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40367,6 +40373,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration| and "
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration| and all the following apply:"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration| or "
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40397,7 +40406,7 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|%{scanners}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|%{scanners} %{vulnerabilitiesAllowed} %{severities} in an open merge request targeting %{branches}."
|
||||
msgid "SecurityOrchestration|%{state} and %{statuses}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|, and %{count} more"
|
||||
|
|
@ -40442,9 +40451,6 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|An error occurred while fetching the scan result policies."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Any security scanner finds"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Are you sure you want to delete this policy? This action cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40547,9 +40553,6 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|License Scan"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|License scanner finds any license %{matching} %{licenses}%{detection} in an open merge request targeting %{branches}."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|New policy"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40703,6 +40706,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|Select users"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Severity is %{severity}."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Something went wrong, unable to fetch policies"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40778,6 +40784,15 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|View policy project"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|Vulnerabilities are %{vulnerabilityStates}."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|When %{scanners} %{vulnerabilitiesAllowed} %{vulnerability} in an open merge request targeting %{branches}%{criteriaApply}"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|When license scanner finds any license %{matching} %{licenses}%{detection} in an open merge request targeting %{branches}."
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|YAML"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40793,6 +40808,9 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|any"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|any security scanner finds"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|branch"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -40826,12 +40844,6 @@ msgstr ""
|
|||
msgid "SecurityOrchestration|the %{namespaces} namespace"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|vulnerabilities"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityOrchestration|vulnerability"
|
||||
msgstr ""
|
||||
|
||||
msgid "SecurityPolicies|Invalid or empty policy"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -47834,6 +47846,9 @@ msgstr ""
|
|||
msgid "Unauthenticated web rate limit period in seconds"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unauthorized to update the environment"
|
||||
msgstr ""
|
||||
|
||||
msgid "Unavailable"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
FactoryBot.define do
|
||||
factory :ci_reports_security_finding, class: '::Gitlab::Ci::Reports::Security::Finding' do
|
||||
compare_key { "#{identifiers.first&.external_type}:#{identifiers.first&.external_id}:#{location.fingerprint}" }
|
||||
confidence { :medium }
|
||||
identifiers { Array.new(1) { association(:ci_reports_security_identifier) } }
|
||||
location factory: :ci_reports_security_locations_sast
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@
|
|||
"message": "Remediation for this vulnerability should remediate CVE-2140 as well",
|
||||
"description": "",
|
||||
"cve": "CVE-2139",
|
||||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d4",
|
||||
"severity": "High",
|
||||
"solution": "Upgrade to latest version.",
|
||||
"scanner": {
|
||||
|
|
@ -132,6 +133,7 @@
|
|||
"message": "Remediation for this vulnerability should remediate CVE-2139 as well",
|
||||
"description": "",
|
||||
"cve": "CVE-2140",
|
||||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d5",
|
||||
"severity": "High",
|
||||
"solution": "Upgrade to latest version.",
|
||||
"scanner": {
|
||||
|
|
@ -439,10 +441,10 @@
|
|||
{
|
||||
"fixes": [
|
||||
{
|
||||
"cve": "CVE-2139"
|
||||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d4"
|
||||
},
|
||||
{
|
||||
"cve": "CVE-2140"
|
||||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d5"
|
||||
}
|
||||
],
|
||||
"summary": "this remediates CVE-2139 and CVE-2140",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { s__ } from '~/locale';
|
||||
import FrequentItemsList from '~/super_sidebar/components//frequent_items_list.vue';
|
||||
import ItemsList from '~/super_sidebar/components/items_list.vue';
|
||||
|
|
@ -18,18 +18,20 @@ describe('FrequentItemsList component', () => {
|
|||
const findListTitle = () => wrapper.findByTestId('list-title');
|
||||
const findItemsList = () => wrapper.findComponent(ItemsList);
|
||||
const findEmptyText = () => wrapper.findByTestId('empty-text');
|
||||
const findRemoveItemButton = () => wrapper.findByTestId('item-remove');
|
||||
|
||||
const createWrapper = ({ props = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(FrequentItemsList, {
|
||||
const createWrapperFactory = (mountFn = shallowMountExtended) => () => {
|
||||
wrapper = mountFn(FrequentItemsList, {
|
||||
propsData: {
|
||||
title,
|
||||
pristineText,
|
||||
storageKey,
|
||||
maxItems,
|
||||
...props,
|
||||
},
|
||||
});
|
||||
};
|
||||
const createWrapper = createWrapperFactory();
|
||||
const createFullWrapper = createWrapperFactory(mountExtended);
|
||||
|
||||
describe('default', () => {
|
||||
beforeEach(() => {
|
||||
|
|
@ -64,16 +66,20 @@ describe('FrequentItemsList component', () => {
|
|||
it('does not render the empty text slot', () => {
|
||||
expect(findEmptyText().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('items editing', () => {
|
||||
it('remove-item event emission from items-list causes list item to be removed', async () => {
|
||||
const localStorageProjects = findItemsList().props('items');
|
||||
describe('items editing', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem(storageKey, cachedFrequentProjects);
|
||||
createFullWrapper();
|
||||
});
|
||||
|
||||
await findItemsList().vm.$emit('remove-item', localStorageProjects[0]);
|
||||
it('remove-item event emission from items-list causes list item to be removed', async () => {
|
||||
const localStorageProjects = findItemsList().props('items');
|
||||
await findRemoveItemButton().trigger('click');
|
||||
|
||||
expect(findItemsList().props('items')).toHaveLength(maxItems - 1);
|
||||
expect(findItemsList().props('items')).not.toContain(localStorageProjects[0]);
|
||||
});
|
||||
expect(findItemsList().props('items')).toHaveLength(maxItems - 1);
|
||||
expect(findItemsList().props('items')).not.toContain(localStorageProjects[0]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import toggleWhatsNewDrawer from '~/whats_new';
|
|||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import HelpCenter from '~/super_sidebar/components/help_center.vue';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
import { DOMAIN, PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
|
||||
import { DOCS_URL, FORUM_URL, PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
|
||||
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
|
||||
import { STORAGE_KEY } from '~/whats_new/utils/notification';
|
||||
import { helpCenterState } from '~/super_sidebar/constants';
|
||||
|
|
@ -52,7 +52,7 @@ describe('HelpCenter component', () => {
|
|||
},
|
||||
{
|
||||
text: HelpCenter.i18n.docs,
|
||||
href: `https://docs.${DOMAIN}`,
|
||||
href: DOCS_URL,
|
||||
extraAttrs: trackingAttrs('gitlab_documentation'),
|
||||
},
|
||||
{
|
||||
|
|
@ -62,7 +62,7 @@ describe('HelpCenter component', () => {
|
|||
},
|
||||
{
|
||||
text: HelpCenter.i18n.forum,
|
||||
href: `https://forum.${DOMAIN}/`,
|
||||
href: FORUM_URL,
|
||||
extraAttrs: trackingAttrs('community_forum'),
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { GlIcon } from '@gitlab/ui';
|
||||
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import ItemsList from '~/super_sidebar/components/items_list.vue';
|
||||
import NavItem from '~/super_sidebar/components/nav_item.vue';
|
||||
import { cachedFrequentProjects } from '../mock_data';
|
||||
|
|
@ -12,8 +11,8 @@ describe('ItemsList component', () => {
|
|||
|
||||
const findNavItems = () => wrapper.findAllComponents(NavItem);
|
||||
|
||||
const createWrapper = ({ props = {}, slots = {}, mountFn = shallowMountExtended } = {}) => {
|
||||
wrapper = mountFn(ItemsList, {
|
||||
const createWrapper = ({ props = {}, slots = {} } = {}) => {
|
||||
wrapper = shallowMountExtended(ItemsList, {
|
||||
propsData: {
|
||||
...props,
|
||||
},
|
||||
|
|
@ -61,41 +60,4 @@ describe('ItemsList component', () => {
|
|||
|
||||
expect(wrapper.findByTestId(testId).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('item removal', () => {
|
||||
const findRemoveButton = () => wrapper.findByTestId('item-remove');
|
||||
const mockProject = {
|
||||
...firstMockedProject,
|
||||
title: firstMockedProject.name,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
createWrapper({
|
||||
props: {
|
||||
items: [mockProject],
|
||||
},
|
||||
mountFn: mountExtended,
|
||||
});
|
||||
});
|
||||
|
||||
it('renders the remove button', () => {
|
||||
const itemRemoveButton = findRemoveButton();
|
||||
|
||||
expect(itemRemoveButton.exists()).toBe(true);
|
||||
expect(itemRemoveButton.attributes('title')).toBe('Remove');
|
||||
expect(itemRemoveButton.findComponent(GlIcon).props('name')).toBe('dash');
|
||||
});
|
||||
|
||||
it('emits `remove-item` event with item param when remove button is clicked', () => {
|
||||
const itemRemoveButton = findRemoveButton();
|
||||
|
||||
itemRemoveButton.vm.$emit(
|
||||
'click',
|
||||
{ stopPropagation: jest.fn(), preventDefault: jest.fn() },
|
||||
mockProject,
|
||||
);
|
||||
|
||||
expect(wrapper.emitted('remove-item')).toEqual([[mockProject]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { mountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { s__ } from '~/locale';
|
||||
import SidebarMenu from '~/super_sidebar/components/sidebar_menu.vue';
|
||||
import PinnedSection from '~/super_sidebar/components/pinned_section.vue';
|
||||
import { PANELS_WITH_PINS } from '~/super_sidebar/constants';
|
||||
|
|
@ -181,4 +182,11 @@ describe('SidebarMenu component', () => {
|
|||
expect(findMainMenuSeparator().exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('adds aria-label attribute to nav element', () => {
|
||||
createWrapper({ ...sidebarData });
|
||||
expect(wrapper.find('nav').attributes('aria-label')).toBe(s__('Navigation|Main navigation'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -310,12 +310,11 @@ describe('vue_shared/components/chronic_duration_input', () => {
|
|||
});
|
||||
|
||||
it('passes updated prop via v-model', async () => {
|
||||
// setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
wrapper.setData({ value: MOCK_VALUE });
|
||||
textElement.value = '2hr20min';
|
||||
textElement.dispatchEvent(new Event('input'));
|
||||
await nextTick();
|
||||
|
||||
expect(textElement.value).toBe('2 hrs 20 mins');
|
||||
expect(textElement.value).toBe('2hr20min');
|
||||
expect(hiddenElement.value).toBe(MOCK_VALUE.toString());
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Mutations::Environments::Update, feature_category: :environment_management do
|
||||
let_it_be(:project) { create(:project) }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
let_it_be(:maintainer) { create(:user) }
|
||||
let_it_be(:reporter) { create(:user) }
|
||||
|
||||
let(:user) { maintainer }
|
||||
|
||||
subject(:mutation) { described_class.new(object: nil, context: { current_user: user }, field: nil) }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(maintainer)
|
||||
project.add_reporter(reporter)
|
||||
end
|
||||
|
||||
describe '#resolve' do
|
||||
subject { mutation.resolve(id: environment_id, external_url: external_url) }
|
||||
|
||||
let(:environment_id) { environment.to_global_id }
|
||||
let(:external_url) { 'https://gitlab.com/' }
|
||||
|
||||
context 'when service execution succeeded' do
|
||||
it 'returns no errors' do
|
||||
expect(subject[:errors]).to be_empty
|
||||
end
|
||||
|
||||
it 'updates the environment' do
|
||||
expect(subject[:environment][:external_url]).to eq(external_url)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when service cannot update the attribute' do
|
||||
let(:external_url) { 'http://${URL}' }
|
||||
|
||||
it 'returns an error' do
|
||||
expect(subject)
|
||||
.to eq({
|
||||
environment: environment,
|
||||
errors: ['External url URI is invalid']
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is reporter who does not have permission to access the environment' do
|
||||
let(:user) { reporter }
|
||||
|
||||
it 'raises an error' do
|
||||
expect { subject }.to raise_error(Gitlab::Graphql::Authorize::AuthorizeResource::RESOURCE_ACCESS_ERROR)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -37,5 +37,22 @@ RSpec.describe SafeFormatHelper, feature_category: :shared do
|
|||
.to raise_error ArgumentError, message
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a view component' do
|
||||
let(:view_component) do
|
||||
Class.new(ViewComponent::Base) do
|
||||
include SafeFormatHelper
|
||||
|
||||
def call
|
||||
safe_format('<b>%{value}</b>', value: '<br>')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'safetly formats' do
|
||||
expect(view_component.new.call)
|
||||
.to eq('<b><br></b>')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -184,8 +184,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
let(:artifact) { build(:ci_job_artifact, :common_security_report_with_blank_names) }
|
||||
|
||||
context 'when message is provided' do
|
||||
let(:finding) { report.findings.first }
|
||||
|
||||
it 'sets message from the report as a finding name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
|
||||
expected_name = Gitlab::Json.parse(finding.raw_metadata)['message']
|
||||
|
||||
expect(finding.name).to eq(expected_name)
|
||||
|
|
@ -194,8 +195,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
|
||||
context 'when message is not provided' do
|
||||
context 'and name is provided' do
|
||||
let(:finding) { report.findings.second }
|
||||
|
||||
it 'sets name from the report as a name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
|
||||
expected_name = Gitlab::Json.parse(finding.raw_metadata)['name']
|
||||
|
||||
expect(finding.name).to eq(expected_name)
|
||||
|
|
@ -203,11 +205,12 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
end
|
||||
|
||||
context 'and name is not provided' do
|
||||
let(:finding) { report.findings[2] }
|
||||
|
||||
context 'when location does not exist' do
|
||||
let(:location) { nil }
|
||||
|
||||
it 'returns only identifier name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
|
||||
expect(finding.name).to eq("CVE-2017-11429")
|
||||
end
|
||||
end
|
||||
|
|
@ -215,21 +218,22 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
context 'when location exists' do
|
||||
context 'when CVE identifier exists' do
|
||||
it 'combines identifier with location to create name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-2017-11429' }
|
||||
expect(finding.name).to eq("CVE-2017-11429 in yarn.lock")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when CWE identifier exists' do
|
||||
let(:finding) { report.findings[3] }
|
||||
|
||||
it 'combines identifier with location to create name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CWE-2017-11429' }
|
||||
expect(finding.name).to eq("CWE-2017-11429 in yarn.lock")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when neither CVE nor CWE identifier exist' do
|
||||
let(:finding) { report.findings[4] }
|
||||
|
||||
it 'combines identifier with location to create name' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'OTHER-2017-11429' }
|
||||
expect(finding.name).to eq("other-2017-11429 in yarn.lock")
|
||||
end
|
||||
end
|
||||
|
|
@ -240,8 +244,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
|
||||
describe 'parsing finding.details' do
|
||||
context 'when details are provided' do
|
||||
let(:finding) { report.findings[4] }
|
||||
|
||||
it 'sets details from the report' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-1020' }
|
||||
expected_details = Gitlab::Json.parse(finding.raw_metadata)['details']
|
||||
|
||||
expect(finding.details).to eq(expected_details)
|
||||
|
|
@ -249,8 +254,9 @@ RSpec.describe Gitlab::Ci::Parsers::Security::Common, feature_category: :vulnera
|
|||
end
|
||||
|
||||
context 'when details are not provided' do
|
||||
let(:finding) { report.findings[5] }
|
||||
|
||||
it 'sets empty hash' do
|
||||
finding = report.findings.find { |x| x.compare_key == 'CVE-1030' }
|
||||
expect(finding.details).to eq({})
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Reports::Security::Report do
|
||||
RSpec.describe Gitlab::Ci::Reports::Security::Report, feature_category: :vulnerability_management do
|
||||
let_it_be(:pipeline) { create(:ci_pipeline) }
|
||||
|
||||
let(:created_at) { 2.weeks.ago }
|
||||
|
|
@ -89,7 +89,7 @@ RSpec.describe Gitlab::Ci::Reports::Security::Report do
|
|||
let(:other_report) do
|
||||
create(
|
||||
:ci_reports_security_report,
|
||||
findings: [create(:ci_reports_security_finding, compare_key: 'other_finding')],
|
||||
findings: [create(:ci_reports_security_finding)],
|
||||
scanners: [create(:ci_reports_security_scanner, external_id: 'other_scanner', name: 'Other Scanner')],
|
||||
identifiers: [create(:ci_reports_security_identifier, external_id: 'other_id', name: 'other_scanner')]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -14,4 +14,27 @@ RSpec.describe Gitlab::Database::SchemaValidation::SchemaInconsistency, type: :m
|
|||
it { is_expected.to validate_presence_of(:valitador_name) }
|
||||
it { is_expected.to validate_presence_of(:table_name) }
|
||||
end
|
||||
|
||||
describe 'scopes' do
|
||||
describe '.with_open_issues' do
|
||||
subject(:inconsistencies) { described_class.with_open_issues }
|
||||
|
||||
let(:closed_issue) { create(:issue, :closed) }
|
||||
let(:open_issue) { create(:issue, :opened) }
|
||||
|
||||
let!(:schema_inconsistency_with_issue_closed) do
|
||||
create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
|
||||
valitador_name: 'different_definition_indexes', issue: closed_issue)
|
||||
end
|
||||
|
||||
let!(:schema_inconsistency_with_issue_opened) do
|
||||
create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
|
||||
valitador_name: 'different_definition_indexes', issue: open_issue)
|
||||
end
|
||||
|
||||
it 'returns only schema inconsistencies with GitLab issues open' do
|
||||
expect(inconsistencies).to eq([schema_inconsistency_with_issue_opened])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -63,19 +63,31 @@ RSpec.describe Gitlab::Database::SchemaValidation::TrackInconsistency, feature_c
|
|||
end
|
||||
|
||||
context 'when the schema inconsistency already exists' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
let!(:schema_inconsistency) do
|
||||
create(:schema_inconsistency, object_name: 'index_name', table_name: 'achievements',
|
||||
valitador_name: 'different_definition_indexes')
|
||||
end
|
||||
|
||||
it 'does not create a schema inconsistency record' do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
expect { execute }.not_to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
|
||||
context 'when the GitLab issue is open' do
|
||||
it 'does not create a new schema inconsistency record' do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
schema_inconsistency.issue.update!(state_id: Issue.available_states[:opened])
|
||||
|
||||
expect { execute }.not_to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the GitLab is not open' do
|
||||
it 'creates a new schema inconsistency record' do
|
||||
allow(Gitlab).to receive(:com?).and_return(true)
|
||||
schema_inconsistency.issue.update!(state_id: Issue.available_states[:closed])
|
||||
|
||||
expect { execute }.to change { Gitlab::Database::SchemaValidation::SchemaInconsistency.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe 'Update Environment', feature_category: :deployment_management do
|
||||
include GraphqlHelpers
|
||||
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
let_it_be(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
|
||||
let_it_be(:developer) { create(:user).tap { |u| project.add_maintainer(u) } }
|
||||
|
||||
let(:environment_id) { environment.to_global_id.to_s }
|
||||
let(:current_user) { developer }
|
||||
|
||||
let(:mutation) do
|
||||
graphql_mutation(:environment_update, input)
|
||||
end
|
||||
|
||||
context 'when updating external URL' do
|
||||
let(:input) do
|
||||
{
|
||||
id: environment_id,
|
||||
external_url: 'https://gitlab.com/'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates successfully' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { environment.reload.external_url }.to('https://gitlab.com/')
|
||||
|
||||
expect(graphql_mutation_response(:environment_update)['errors']).to be_empty
|
||||
end
|
||||
|
||||
context 'when url is invalid' do
|
||||
let(:input) do
|
||||
{
|
||||
id: environment_id,
|
||||
external_url: 'http://${URL}'
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns error' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.not_to change { environment.reload.external_url }
|
||||
|
||||
expect(graphql_mutation_response(:environment_update)['errors'].first).to include('URI is invalid')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when updating tier' do
|
||||
let(:input) do
|
||||
{
|
||||
id: environment_id,
|
||||
tier: 'STAGING'
|
||||
}
|
||||
end
|
||||
|
||||
it 'updates successfully' do
|
||||
expect do
|
||||
post_graphql_mutation(mutation, current_user: current_user)
|
||||
end.to change { environment.reload.tier }.to('staging')
|
||||
|
||||
expect(graphql_mutation_response(:environment_update)['errors']).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Environments::UpdateService, feature_category: :environment_management do
|
||||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:developer) { create(:user).tap { |u| project.add_developer(u) } }
|
||||
let_it_be(:reporter) { create(:user).tap { |u| project.add_reporter(u) } }
|
||||
let_it_be(:environment) { create(:environment, project: project) }
|
||||
|
||||
let(:service) { described_class.new(project, current_user, params) }
|
||||
let(:current_user) { developer }
|
||||
let(:params) { {} }
|
||||
|
||||
describe '#execute' do
|
||||
subject { service.execute(environment) }
|
||||
|
||||
let(:params) { { external_url: 'https://gitlab.com/' } }
|
||||
|
||||
it 'updates the external URL' do
|
||||
expect { subject }.to change { environment.reload.external_url }.to('https://gitlab.com/')
|
||||
end
|
||||
|
||||
it 'returns successful response' do
|
||||
response = subject
|
||||
|
||||
expect(response).to be_success
|
||||
expect(response.payload[:environment]).to eq(environment)
|
||||
end
|
||||
|
||||
context 'when params contain invalid value' do
|
||||
let(:params) { { external_url: 'http://${URL}' } }
|
||||
|
||||
it 'returns an error' do
|
||||
response = subject
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.message).to match_array("External url URI is invalid")
|
||||
expect(response.payload[:environment]).to eq(environment)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is reporter' do
|
||||
let(:current_user) { reporter }
|
||||
|
||||
it 'returns an error' do
|
||||
response = subject
|
||||
|
||||
expect(response).to be_error
|
||||
expect(response.message).to eq('Unauthorized to update the environment')
|
||||
expect(response.payload[:environment]).to eq(environment)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -19,7 +19,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_1_primary, identifier_1_cve],
|
||||
scanner: scanner_1,
|
||||
severity: :low
|
||||
severity: :low,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94610'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -27,7 +28,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_1_primary, identifier_1_cve],
|
||||
scanner: scanner_1,
|
||||
severity: :low
|
||||
severity: :low,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94611'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -36,7 +38,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
identifiers: [identifier_2_primary, identifier_2_cve],
|
||||
location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34),
|
||||
scanner: scanner_2,
|
||||
severity: :medium
|
||||
severity: :medium,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94612'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -45,7 +48,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
identifiers: [identifier_2_primary, identifier_2_cve],
|
||||
location: build(:ci_reports_security_locations_sast, start_line: 32, end_line: 34),
|
||||
scanner: scanner_2,
|
||||
severity: :medium
|
||||
severity: :medium,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94613'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -54,7 +58,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
identifiers: [identifier_2_primary, identifier_2_cve],
|
||||
location: build(:ci_reports_security_locations_sast, start_line: 42, end_line: 44),
|
||||
scanner: scanner_2,
|
||||
severity: :medium
|
||||
severity: :medium,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94614'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -62,7 +67,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_cwe],
|
||||
scanner: scanner_3,
|
||||
severity: :high
|
||||
severity: :high,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94615'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -70,7 +76,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_cwe],
|
||||
scanner: scanner_1,
|
||||
severity: :critical
|
||||
severity: :critical,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94616'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -78,7 +85,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_wasc],
|
||||
scanner: scanner_1,
|
||||
severity: :medium
|
||||
severity: :medium,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94617'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -86,7 +94,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
build(:ci_reports_security_finding,
|
||||
identifiers: [identifier_wasc],
|
||||
scanner: scanner_2,
|
||||
severity: :critical
|
||||
severity: :critical,
|
||||
uuid: '61eb8e3e-3be1-4d6c-ba26-4e0dd4f94618'
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -190,8 +199,8 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
finding_cwe_2,
|
||||
finding_wasc_2,
|
||||
finding_cwe_1,
|
||||
finding_id_2_loc_2,
|
||||
finding_id_2_loc_1,
|
||||
finding_id_2_loc_2,
|
||||
finding_wasc_1,
|
||||
finding_id_1
|
||||
])
|
||||
|
|
@ -217,9 +226,32 @@ RSpec.describe Security::MergeReportsService, '#execute', feature_category: :cod
|
|||
let(:identifier_cve) { build(:ci_reports_security_identifier, external_id: 'CVE-2019-123', external_type: 'cve') }
|
||||
let(:identifier_semgrep) { build(:ci_reports_security_identifier, external_id: 'rules.bandit.B105', external_type: 'semgrep_id') }
|
||||
|
||||
let(:finding_id_1) { build(:ci_reports_security_finding, identifiers: [identifier_bandit, identifier_cve], scanner: bandit_scanner, report_type: :sast) }
|
||||
let(:finding_id_2) { build(:ci_reports_security_finding, identifiers: [identifier_cve], scanner: semgrep_scanner, report_type: :sast) }
|
||||
let(:finding_id_3) { build(:ci_reports_security_finding, identifiers: [identifier_semgrep], scanner: semgrep_scanner, report_type: :sast) }
|
||||
let(:finding_id_1) do
|
||||
build(
|
||||
:ci_reports_security_finding,
|
||||
identifiers: [identifier_bandit, identifier_cve],
|
||||
scanner: bandit_scanner,
|
||||
report_type: :sast,
|
||||
uuid: '21ab978a-7052-5428-af0b-c7a4b3fe5020')
|
||||
end
|
||||
|
||||
let(:finding_id_2) do
|
||||
build(
|
||||
:ci_reports_security_finding,
|
||||
identifiers: [identifier_cve],
|
||||
scanner: semgrep_scanner,
|
||||
report_type: :sast,
|
||||
uuid: '21ab978a-7052-5428-af0b-c7a4b3fe5021')
|
||||
end
|
||||
|
||||
let(:finding_id_3) do
|
||||
build(
|
||||
:ci_reports_security_finding,
|
||||
identifiers: [identifier_semgrep],
|
||||
scanner: semgrep_scanner,
|
||||
report_type: :sast,
|
||||
uuid: '21ab978a-7052-5428-af0b-c7a4b3fe5022')
|
||||
end
|
||||
|
||||
let(:bandit_report) do
|
||||
build(:ci_reports_security_report,
|
||||
|
|
|
|||
Loading…
Reference in New Issue